java的四种引用,强弱软虚和jvm优化
Posted h-c-g
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java的四种引用,强弱软虚和jvm优化相关的知识,希望对你有一定的参考价值。
1、强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:
Object o=new Object(); // 强引用
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:
o=null; // 帮助垃圾收集器回收此对象
显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:
private transient Object[] elementData; public void clear() modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0;
在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
2、软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str=new String("abc"); // 强引用 SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
当内存不足时,等价于:
If(JVM.内存不足()) str = null; // 转换为软引用 System.gc(); // 垃圾回收器进行回收
虚引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
Browser prev = new Browser(); // 获取页面进行浏览 SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用 if(sr.get()!=null) rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取 else prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了 sr = new SoftReference(prev); // 重新构建
这样就很好的解决了实际的问题。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3、弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
String str=new String("abc"); WeakReference<String> abcWeakRef = new WeakReference<String>(str); str=null;
当垃圾回收器进行扫描回收时等价于:
str = null; System.gc();
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
下面的代码会让str再次变为一个强引用:
String abc = abcWeakRef.get();
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
这个引用不会在对象的垃圾回收判断中产生任何附加的影响。
public class ReferenceTest private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>(); public static void checkQueue() Reference<? extends VeryBig> ref = null; while ((ref = rq.poll()) != null) if (ref != null) System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).id); public static void main(String args[]) int size = 3; LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>(); for (int i = 0; i < size; i++) weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq)); System.out.println("Just created weak: " + weakList.getLast()); System.gc(); try // 下面休息几分钟,让上面的垃圾回收线程运行完成 Thread.currentThread().sleep(6000); catch (InterruptedException e) e.printStackTrace(); checkQueue(); class VeryBig public String id; // 占用空间,让线程进行回收 byte[] b = new byte[2 * 1024]; public VeryBig(String id) this.id = id; protected void finalize() System.out.println("Finalizing VeryBig " + id); class VeryBigWeakReference extends WeakReference<VeryBig> public String id; public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) super(big, rq); this.id = big.id; protected void finalize() System.out.println("Finalizing VeryBigWeakReference " + id);
最后的输出结果为:
Just created weak: [email protected] Just created weak: [email protected] Just created weak: [email protected] Finalizing VeryBig Weak 2 Finalizing VeryBig Weak 1 Finalizing VeryBig Weak 0 In queue: Weak 1 In queue: Weak 2 In queue: Weak 0
4、虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
5、总结
Java4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
通过图来看一下他们之间在垃圾回收时的区别:
当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收
jvm的堆栈内存。
安装Java开发软件时,默认安装包含两个文件夹,一个JDK(Java开发工具箱),一个JRE(Java运行环境,内含JVM),其中JDK内另含一个JRE。如果只是运行Java程序,则JRE已足够;而JDK则只有开发人员才用到。
优化内存,主要是在bin/catalina.bat/sh 配置文件中进行。linux上,在catalina.sh中添加:
JAVA_OPTS="-server -Xms1G -Xmx2G -Xss256K -Djava.awt.headless=true -Dfile.encoding=utf-8 -XX:MaxPermSize=256m -XX:PermSize=128M -XX:MaxPermSize=256M"
1)错误提示:java.lang.OutOfMemoryError:Java heap space
Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,有可能导致系统无法运行。常见的问题是报Tomcat内存溢出错误,Outof Memory(系统内存不足)的异常,从而导致客户端显示500错误,一般调整Tomcat的-Xms和-Xmx即可解决问题,通常将-Xms和-Xmx设置成一样,堆的最大值设置为物理可用内存的最大值的80%。
set JAVA_OPTS=-Xms512m-Xmx512m
2)错误提示:java.lang.OutOfMemoryError: PermGenspace
PermGenspace的全称是Permanent Generationspace,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGenspace中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行precompile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。解决方法:
set JAVA_OPTS=-XX:PermSize=128M
3)在使用-Xms和-Xmx调整tomcat的堆大小时,还需要考虑垃圾回收机制。如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。
2、连接数优化:
#优化连接数,主要是在conf/server.xml配置文件中进行修改。
找到Connectorport="8080" protocol="HTTP/1.1",增加maxThreads和acceptCount属性(使acceptCount大于等于maxThreads),如下:
- <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" acceptCount="500" maxThreads="400" />
其中:
- maxThreads:tomcat可用于请求处理的最大线程数,默认是200
- minSpareThreads:tomcat初始线程数,即最小空闲线程数
- maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭
- acceptCount:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理.默认100
在server.xml中增加executor节点,然后配置connector的executor属性,如下:
- <Executor name="tomcatThreadPool" namePrefix="req-exec-" maxThreads="1000" minSpareThreads="50" maxIdleTime="60000"/>
- <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool"/>
其中:
- namePrefix:线程池中线程的命名前缀
- maxThreads:线程池的最大线程数
- minSpareThreads:线程池的最小空闲线程数
- maxIdleTime:超过最小空闲线程数时,多的线程会等待这个时间长度,然后关闭
- threadPriority:线程优先级
注:当tomcat并发用户量大的时候,单个jvm进程确实可能打开过多的文件句柄,这时会报java.net.SocketException:Too many open files错误。可使用下面步骤检查:
- ps -ef |grep tomcat 查看tomcat的进程ID,记录ID号,假设进程ID为10001
- lsof -p 10001|wc -l 查看当前进程id为10001的 文件操作数
- 使用命令:ulimit -a 查看每个用户允许打开的最大文件数
内存优化:
- -server:启用jdk的server版本。
- -Xms:虚拟机初始化时的最小堆内存。
- -Xmx:虚拟机可使用的最大堆内存。 #-Xms与-Xmx设成一样的值,避免JVM因为频繁的GC导致性能大起大落
- -XX:PermSize:设置非堆内存初始值,默认是物理内存的1/64。
- -XX:MaxNewSize:新生代占整个堆内存的最大值。
- -XX:MaxPermSize:Perm(俗称方法区)占整个堆内存的最大值,也称内存最大永久保留区域。
一、Eclise 中设置jvm内存 1、Eclise 中设置jvm内存: 改动eclipse的配置文件,对全部project都起作用 改动eclipse根文件夹下的eclipse.ini文件 -vmargs //虚拟机设置 -Xms40m //初始内存 -Xmx256m //最大内存 -Xmn16m //最小内存 -XX:PermSize=128M //非堆内存 -XX:MaxPermSize=256M 2、Eclise 中设置jvm内存:jres VM Arguments參数的设置,对全部project都起作用 打开eclipse window-preferences-Java-Installed JREs -Edit-Default VM Arguments 在VM自变量中输入:-Xmx128m -Xms64m -Xmn32m -Xss16m 3、Eclise 中设置jvm内存:RunConfigurations VM arguments參数设置,仅仅对这个project起作用 在Eclipse中-->右击project/要执行的主类-->Run As-->RunConfigurations-->(x)=Arguments-->VM arguments 中增加 -Xmx36m 然后Apply-->Run 这上面的36指的是给java虚拟机分配的最大堆内存大小,单位是MB,也就是说上面的那句话的含义是JVM的最大堆内存是36MB 4、Eclise 中设置jvm内存:Debug Configurations VM arguments參数设置,仅仅对这个project起作用 在Eclipse中-->右击project/要执行的主类-->Debug As-->DebugConfigurations-->(x)=Arguments-->VM arguments 中增加-Xmx36m 然后Apply-->Run 这上面的36指的是给java虚拟机分配的最大堆内存大小。单位是MB,也就是说上面的那句话的含义是JVM的最大堆内存是36MB
二、Tomcat内存设置 windows下在catalina.bat的第一行添加: Java代码 :set JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m linux下在catalina.sh的第一行添加: Java代码 :JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m 实例+具体解释 设置Tomcat启动的初始内存其初始空间(即-Xms)是物理内存的1/64。最大空间(-Xmx)是物理内存的1/4。 能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置 实例,下面给出1G内存环境下java jvm 的參数设置參考: JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true " JAVA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=256m -XX: NewSize=192m -XX:MaxNewSize=384m" CATALINA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=256m -XX:NewSize=192m -XX:MaxNewSize=384m" Linux: 在/usr/local/apache-tomcat-5.5.23/bin 文件夹下的catalina.sh加入: JAVA_OPTS=‘-Xms512m -Xmx1024m‘要加“m”说明是MB。否则就是KB了,在启动tomcat时会 报内存不足。 -Xms:初始值-Xmx:最大值-Xmn:最小值 Windows: 在catalina.bat最前面增加set JAVA_OPTS=-Xms128m -Xmx350m 假设用startup.bat启动tomcat,OK设置生效.够成功的分配200M内存. 可是假设不是运行startup.bat启动tomcat而是利用windows的系统服务启动tomcat服务,上面的设置就不生效了,就是说set JAVA_OPTS=-Xms128m -Xmx350m 没起作用.上面分配200M内存就OOM了.. windows服务运行的是bin\tomcat.exe.他读取注冊表中的值,而不是catalina.bat的设置. 解决的方法: 改动注冊表HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\Tomcat Service Manager\Tomcat5\Parameters\JavaOptions 原值为-Dcatalina.home="C:\ApacheGroup\Tomcat 5.0"-Djava.endorsed.dirs="C:\ApacheGroup\Tomcat 5.0\common\endorsed"-Xrs增加 -Xms300m -Xmx350m 重起tomcat服务,设置生效
常见的三种Java内存溢出
1. java.lang.OutOfMemoryError: Java heap space —-JVM Heap(堆)溢出。JVM 在启动的时候会自动设置 JVM Heap 的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。可以利用 JVM提供的 -Xmn -Xms -Xmx 等选项可进行设置。Heap 的大小是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98% 的时间是用于 GC,且可用的 Heap size 不足 2% 的时候将抛出此异常信息。解决方法:手动设置 JVM Heap(堆)的大小。
Heap size 设置 JVM堆的设置是指java程序执行过程中JVM能够调配使用的内存空间的设置.JVM在启动的时候会自己主动设置Heap size的值。其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。 提示:在JVM中假设98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。 提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为同样,而-Xmn为1/4的-Xmx值。 解决方法: 手动设置Heap size 改动TOMCAT_HOME/bin/catalina.bat,在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行: Java代码 set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m 或改动catalina.sh 在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行: JAVA_OPTS="$JAVA_OPTS -server -Xms800m -Xmx800m -XX:MaxNewSize=256m"
2.java.lang.OutOfMemoryError: PermGen space —- PermGen space溢出。PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被 JVM 存放Class 和 Meta 信息的,Class 在被 Load 的时候被放入 PermGen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运行期对 PermGen space 进行清理,所以如果你的 APP 会载入很多 CLASS 的话,就很可能出现 PermGen space 溢出。解决方法: 手动设置 MaxPermSize 大小
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序执行期对PermGen space进行清理,所以假设你的应用中有非常CLASS的话,就非常可能出现PermGen space错误。这样的错误常见在webserver对JSP进行pre compile的时候。假设你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。 解决方法: 1. 手动设置MaxPermSize大小 改动TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在Java代码 “echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行: set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m catalina.sh下为: Java代码 JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"
3.java.lang.StackOverflowError —- 栈溢出栈溢出了,JVM 依然是采用栈式的虚拟机,这个和 C 与 Pascal 都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量),那么栈区也不过是需要 1MB 的空间。通常栈的大小是 1-2MB 的。通常递归也不要递归的层次过多,很容易溢出。解决方法:修改程序。
以上是关于java的四种引用,强弱软虚和jvm优化的主要内容,如果未能解决你的问题,请参考以下文章