Java垃圾收集机制

Garbage Collection

Posted by Jerry on April 20, 2017

Java和C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。——引用自《深入理解JVM》

垃圾收集是针对于堆和方法区进行垃圾收集(而虚拟机栈、本地方法栈、程序计数器是线程隔离的数据区,随线程而生,随线程而消失。)

为什么要理解垃圾收集机制

  1. 更好地排查各种内存溢出、内存泄漏问题
  2. 写代码时候可以有时候可以帮助GC收集垃圾

几种常见的垃圾收集算法

堆中存放着java 中几乎所有的对象实例,收集垃圾前,首先需要判断哪些对象“已死”,哪些“还存活”。

什么是有向可达图(或者说可达性分析)

GC Roots的对象作为起始点,当一个对象到GC Roots没有任何路径(引用链),说明这个对象是不可达的,说明它“已死”。 GC Roots主要有以下几部分:

  • 虚拟机栈(就是存放变量)中的引用对象
  • 方法区中类静态属性引用对象
  • 方法区中常量引用对象
  • 本地方法栈中JNI(就是Native方法)引用对象

总结一句话,GC Roots就是引用对象:)

需要注意的是,即使在可达性分析中不可达的对象,也并非是“非死不可”,因为对象被标记后,在进行GC时候会调用该对象的finalize()方法,如果该对象没有覆盖该方法,或者说该方法已经被调用过一次,虚拟机将回收该内存finalize()方法是对象逃脱死亡的最后一次机会,finalize()方法只会被调用一次。

引用计数算法

在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

复制回收算法

标记-清除算法

标记整理算法

JVM中的垃圾收集

年轻代和年老代区别

  1. 年轻代分为两部分 Eden区:当一个实例被创建了,首先会被存储在堆内存年轻代的 Eden 区中。 Survivor区(S0 和 S1):作为年轻代 GC(Minor GC)周期的一部分,存活的对象(仍然被引用的)从 Eden 区被移动到 Survivor 区的 S0 中。类似的,垃圾回收器会扫描 S0 然后将存活的实例移动到 S1 中。
  2. 年老代
    • 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
    • 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
  3. 持久代(Permanent Generation) 用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

JVM中几种收集器

  1. Serial收集器(复制算法) 新生代单线程收集器,标记和清理都是单线程,优点是简单高效。
  2. Serial Old收集器(标记-整理算法) 老年代单线程收集器,Serial收集器的老年代版本。
  3. ParNew收集器(停止-复制算法)  新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
  4. Parallel Scavenge收集器(停止-复制算法) 并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。
  5. Parallel Old收集器(停止-复制算法) Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先
  6. CMS(Concurrent Mark Sweep)收集器(标记-清理算法) 高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择
  7. G1收集器

参考文献

  1. 《深入理解JVM》
  2. http://javapapers.com/java/java-garbage-collection-introduction/