文章目录
前言
本文介绍JVM垃圾回收相关的知识。主要内容有:判断对象是否存活、对象引用的类型、垃圾回收算法、垃圾回收器。
判断对象是否存活
垃圾回收主要是对对象进行回收,故首先需要找出那些不再存活的对象,主要有两种方法。
引用计数法
用一个引用值标记对象被引用的次数,当引用值为0,表明对象可被回收。
优点:实现简单、效率高
缺点:如果对象之间存在相互引用,引用值无法归零,无法回收对象。
可达性分析
判断对象是否在GCRoot的引用路径上,如果没有,表明对象可被回收。
对象引用的类型
对象有四种引用类型。强软弱虚,用男人女人老人死人来类比,应该很好记忆。
强引用
一般我们new出来的对象
软引用
非必需的对象,在oom即将发生之前被回收
弱引用
非必需的对象,在下一次gc时被回收
虚引用
任何时候都可以被gc
垃圾回收算法
清楚哪些对象需要被回收后,下一步操作就是回收对象,这里引出回收对象的几种算法。
标记清除法
标记待回收对象,直接清除。
优点:效率高
缺点:内存不规整(有碎片)
复制算法
内存分两块,第一块标记存活对象,然后复制到第二块,最后清空第一块。
优点:内存规整
缺点:空间浪费、复制耗时
标记整理法
标记待回收对象,直接清除,然后整理存活对象。
优点:内存规整
缺点:整理耗时
分代算法
堆内存划分为新生代和老年代,新生代由eden和survivor区(两块)组成,对象在eden区创建,经过gc后还存活,就移入survivor区。经过n(默认16)次gc后还存活,移入老年代。新生代和老年代会使用不同的垃圾回收方式。一般来说,新生代采用复制算法,因为大部分对象在gc后会被回收,剩下的存活对象较少,复制消耗低。由于survivor区分为两块。当gc发生时,存活对象(在eden和其中一块survivor区中)被复制到survivor中的一块,另外一块被清除。
垃圾回收器
垃圾回收算法是一种思路,而垃圾收集器是这些算法的具体实现。根据具体的业务场景选择合适的垃圾回收器。
新生代使用:Serial/ParNew/Parallel Scavenge
老年代使用:Serial Old/Parallel Old/CMS/G1
Serial(复制)
单线程,适合单CPU,gc线程执行时,应用线程暂停,即Stop The World现象。
ParNew(复制)
Serial的多线程版本,适合多CPU
Parallel Scavenge(复制)
和ParNew类似,区别是该回收器关注吞吐量的提高,即尽量的减少垃圾收集时间,但并不意味着减少STW的时间,主要适合一些对短暂停顿时间不敏感的程序,如后台的一些计算、批处理。
Serial Old(标记整理)
Serial的老年代版本
Parallel Old(标记整理)
Parallel Scavenge的老年代版本
CMS(标记清除)
CMS的出现是为了减少停顿时间的,适用于用户交互频繁的程序,注重用户体验。主要有几个步骤。
-
初始标记:标记和GCRoots直接关联的对象,停顿时间较短
-
并发标记:标记待清除对象,gc线程和应用线程并发执行
-
重新标记:并发标记阶段由于应用线程是执行的,所以还需要再次进行标记,停顿时间较短
-
并发清除:对标记的对象进行清除,gc线程和应用线程并发执行,由于是标记清除法,故内存不规整
G1
只需要G1即可同时管理新生代和老年代,使用标记整理法,块与块之间使用复制算法。兼顾停顿时间和吞吐量。
感想
JVM还是很有必要了解的,装装逼也是可以的。最近在看一本叫《深度工作》的书,让我重新意识到专注的重要性,这篇文章的知识点和整理是我在心无杂念,高度专注的前提下完成的。