JVM垃圾回收
1. 概念理解
1.1. 并行(Parallel)与并发(Concurrent)
并行:指多个垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
并发:指用户线程与垃圾收集线程同时执行
1.2. Minor GC 与 Major GC
Minor GC:指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
Major GC:指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC。Major GC的速度一般会比Minor GC慢10倍以上。
1.3. 吞吐量
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%
2. 对象存活判断
回收的第一步是标记出哪些需要回收,为此需要有一种方式来判断对象是否存活,存活的不用回收
判断的方式是可达性分析,通过遍历对象的引用树来找出存活的对象
所谓的引用树本质上是有根的图结构,它沿着对象的根句柄向下查找到活着的节点,并标记下来;其余没有被标记的节点就是死掉的节点,这些对象就是可以被回收的
遍历从GC Roots开始,那么哪些对象可作为GC Roots对象?
- 虚拟机栈中应用的对象
- 方法区里面的静态对象
- 方法区常量池的对象
- 本地方法栈JNI应用的对象
3. 垃圾回收算法
3.1. 标记-清除算法(Mark-Sweep)
标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
优点:简单
缺点:容易产生大量碎片
3.2. 复制算法(Copying)
它将可用内存划分为两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
Java的新生代串行垃圾回收器中使用了复制算法的思想。新生代分为 eden 空间、survivor from 空间、survivor to 空间 3 个部分。其中survivor from 空间和 survivor to 空间可以视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。默认Eden与Survivor的比例是8:1
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
优点:简单,不易产生碎片
缺点:可用内存变少,且如果存活对象较多,则复制的效率会大大降低
3.3. 标记-整理算法(Mark-Compact)
该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在年轻代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高。
标记-压缩算法修复了标记-清除算法的短板——它将所有标记的也就是存活的对象都移动到内存区域的开始位置。这种方法的缺点就是GC暂停的时间会增 长,因为你需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址。相对于标记-清除算法,它的优点是不容易产生碎片;相对于复制算法,它的优点是可用内存空间不会减少。
优点:不易产生碎片,充分利用可用内存空间
缺点:用户等待时间变长
3.4. 分代回收算法(Generational Collection)
分代回收算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活周期的不同将内存划分为几块。根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率。
以HotSpot虚拟机为例,它将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几 次回收后依然存活,对象就会被放入称为老生代的内存空间。在老生代中,几乎所有的对象都是经过几次垃圾回收后依然得以幸存的。因此,可以认为这些对象在一 段时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。如果依然使用复制算法回收老生代,将需要复制大量对象。再加上老生代的回收性价比也要低于新生代,因此这种做法也是不可取的。根据分代的思想,可以对老年代的回收使用与新生代不同的标记-压缩算法,以提高垃圾回收效率。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(一般为8:1:1),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
(PS:年轻代一般使用复制算法,年老代一般使用标记整理算法)
4. 垃圾收集器
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
4.1. Serial 收集器
串行GC,采用复制算法
4.2. ParNew 收集器
并行GC,采用复制算法,相当于Serial的多线程版本
4.3. Parallel Scavenge 收集器
并行GC,采用复制算法。追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间 / (用户线程时间+GC线程时间)
这里注意,经过测试JDK 1.8.0_152默认垃圾收集器为:PS MarkSweep 和 PS Scavenge,而并不是G1
4.4. Serial Old 收集器
串行GC,采用标记整理算法,Serial老年代版本
4.5. Parallel Old 收集器
并行GC,采用标记整理算法,它是Parallel Scavenge收集器的老年代版本
4.6. CMS 收集器
并行GC,并发,采用标记清除算法
CMS(Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。
CMS整个过程分为4个步骤:
- 初始标记(CMS initial mark) -stop the world
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark) -stop the world
- 并发清除(CMS concurrent sweep)
4.7. G1 收集器
最先进的收集器,独立完成分代回收
5. 查看垃圾回收器
5.1. 命令行
java -XX:+PrintCommandLineFlags -version
也就是说,本例中JVM使用的垃圾回收器是 Parallel Scavenge (新生代,并行GC) + Serial Old (老年代,串行GC)
5.2. 代码
6. 其它相关