Java垃圾回收机制
JVM框架
Java虚拟机HotSpot的框架:
JVM主要组成部分:Class Loader(类加载器)、Runtime Data Area(运行时数据区)、执行引擎(Execution Engine)。
JVM垃圾回收简介
Step 1: Marking
GC算法在扫描存活对象时通常需要从Root节点开始,扫描所有存活对象的引用,构建出对象图。
Root:静态字段、方法参数、局部变量、CPU寄存器
Step 2: Normal Deletion
删除没有被引用的对象,释放空间。
Step 2a: Deletion with Compacting
删除垃圾并压缩存活的引用对象,有利于提高内存分配的效率。
Generational Garbage Collection(分代垃圾回收)
HotSpot堆结构:
Young Generation:从eden区分配新对象,eden区满后,发生一次minor garbage collection,把eden区和一个survivor区中存活的对象移动到另一个survivor区中,存活的对象age加1,当存活的对象age达到一个阈值时晋升到Old Generation。
Old Generation:保存存活长久对象的地方,Old Generation满后会发生major garbage collection(full garbage collection)。
Stop the World Event:minor garbage collection和major garbage collection都是Stop the World Event,即垃圾回收的时候会暂停程序中线程的执行。
Permanent generation:保存JVM中用于描述类和方法的元数据信息。
整个GC的流程总结图:
GC分代的基本假设是:绝大部分对象的生命周期都非常短暂,存活时间短。
分配小对象的开销负担小,不要吝啬去创建。
GC最喜欢这种小而短命的对象。
让对象的生命周期尽可能短,例如在方法体内创建,使其能尽快地在YoungGC中被回收,不会晋升(romote)到年老代(Old Generation)。
对象分配的优化:尽量避免大对象的分配,当对象大到Eden Generation放不下时,JVM只能尝试去Old Generation分配,这种情况需要尽可能避免,因为一旦在Old Generation分配,这个对象就只能被Old Generation的GC或是FullGC回收了。
不可变对象可以减少GC的压力:Hotspot JVM为了提高YoungGC的性能,避免每次YoungGC都扫描Old Generation中的对象引用,采用了卡表(Card Table) 的方式。简单来说,当Old Generation中的对象发生对Young Generation中的对象产生新的引用关系或释放引用时,都会在卡表中响应的标记上标记为脏(dirty),而YoungGC时,只需要扫描这些dirty的项就可以了。可变对象对其它对象的引用关系可能会频繁变化,并且有可能在运行过程中持有越来越多的引用,特别是容器。这些都会导致对应的卡表项被频繁标记为dirty。而不可变对象的引用关系非常稳定,在扫描卡表时就不会扫到它们对应的项了。
指定容器初始化大小可以减少GC的压力:每次容器扩容分配更大的空间,可能会增加GC的次数。
各类引用:java.lang.ref.Reference有几个子类,用于处理和GC相关的引用。JVM的引用类型简单来说有几种:
Strong Reference,最常见的引用。
Weak Reference,当没有指向它的强引用时会被GC回收。
Soft Reference,只当临近OOM时才会被GC回收。
Phantom Reference,主要用于识别对象被GC的时机,通常用于做一些清理工作。
Garbage Collector(垃圾收集器)
JVM中会在以下情况触发回收:对象没有被引用,作用域发生未捕捉异常,程序正常执行完毕,程序执行了System.exit(),程序发生意外终止。
JVM中标记垃圾使用的算法是一种根搜索算法。简单的说,就是从一个叫GC Roots的对象开始,向下搜索,如果一个对象不能达到GC Roots对象的时候,说明它可以被回收了。这种算法比一种叫做引用计数法的垃圾标记算法要好,因为它避免了当两个对象啊互相引用时无法被回收的现象。
JVM中对于被标记为垃圾的对象进行回收时又分为了一下3种算法:
1.标记清除算法,该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收,这种算法在存活对象较多时比较高效,但会产生内存碎片。
2.复制算法,该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效。
3.标记整理算法,标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题。
HotSpot 1.6版使用的垃圾收集器如下图(图中两个收集器之间有连线,说明它们可以配合使用):
1.Serial GC
从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停。这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中。
2.ParNew GC
是在SerialGC的基础上,增加了多线程机制。但是如果机器是单CPU的,这种收集器是比SerialGC效率低的。
3.Parrallel Scavenge GC
这种收集器又叫吞吐量优先收集器,而吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%。Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置。
4.ParallelOld
是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代只能使用串行回收收集器。
5.Serial Old
是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器。
6.CMS(Concurrent Mark Sweep)
又称响应时间优先回收器,主要目标就是:低应用停顿时间。使用标记清除算法(不进行压缩,会产生内存碎片),同时它又是一个使用多线程并发回收的垃圾收集器。他的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记(CMS initial mark)、并发标记(CMS concurrent mark)、重新标记(CMS remark)、并发清除(CMS concurrent sweep)和并发重置。其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上来说,CMS 收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。
7.Garbage First(G1)
比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间。与CMS收集器相比,G1收集器是基于标记-压缩算法的。因此,它不会产生内存碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。
对于垃圾收集器的组合使用可以通过下表中的参数指定:
参考:
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
http://coolshell.cn/articles/11541.html
http://www.ibm.com/developerworks/cn/java/j-lo-JVMGarbageCollection/index.html
http://www.importnew.com/16388.html