关于JVM的垃圾回收
- 1、垃圾回收的几个基本问题
-
- 1.1、什么场景下该使用什么垃圾回收策略?
- 1.2、垃圾回收发生在哪些区域?
- 1.3、对象在什么时候能够被回收?
- 2、可达性分析
-
- 2.1、可达性分析说明图
- 2.2、什么是根对象
- 2.3、关于几种引用类型
- 2.4、关于finalize() 方法
- 3、三种基本垃圾回收算法
-
- 3.1、标记-清除(Mark-Sweep)算法
- 3.2、标记-整理(Mark-Compact)算法
- 3.3、复制(Copy)算法
- 3.4、三种基础垃圾回收算法的对比
- 4、两种综合垃圾回收算法
-
- 4.1、分代收集算法
-
- 4.1.1、关于分代收集算法
- 4.1.2、分代回收对象
- 4.1.3、不同区域触发垃圾回收的条件
- 4.1.4、关于堆内存(Heap)的两点注意项
- 4.1.5、分代收集算法的好处与调优原则
- 4.2、增量算法
1、垃圾回收的几个基本问题
1.1、什么场景下该使用什么垃圾回收策略?
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR9EMNpWT5tGVNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLjljZzQjZzkjN2gTNzQGM5MmYwQzM3IGNmVmZ5gTYxM2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
- 在对内存要求苛刻的场景:想办法提高垃圾的回收效率,多回收掉一些对象,腾出更多内存。
- 在CPU使用率高的情况下:降低高并发时垃圾回收的频率,让CPU更多的去执行你的业务,而不是垃圾回收。
1.2、垃圾回收发生在哪些区域?
- 堆(Heap) 和 方法区线程共享,是垃圾回收需要考虑的区域。
- 虚拟机栈、本地方法栈以及程序计数器都是线程隔离的,随着线程的创建而创建,随着线程的销毁而销毁。所以不需要考虑垃圾回收。
1.3、对象在什么时候能够被回收?
-
引用计数法(java 未使用该方法):
|_ 通过对象的引用计数器来判断该对象是否被引用。当对象间循环引用时,引用技术法就不起作用了。
-
可达性分析:
|_ 以根对象(GC Roots)作为起点向下搜索,走过的路径被称为引用链(Reference Chain),如果某个对象到根对象没有引用链相连时,就认为这个对象是不可达的,可以回收。
2、可达性分析
2.1、可达性分析说明图
以下为可达性分析说明图(图 2-1):
2.2、什么是根对象
思考( 图2-1 )中的根对象(GC Roots)具体包含了哪些对象?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即 Native方法)引用的对象。
需要注意的是:
一个对象即使不可达,也不一定会被回收
。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
见以下说明图(图 2-2):
2.3、关于几种引用类型
-
强引用(StrongReference)
|_
|_形如 Object obj = new Object() 的引用。通过关键字new创建的对象所关联的引用就是强引用。
如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
-
软引用
|_
|_软引用通过java.lang.SoftReference类实现。形如 SoftReference<String> str = new SoftReference<>("hello")
|_是用来描述一些有用但非必需的对象。
软引用关联的对象,只有在内存不足的时候才会回收。
-
弱引用
|_
|_弱引用通过WeakReference类实现。形如 WeakReference<String> str = new WeakReference<>("hello")
|_弱引用也是用来描述非必需对象的。
无论内存是否充足,都会回收被弱引用关联的对象。
-
虚引用
|_
形如 ReferenceQueue<String> queue = new ReferenceQueue<>();
|_ 不影响对象的生命周期,如果一个对象只有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动,必须和引用队列(ReferenceQueue)配合使用。当垃圾回收器准备回收一个对象时,如果发现他还有虚引用,就会回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。PhantomReference<String> pha = new PhantomReference<>("hello", queue);
2.4、关于finalize() 方法
- finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
- 避免使用finalize()方法,操作不当可能会导致问题。
- finalize() 优先级低,何时会被调用无法确定,因为什么时间发生 GC不确定。
- 建议使用 try…catch…finally 来替代 finalize()。
3、三种基本垃圾回收算法
3.1、标记-清除(Mark-Sweep)算法
- 标记需要回收的对象。
- 清理掉要回收的对象。
关于JVM的垃圾回收(三)1、垃圾回收的几个基本问题2、可达性分析3、三种基本垃圾回收算法4、两种综合垃圾回收算法
3.2、标记-整理(Mark-Compact)算法
- 标记需要回收的对象。
- 把所有的存活对象压缩到内存的一端。
- 清理掉边界外的所有空间。
3.3、复制(Copy)算法
- 把内存分为两块,每次只使用一块。
- 将正在使用的内存中的存活对象复制到未使用的内存中去,然后清除掉正在使用的内存中的所有对象。
- 交换两个内存的角色,等待下次回收。
3.4、三种基础垃圾回收算法的对比
回收算法 | 优点 | 缺点 |
---|---|---|
标记-清除 | 实现简单 | 存在内存碎片、分配内存速度会受影响 |
标记-整理 | 无碎片 | 整理存在开销 |
复制 | 性能好、无碎片 | 内存利用率低 |
4、两种综合垃圾回收算法
4.1、分代收集算法
4.1.1、关于分代收集算法
分代收集算法的说明:
-
把内存分成多个区域,不同区域使用不同的回收算法回收对象。
-
各种商业虚拟机堆内存的垃圾收集基本上都采用了分代收集。
-
根据对象的存活周期,把内存分成多个区域,不同区域使用不同的回收算法回收对象。
4.1.2、分代回收对象
- 新生代回收(Minor GC | Young GC)
- 老年代回收(Major GC)
- 清理整个堆 (Full GC)
- 因为 Major GC 执行时,一般会伴随的执行一次 Minor GC ,所以 Major GC 约等于 Full GC
4.1.3、不同区域触发垃圾回收的条件
- 当伊甸园空间不足时,触发新生代(Minor GC)垃圾回收。
- 触发老年代(Full GC)垃圾回收的条件:
- 老年代(Tenured)空间不足
- 元空间(Metaspace)不足
- 要晋升到老年代(Tenured)的对象,所占用的空间大于老年代的剩余空间。
- 显示调用了 System.gc()
- 建议垃圾回收器执行垃圾回收
- -XX:+DisableExplicitGC 参数,忽略掉System.gc()的调用
4.1.4、关于堆内存(Heap)的两点注意项
1、新建的对象不一定分配到伊甸园(Eden)
-
对于大于 -XX:PretenureSizeThreshold,就会直接分配到老年代。
-
新生代空间不够。
2、对象不一定要达到年龄才进入老年代
-
。动态年龄:如果幸存区(Survivor)空间中多有相同年龄对象大小的总和,大于 Survivor 空间的一半,那么年龄大于等于该年龄的对象就直接进入老年代
4.1.5、分代收集算法的好处与调优原则
4.2、增量算法
- 增量算法就是每次只收集一小片区域的内存空间的垃圾
.