学习内容:
GC、垃圾回收器、垃圾回收算法
目录
一、GC垃圾回收算法
1.1 可达性分析法
1.1.1 GC ROOT对象:
1.1.3 四种引用方式:
⭐小结:
1.2 其他回收算法
二、详解分代回收法:
三、垃圾回收器
3.1 串行垃圾回收器:
3.2 吞吐量优先回收器:
3.3 响应时间优先:
3.4 G1 garbage first
一、GC垃圾回收算法
1.1 可达性分析法
1. 标记可达对象 2. 回收不可达对象
也是虚拟机采用的方法,扫描所有的GC ROOT对象,如果对象能够被一个GC ROOT对象,直接或间接找到,那么它就可能不会被回收(这取决于引用类型),反之可回收。
1.1.1 GC ROOT对象:
指在Java虚拟机(JVM)的内存中,被直接或者间接引用的对象。这些对象被视为不可回收的,并且是垃圾收集器的根节点。它们的作用是为垃圾回收器提供一个初始的扫描位置,以便确定哪些对象是可达的,哪些对象是不可达的。垃圾回收器会从GC Root开始扫描,并标记所有可达对象,最终将不可达对象回收掉。
具体来说,GC Root对象主要包括以下几类:
- 虚拟机栈(栈桢中的本地变量表)中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI(Java Native Interface)的引用的对象。
1.1.3 四种引用方式:
- 强引用:通过GC ROOT直接或间接找到的,称为强引用,那么它也肯定不会被垃圾回收。
- 软引用:没有被GC ROOT对象直接引用,当发生一次垃圾回收后内存不足就回收掉它。
- 弱引用:也是没有被GC ROOT对象直接引用,不管内存足不足都会回收
如果引用的对象被回收了,那么该引用就会放入引用队列,可调用poll方法,回收引用队列里面的对象。
- 虚引用:虚引用对应的是ByteBuffer直接内存,它不会被JVM直接回收掉,所以会有一个虚引用对象指向这快地址,从而当ByteBuffer对象被回收是,这快内存也会被回收。
- 终结器引用:在垃圾回收时会先被放入队列,在由一个优先级较低的线程回收。
⭐小结:
JVM通过可达性分析法找到引用对象,那些可以到达的对象是否被回收取决于何种引用,强引用一定不会被回收,软引用和弱引用根据JVM内存足不足判断是否回收。
1.2 其他回收算法
- 标记-清除(Mark-Sweep)算法:
- 标记阶段:从根(GC ROOT)对象(如全局变量、栈中的变量等)开始,通过追踪对象之间的引用关系,标记所有可达的对象。
- 清除阶段:遍历堆内存,清除未被标记的对象。此算法会产生内存碎片,影响内存的使用效率。
- 复制(Copying)算法:
- 将内存划分为大小相等的两块,每次只使用其中一块。当一块内存用完后,将存活的对象复制到另一块内存中,然后清除当前内存块的所有对象。
- 此算法适用于对象存活率较低的场景,如新生代。但当存活对象较多时,复制效率会大大降低。
- 标记-整理(Mark-Compact)算法:
- 标记阶段与标记-清除算法相同。
- 在清除阶段,不是直接清除对象,而是将所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
- 此算法解决了内存碎片的问题,提高了内存的利用率。
- 分代收集(Generational Collection)算法:
- 根据对象的存活周期将内存划分为几块,如新生代和老年代。不同的区域采用不同的收集算法。
- 新生代通常使用复制算法,因为新生代的对象存活率较低。老年代则采用标记-清除或标记-整理算法。
- 引用计数(Reference Counting)算法:
- 为每个对象维护一个引用计数器,当有新的引用指向对象时,计数器加1;当引用断开时,计数器减1。
- 当计数器为0时,对象被视为垃圾,可以被回收。
- 但此算法无法处理循环引用的问题,因此在Java中并未采用。
二、详解分代回收法:
JVM堆内存分为新生代区域和老年代区域,新生代又分为伊甸园和幸存区。
- 创建对象先在伊甸园分配空间,空间不足,发生minor gc
- minor gc:典型的复制算法,扫描一遍GC ROOT对象,标记可达对象,然后将对象复制到幸存区的一块区域(from或to),清空伊甸园内存,幸存区对象寿命+1
- 如果寿命足够大,就会放到老年代区域
- 如果minor gc时幸存区内存不够,会放入直接放入到老年代,如果老年代也不够了,会执行Full GC。
- 注意:如果大对象新生代内存不够,老年代够,它会直接放到老年代里面,不会触发GC。
小结:
新生代一般采用复制算法,老年代一般采用标记清除或整理算法
虚拟机参数:
三、垃圾回收器
3.1 串行垃圾回收器:
虚拟机参数设置:-XX:+UseSerialGC=Serial + SerialOld。
- Serial:工作在新生代区域,采用复制算法
- SerialOld:工作在老年代区域,采用标记-整理算法
3.2 吞吐量优先回收器:
虚拟机参数设置:-XX:+UseParallelGC,-XX:ParallerOldGC
- Parallel Scavenge收集器:
- 是一款新生代收集器,基于标记复制算法。
- 能够并行收集,以吞吐量优先。
- Parallel Old收集器:
- 是Parallel Scavenge收集器的老年代版本。
- 使用多线程和“标记-整理”算法。
总结:开启多个线程,这取决于你的CPU核数,并行,用户线程不能执行
3.3 响应时间优先:
- ParNew收集器:
- 实质上是Serial收集器的多线程并行版本。
- 可以同时采用多条线程进行垃圾处理,主要用于新生代。
- CMS(concur-mark-sweep)
- 工作在老年代区域
执行步骤:
- 初始标记:标记GC Roots能直接关联到的对象,此过程速度很快,但仍然存在“Stop The World”问题,即在此阶段,其他线程需要暂停。
- 并发标记:进行GC Roots Tracing的过程,找出存活对象(直接或间接),同时用户线程可以并发执行。
- 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。此阶段同样存在“Stop The World”问题。
- 并发清除:对标记的对象进行清除回收。
虚拟机参数设置:-XX:+UseConcMarkSweepGC,-XX:UseParNewGC
总结:并发,只有初始标记和重新标记会有STW,其他阶段用户可并发执行,减少STW的时间,但是要预留空间给浮动垃圾,注意,当老年代中内存碎片过多,会退化为SerialOld做一次垃圾回收。
3.4 G1 garbage first
JDK1.9之后,默认的垃圾回收器
使用场景:
- 同时注重吞吐量和低延迟,默认的暂停目标为200ms
- 超大堆内存优势大,会将堆划分为多个大小相等的region
- 整体上是标记+整理,两个区域之间是复制算法
相关参数:
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=size
- -XX:MaxGCPauseMillis=time
垃圾回收阶段:
G1这里我比较懵,大佬请移步请他文章。