Skip to content

JVM GC

  1. GC 发生在堆中,新生代、幸存区(0,1)、老年区
  2. 重GC
    1. Full GC:收集整个堆,包括新生代,老年代,永久代(在JDK1.8及以后,永久代被移除,换为metaspace元空间)等所有部分的模式。
    2. 触发条件:
      1. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前MinorGC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。
      2. 老年代空间不够分配新的内存(或永久代空间不足,但只是JDK1.7有的,这也是用元空间来取代永久代的原因,可以减少FulGC的频率,减少GC负担,提升其效率)。
      3. 由 Eden 区、From Space 区向 To Space 区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
      4. 调用 System.gc 时,系统建议执行 Full GC,但是不必然执行。
  3. 轻GC
    1. 只收集新生代的GC
    2. 触发条件:当 Eden 区满了,触发 Minor GC

禁用显示垃圾回收

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 对象,一些常驻的异常对象 (比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(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

终结器引用

回收效率低下

无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 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)垃圾回收器的设计目标之一就是取代之前的垃圾回收器,包括 CMS(Concurrent Mark-Sweep)以及之前的所有垃圾回收方法。G1 垃圾回收器在 JDK 7 中首次引入,而在 JDK 9 中成为了默认的垃圾回收器。

G1 垃圾回收器的设计是为了解决之前垃圾回收器在处理大堆时可能遇到的性能问题,尤其是在应用程序需要低停顿时间的情况下。它通过使用分代垃圾回收的思想,结合了并行性和并发性,以提供可预测的停顿时间和高吞吐量。相比于 CMS,G1 具有更好的整体性能,并且能够更好地处理大堆内存。

因此,如果你在使用 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 垃圾回收器通常会更加注重内存的使用效率,因为它旨在尽量减少停顿时间,减少对堆内存的占用。