JVM垃圾回收算法和垃圾收集器
一: JVM 垃圾回收算法
根搜索算法:
程序把所有的引用节点看做一张图,从一个节点GC Root开始,寻找对应的引用节点,直到所有的引用节点寻找完毕,剩下的即被没有引用的节点,可以被回收。
可以当做GC Root的对象有如下四种:
1,虚拟机栈中引用的对象
2,方法区中静态属性引用的对象
3,方法区中常量引用的对象
4,本地方法栈(Native栈)中引用的对象
根据根搜索算法,找出内存中存在的垃圾并进行回收,JVM提供了四种垃圾回收算法:
1,复制算法
把内存分为大小相等的From和To两块,每次只使用其中一块From,当From被占满,则把From中的存活对象复制到To,同时释放From中的所有内存。
由于在实际程序运行时,有98%的对象生命周期非常短暂,创建之后很快就被回收了,因此用于存储可存活对象的空间可以小一些。现在的JVM是把新生代内存分为一个较大的Eden和两个大小相等的较小的Survivor内存区,每次只使用Eden和其中一个Survivor区。
复制算法不产生碎片,但是浪费空间,始终有一个区域是空余的。
2,标记-清除算法
算法有两个阶段:标记和清除。
容易产生大量碎片,会出现即时内存足够的情况下,因为没有够大的连续内存空间,导致会触发一次垃圾回收动作。
3,标记-整理算法
算法有两个阶段:标记和整理。
在每次标记出需回收的内存后,把存活的对象都移动到内存的一端。解决了标记-清除算法产生大量碎片的问题。
4,分代收集算法
根据对象的存活周期不同,把Java堆内存分为新生代和年老代两个部分。新生代由于只有少量对象能存活下来,复制成本低,可以使用复制算法。年老代由于对象的生命周期长,因此采用标记-整理或标记-清除算法。
二: JVM垃圾收集器
Java堆内存划分了新生代和老年代两部分,Java虚拟机也提供了几种不同的垃圾收集器,这些垃圾收集器在堆内存中搭配使用。
1,串行垃圾收集器(Serial Collector)
串行收集器组合(Serial + Serial Old);
Serial 收集器使用复制算法,单线程执行收集任务,在执行垃圾收集任务时,必须暂停当前的所有工作线程,适合于单核处理器平台。是目前JVM在Client模式下新生代默认的垃圾收集器。
Serial Old收集器是Serial收集器在老年代的版本,也是单线程收集器,使用标记-整理算法。是目前JVM在Client模式下老年代默认的垃圾收集器。
可通过参数设置:-XX:+UseSerialGC 使用串行垃圾收集器
2,ParNew垃圾收集器
串行垃圾收集器的多线程版本。JVM在Server模式下新生代默认的垃圾收集器
3,Parallel Scavenge 垃圾收集器
使用复制算法,也是一个多线程垃圾收集器,致力于达到高吞吐量的目的,是高吞吐量优先的垃圾收集器。自适应调整策略是ParNew和Parallel Scavenge的最大区别。
可通过参数设置:
-XX:-UseParallelGC 新生代使用并行清除的垃圾收集器
-XX:MaxGCPauseMillis=n 设置垃圾收集最大暂停时间
-XX:GCTimeRation=n 大于0小于100的整数,程序运行时间占总时间的比例。公式为1/(1+n),默认值为99,即垃圾收集运行最大1%的时间
-XX:+UseAdaptiveSizePolicy 打开该开关,则自动指定新生代内存大小,以及其中Eden区和Survivor区的比例,虚拟机会根据当前系统性能监测情况,动态调整这些参数,以达到最大的吞吐量。这种方式叫做GC自适应调整策略。
4,Parallel Old垃圾收集器
是Parallel Scavenge收集器的老年代版本,使用标记-整理算法。
-XX:-UseParallelOldGC 新生代和老年代使用并行清除的垃圾收集器(与该参数-XX:-UseParallelGC同步启用)
5, CMS(Concurrent Mark Sweep)垃圾收集器
CMS致力于获取最短的垃圾收集停顿时间,在多处理器平台上,能使垃圾收集工作和用户线程同时工作。是一种老年代垃圾收集器,使用多线程的标记-清除算法。官方强烈建议使用G1收集器来代替CMS收集器。
CMS工作过程分为四个阶段:初始标记,并发标记,重新标记,并发清除。其中初始标记和重新标记需要暂停所有的工作线程。
初始标记:这个阶段,虚拟机需要停顿所有正在执行的任务,STW(Stop the World)。从GC Root对象开始,只扫描跟GC Root对象直接关联的对象,并做标记,这个过程虽然STW,但是很快就能完成。
并发标记:这个阶段,是在初始标记之后关联的对象上继续向下追溯标记。与应用程序并发执行,不会STW。
重新标记:这个阶段,虚拟机会STW,为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
并发清除:清理垃圾对象,并发执行。
CMS收集器有以下三个不足:
a, 对CPU资源非常敏感,默认启动的垃圾收集线程数:(CPU个数+3)/4。
b, 无法处理浮动垃圾,可能导致Concurrent ModeFailure而导致另一次GC,浮动垃圾是指即在并发清除过程中工作线程产生的垃圾,需要等到下一次GC才能清除掉。
Concurrent ModeFailure 是指CMS在GC时,需要在老年代预留一部分空间供并发工作时使用,如果在GC时,预留的空间不够分配,则会出现Concurrent ModeFailure,此时虚拟机会启用预备方案,使用Serial Old收集器进行垃圾回收。
c, CMS是基于标记-清除算法,因此会产生大量的垃圾碎片。当无法找到一个足够大的连续区域存放对象时,会触发一次Full GC。
可通过参数设置:
-XX: +UseConcMarkSweepGC 使用CMS垃圾收集器。
-XX:CMSInitiatingOccupancyFraction=70 是指内存占用达到该比例70%时启动CMS进行垃圾回收。
-XX:+UseCMSCompactAfFullCollection 默认启用,在每次Full GC后,进行内存压缩整理。
-XX:CMSFullGCBeforeCompaction 默认值为0,是指在CMS Full GC多少次后,进行内存压缩整理。与参数UseCMSCompactAfFullCollection 配合使用。
6,G1(Garbage-First)垃圾收集器
G1致力于在高吞吐量和短的停顿时间这两者取得一个最好的平衡。G1全面支持JDK7 Update4及后续版本,主要用于内存大,多处理器的机器。
在Java 9之后,G1已经取代CMS成为默认的垃圾收集器。
G1有两个最突出的改进:
a,基于标记-整理算法,不产生碎片
b,可以非常精确的控制停顿时间,可以在不牺牲吞吐量的前提下,实现短的停顿时间进行垃圾回收。
G1收集器避免全区的垃圾回收,它把堆内存划分为几个大小固定的独立区域,并且跟踪这几个区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的垃圾收集时间,优先回收垃圾最多的区域。
可通过参数设置:
-XX:+UseG1GC 使用G1垃圾收集器
三: Java四种应用类型的垃圾回收方式
1,强引用:
强引用不会被GC回收。
2,软应用:
如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存
3,弱引用:
弱引用与软引用的区别在于:垃圾回收器一旦发现了弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些弱引用的对象
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnqueued方法返回对象是否被垃圾回收器标记。
1 the PhantomReference.get() method always returns null
2 another difference is that the PhantomReference is enqueueed only after the finalize() method has been called.
3 If you want the reference to be added to the reference queue, you have to keep a strong reference to the Phantom reference.(这一点很重要,为了保持phantom reference被回收后,添加到reference queue中,必须有一个strong reference 引用到这些phantom reference)
当phantom reference 被enqueue到队列时,phantom reference引用的referent已经在内存中删除
当对象不可达,但是调用finalize之后又变得可达的情况存在,在finalize函数中通过this指针让其他句柄执行本身即可,但是再下次回收时不会再调用finalize,因为只能调用一次。
protected void finalize()
{
main.ref=this; // 恢复本对象,让本对象可达
}
finalize函数的调用具有很大的不确定性:可能不被调用——有资源泄漏的风险
在某些情况下,finalize()压根儿不被调用。比如在JVM退出的当口,内存中那些对象的finalize函数可能就不会被调用了。
因此一些清理工作如文件的关闭,连接的关闭等不要放到finalize函数中,要在程序中单独进行管理,一般finalize只做C/C++内存的回收
很多时候,我们认为一个对象的finalize()
方法执行过以后,如果对象没有自救,这个对象马上就被垃圾回收了。但是实际不然,有个时间差,要到下次垃圾回收时才会真正回收掉这个对象。
四, FullGC的触发条件
1,直接调用System.gc();
2,老年代空间不足:
在新生代的对象转入,以及创建大对象,大数组时可能出现空间不足的情况。
3,永久代空间不足:
永久代不属于堆空间,也就是方法区。存放的是虚拟机加载的类的元数据信息:如常量,静态变量,即时编译器编译后的代码。
系统要加载的类,反射的类和调用的方法较多时,会出现空间不足的情况。
4,CMS GC时出现promotion failed和concurrent mode failed:
promotion failed 是在Min GC时,有对象在survivor放不下而只能放到老年代,而此时老年代的空间不足引起的。
concurrent mode failed是在CMS GC时,有对象要放入老年代,而此时老年代的空间不足引起的。
5,统计得到MinGC后晋升到老年代的平均大小大于老年代的剩余空间大小。