您的位置:首页 > 其它

JVM系列五:垃圾回收器

2015-07-18 21:47 543 查看
前面说到Java虚拟机的内存分配有两种,静态(栈)分配和动态(堆)分配,所以对于内存回收策略,也有两种:静态内存回收,动态内存回收

静态内存回收

如下面这段代码

public void staticData(int arg){
long a=1;
Object obj=new Object();
}

其中参数arg,a是基本数据类型,obj是一个引用。javac在编译时就已经确定了这些变量的静态存储空间,其中arg会分配4个字节,long会分配8个字节,引用obj占用4个字节,所以这个方法占用的静态内存空间是4+8+4=16字节。这些内存会在栈上分配,方法执行完后就被回收。而方法结束后,obj虽然被回收了,但obj所指向的对象是存放在堆中的,它是可以被共享的,所以不一定会随着方法执行结束而消失。只有等到这个对象不再被使用后才会被回收,这类对象的回收是动态的。

动态内存回收

动态内存回收有垃圾回收器自动完成,其需要解决的问题有三个:

如何确定一个对象是否不再被使用

确定后又在什么时候回收呢

怎么回收这些对象

可达性分析算法

在主流的商用程序语言(Java,C#,Lisp)的主流实现中,都是称通过可达性分析来判断对象是否存活。这个算法的基本思路是通过一系列称为
GC Roots
的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到
GC Roots
没有任何引用链相连时,则证明此对象不可用。

在Java语言中,可作为
GC Roots
的对象包括以下几种:

虚拟机栈(栈帧中局部变量表)中引用的对象

方法区中类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI(即一般说的native方法)引用的对象

关于Java中的引用

垃圾收集算法

垃圾收集算法的实现有很多种,包括
标记-清除算法
复制算法
标记-整理算法
分代收集算法
等。对于现在大多数商业虚拟机来说,主要还是用的是
分代收集算法
。其原理就是将Java堆内存根据对象的存活时间分为多个区(新生代和老年代),每个区使用不同的收集算法(上述)。对于新生代,对象存活存活量少,可以用
复制
算法;而对于老年代,大多对象都能长期存活,就可以使用
标记-清除
标记-整理
算法来回收。这样减少了每次垃圾收集时所要扫描的对象的数量,从而提高了垃圾回收效率

垃圾收集器

垃圾收集器是垃圾收集算法的具体实现,对于HotSpot虚拟机,包含的垃圾收集器如下图所示:



上图中展示了7种作用于不同区的收集器,如果两个收集器之间存在连线,说明他们可以搭配使用。

主要分为四类:Serial Collector,Parallel Collector,CMS Collector,G1

Serial Collector(串行回收器)

JVM在client模式下默认的GC模式,包括Serial和Serial Old,可以通过JVM配置参数
-XX:+UseSerialGC
来指定GC使用该收集算法。由于是单线程GC工作,所以无论Minor GC还是Full GC,都会造成应用程序的全部停止。在Full GC中,会对整个Old区进行压缩。

在创建新对象时,如果新对象的大小超过Eden区的总大小,或者超过了PretenureSizeThreshold配置参数配置的大小,就只能在Old区分配。

当Eden空间不足时,首先检查每次Minor GC时复制到Old区的平均对象大小是否大于Old的剩余空间,如果大于,则直接触发Full GC,否则,再看HandlePromotionFailure(-XX:-HandlePromotionFailure)的值,如果为true,则触发Minor GC,否则,触发Minor GC后再触发Full GC。也就是说,如果每次需要复制的对象的大小超过了Old的剩余空间大小,就说明当前Old区的剩余空间大小已经不能满足Minor GC时从Eden,Survivor区复制过来的对象的存放空间了,所以只能触发Full GC。

我们知道,在Minor GC中,除了回收Eden去的非活动对象外,还会把一些“老对象”复制到Old区,而“老对象”的定义可以通过配置参数MaxTenuringThreshold来设置,如设置 -XX:MaxTenuringThreshold=10,则如果一个对象已经经历了10次Minor GC后仍然存活在Young区,则下次Minor GC时,直接将这个对象复制到Old区。还有一种情况是,如果Minor GC时,Survivor区存放不下这些将要存放到Survivor区的对象时,也会将这些对象复制到Old区;如果Old区空间不足,则触发Full GC,Full GC会清除堆中的所有垃圾对象

Parallel Collector(并行回收器)

使用多线程的方式,利用多CUP来提高GC的效率,主要以到达一定的吞吐量为目标;Server模式下默认GC模式。Parallel Collector根据Minor GC,Full GC的不同分为三种,分别是
ParNew
ParallelScavenge
ParallelOld


ParNew
可以通过
-XX:+UseParNewGC
参数来指定,它的对象分配和回收策略与Serial Collector类似,只是回收的线程是多线程并行回收的。在Parallel Collector中还有一个UseAdaptiveSizePolicy配置,这个参数用来动态控制Eden,Survivor From ,Survivor To的TenuringThreshold大小的,以便控制那些对象经过多少次回收后可以直接放入Old区

ParallelScavenge
在Server模式下默认的GC模式,可以通过 -XX:UseParallelGC参数来强制指定,并行回收的线程数可以通过 -XX:ParallelGCThreads来指定,这个值有一个计算公式,如果CPU核数小于8,线程数可以和核数一样;否则,值可以设置为 3+(cpu_core5)/8。通过-Xmn来设置Young区的大小,通过SurvivorRatio参数控制Eden,Survivor From,Survivor To的大小比例,如 -XX:SurvivorRatio=8 则表示Eden:Survivor From:Survivor To=8:1:1,其默认值也是8。

当在Eden区中申请内存空间时,如果Eden区剩余空间不够,则看当前申请的空间是否大于等于Eden总空间的一半,如果大于,则直接在Old中分配,如果小于,则触发Minor GC,触发前首先检查每次Minor GC时复制到Old区的平均大小是否大于Old的剩余空间,如果大于,则再触发Full GC。此次触发GC后仍然会按照这个规则重新检查一次,如果仍然满足,Full GC会再一次触发。

当Old区不足时,会触发Full GC,如果配置了ScavengeBeforeFullGC,则在Full GC之前,会先触发Minor GC

ParallelOld
可以通过
-XX:+UseParallelOldGC
来设置,并行回收的线程数可以通过-XX:ParallelGCThreads来指定,这个值有一个计算公式,如果CPU核数小于8,线程数可以和核数一样;否则,值可以设置为 3+(cpu_core5)/8。它与ParallelGC不同之处在于,前者Full GC会清除整个堆的垃圾对象,而后者只清楚部分,并对部分空间压缩。GC时同样也会造成应用程序的全部停止。

CMS Collector(并发收集器)

可以通过
-XX:+UseConcMarkSweepGC
来指定,并发的线程数默认为4,也可以通过ParallelCMSThreads来指定。

CMS GC和上面讨论的GC不太一样。它既不是Minor GC,也不是Full GC,而是基于二者之间的一种GC,它的触发规则是检查Old区或者Perm的使用率,当比例达到一定值时触发CMS GC,回收Old区内存空间。这个比例可以通过CMSInitiatingOccupancyFraction参数来指定,默认为92%。这个默认值是通过((100-MinHeapFreeRatio)+(double)(CMSTriggerRatioMinHeapFreeRatio)/100.0)/100.0计算出来的,其中MinHeapFreeRatio=40,CMSTriggerRatio=80。如果Perm区也使用CMS GC,可以通过-XX:+CMSClassUnloadingEnabled设置,默认值也是92%,也是通过公式((100-MinHeapFreeRatio)+(double)(CMSTriggerPermRatioMinHeapFreeRatio)/100.0)/100.0,其中MinHeapFreeRatio=40,CMSTriggerPermRatio=80

触发CMS GC时回收的只是Old区和Perm区的垃圾对象,和Minor GC,Full GC基本没有关系。在这种模式下,Minor GC的触发规则和回收规则与Serial Collector基本一致,不同之处在于此GC回收为多线程。而触发Full GC有两种情况,一种是Eden区分配失败,Minor GC后分配到Survivor区时Survivor不够,Old区也不够。另一种情况是当CMS GC正在进行时,向Old区申请内存失败。

在hotspot1.6中如果使用了这种GC方式,在程序中显示的调用System.gc(),且设置了ExplicitGCInvokesConcurrent参数,那么在使用NIO时可能会引发内存泄漏。

CMS GC何时执行JVM还会有一些时机选择,如当前CPU是否繁忙。因此它有一个计算规则,并根据这个规则来动态调整。这也给JVM带来了一些开销,如果要禁止这个动态调整,禁止CMS GC自动触发,则可以配置参数 -XX:+UseCMSInitiatingOccuoancyOnly来实现

G1

参考这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: