Skip to content

JVM GC

GC 的区域划分、触发条件和停顿行为取决于垃圾收集器,不能把 Serial/Parallel/CMS 的分代术语直接套用到所有收集器。

  1. Minor/Young GC:传统分代收集器对新生代进行回收,常见触发条件是 Eden 无法继续分配。
  2. Major/Old GC:主要回收老年代,但该术语在不同资料和收集器中的含义并不完全一致。
  3. Full GC:通常表示一次覆盖范围较大的全堆或整堆压缩回收,具体行为由收集器决定;可能由分配失败、晋升失败、元空间压力、显式 GC 请求等触发。
  4. System.gc() 只是向 JVM 提出建议,不保证立即执行;可用 -XX:+DisableExplicitGC 忽略多数显式请求,但应评估对直接内存清理等场景的影响。

禁用显式垃圾回收

shell
-XX:+DisableExplicitGC

Dump

shell
jmap -dump:format=b,live,file=1.bin pid

如何判断对象是否可以回收

可达性分析法

从一个被称为GC Roots的对象向下搜索,如果有一个对象到GC Roots没有任何引用链相连接,则说明此对象不可用

  • 虚拟机栈中引用的对象
  • 方法区类静态属性引用的变量
  • 方法区常量池引用的对象
  • 本地方法栈NI引用的对象

mat 可以查看

在 Java 技术体系里面,固定可作为 GC Roots 的对象包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象。
  • Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象、一些常驻异常对象(例如 NullPointerExceptionOutOfMemoryError)以及系统类加载器。
  • 所有被同步锁(synchronized 关键字)持有的对象。
  • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。

引用计数法

  1. 判断每个对象的引用次数,需要给每个对象创建一个计数器(占用内存)
  2. 如果是 0 则清理

存在问题

  1. 存在循环引用导致无法回收

四种引用

强引用

只有所有 GC Roots 对象都不通过强引用该对象,该对象才能被垃圾回收

强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

java
Object obj = new Object()

软引用

内存不充足且没有强引用会清空

仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象

可以配合引用队列来释放软引用自身

java
SoftReference

弱引用

没有强引用会清空

仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象

可以配合引用队列来释放弱引用自身

java
WeakReference

虚引用

需要放到引用队列,在释放后会通过ReferenceHandler调用Cleaner

必须配合引用队列使用,主要配合 ByteBuifer 使用,被引用对象回收时,会将虚引用入队,由Reference Handler 线程调用虚引用相关方法释放直接内存

java
PhantomReference

终结器引用

finalize() 已被弃用并计划移除,不应在新代码中依赖。资源释放应使用 try-with-resources、AutoCloseable 或 Cleaner 等机制。

回收效率低下

无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被用对象

java
    /**
     * Called by the garbage collector on an object when garbage collection
     * determines that there are no more references to the object.
     * A subclass overrides the {@code finalize} method to dispose of
     * system resources or to perform other cleanup.
     * <p>
     * The general contract of {@code finalize} is that it is invoked
     * if and when the Java&trade; virtual
     * machine has determined that there is no longer any
     * means by which this object can be accessed by any thread that has
     * not yet died, except as a result of an action taken by the
     * finalization of some other object or class which is ready to be
     * finalized. The {@code finalize} method may take any action, including
     * making this object available again to other threads; the usual purpose
     * of {@code finalize}, however, is to perform cleanup actions before
     * the object is irrevocably discarded. For example, the finalize method
     * for an object that represents an input/output connection might perform
     * explicit I/O transactions to break the connection before the object is
     * permanently discarded.
     * <p>
     * The {@code finalize} method of class {@code Object} performs no
     * special action; it simply returns normally. Subclasses of
     * {@code Object} may override this definition.
     * <p>
     * The Java programming language does not guarantee which thread will
     * invoke the {@code finalize} method for any given object. It is
     * guaranteed, however, that the thread that invokes finalize will not
     * be holding any user-visible synchronization locks when finalize is
     * invoked. If an uncaught exception is thrown by the finalize method,
     * the exception is ignored and finalization of that object terminates.
     * <p>
     * After the {@code finalize} method has been invoked for an object, no
     * further action is taken until the Java virtual machine has again
     * determined that there is no longer any means by which this object can
     * be accessed by any thread that has not yet died, including possible
     * actions by other objects or classes which are ready to be finalized,
     * at which point the object may be discarded.
     * <p>
     * The {@code finalize} method is never invoked more than once by a Java
     * virtual machine for any given object.
     * <p>
     * Any exception thrown by the {@code finalize} method causes
     * the finalization of this object to be halted, but is otherwise
     * ignored.
     *
     * @throws Throwable the {@code Exception} raised by this method
     * @see java.lang.ref.WeakReference
     * @see java.lang.ref.PhantomReference
     * @jls 12.6 Finalization of Class Instances
     */
    protected void finalize() throws Throwable { }

标记-复制

  1. 每次 GC 后 伊甸园区存活的对象都会被移动到幸存者区域
  2. 不断的交换(复制)幸存区域,当这个对象交换了 15 次以后,则迁移到老年区
    1. -XX:MaxTenuingThreshold=15
  3. 优点:内存碎片少
  4. 缺点:浪费内存空间,永远有一半的空间浪费 (1/10)
  5. 适合对象存活度较低的时候

标记-清除

  1. 利用可达性分析法,扫描对象进行标记,是否使用
  2. 扫在描没有标记的对象,清除
  3. 优点:不需要额外的内存空间
  4. 缺点:两次扫描浪费时间,会产生内存碎片

标记-压缩(整理)

压缩在标记清除中产生的内存碎片

  1. 利用可达性去遍历内存,把存活对象和垃圾对象进行标记
  2. 将所有的存活的对象向一段移动,将端边界以外的对象都回收掉
  3. 相对于标记-清除算法多了一次碎片移动

总结

内存效率:复制 > 标记清除 > 标记压缩

内存整齐度:复制 = 标记压缩 > 标记清除

内存利用率:复制 < 标记压缩 = 标记清除

年轻代:

  • 存活率低,使用复制算法

老年代:

  • 区域大:存活率低
  • 标记清除和标记压缩算法
    • 先标记清除,如果内存碎片太多的时候则会进行标记压缩算法

分代收集算法:

根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老生代

  • 在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集
  • 老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收

方法区回收

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP 的重加载等,否则通常是很难达成的。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾回收器

Serial收集器

ParNew收集器

Parallel Scavenge收集器

Serial Old收集器

Parallel Old收集器

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网网站或者基于浏览器的 B/S 系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS 收集器就非常符合这类应用的需求。

CMS 收集器是基于标记-清除算法实现。

过程

初始标记(CMS initial mark)

并发标记(CMS concurrent mark)

重新标记(CMS remark)

并发清除(CMS concurrent sweep)

Garbage First收集器

G1(Garbage First)面向大堆和可控停顿目标,在 JDK 7 中引入,并从 JDK 9 起成为 HotSpot 的默认垃圾收集器。CMS 已在 JDK 14 中移除。

G1 将堆划分为多个 Region,并结合并行与并发阶段以满足停顿时间目标。停顿目标是软目标,不保证每次都能达到;G1、Parallel GC、ZGC、Shenandoah 的选择应以真实负载压测为准,不能笼统断言某一种收集器整体性能最好。

因此,如果你在使用 JDK 9 或更新版本,那么 G1 垃圾回收器很可能是你的最佳选择,特别是对于大堆内存、需要可预测停顿时间和高吞吐量的应用程序。

维度对比

并行度

  • Parallel GC(并行垃圾回收器):Parallel GC 是一种使用多线程并行进行垃圾回收的回收器。它在整个堆上执行垃圾回收,并且在回收时会停止应用程序的执行。
  • CMS(Concurrent Mark-Sweep):CMS 垃圾回收器是一种并发的垃圾回收器。它在大部分时间内都与应用程序并发执行,并且尽量减少垃圾回收期间的停顿时间。

停顿时间

  • Parallel GC:尽管 Parallel GC 也会在执行垃圾回收时造成一些停顿,但这些停顿通常会比串行垃圾回收器(Serial GC)更短。但是,这些停顿时间通常会比 CMS 长。
  • CMS:CMS 垃圾回收器旨在减少垃圾回收期间的停顿时间。它通过在大部分时间内与应用程序并发执行来实现这一点,尽管会在标记和清除阶段引入一些停顿。

吞吐量

  • Parallel GC:Parallel GC 旨在提供更高的吞吐量,因为它使用多线程并行执行垃圾回收,可以充分利用多核处理器的优势。
  • CMS:CMS 的重点在于减少停顿时间,因此可能会牺牲一些吞吐量。

内存占用

  • Parallel GC:Parallel GC 可能会导致更多的内存占用,因为它在执行垃圾回收时需要暂停应用程序的执行,并且在此期间可能会产生更多的中间对象。
  • CMS:CMS 垃圾回收器通常会更加注重内存的使用效率,因为它旨在尽量减少停顿时间,减少对堆内存的占用。