JVM学习笔记(二):垃圾回收
垃圾回收 (Garbage Collection)
- 如何判断对象可以回收
- 垃圾回收算法
- 分代垃圾回收
- 垃圾回收器
- 垃圾回收调优
① 如何判断对象可以回收
引用计数法
可达性分析算法
- Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。
- 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收。
- 哪些对象可以作为GC Root?
四种引用
- 强引用
- 只有所有GC Roots对象都不通过“强引用”引用该对象,该对象才能被垃圾回收。
- 软引用 (SoftReference)
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时回再次触发垃圾回收,回收软引用对象。
- 可以配合引用队列来释放软引用自身。
- 弱引用 (WeakReference)
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象。
- 可以配合引用队列来释放弱引用自身。
- 虚引用 (PhantomReference)
- 必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。
- 终结器引用 (FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC时才能回收被引用对象。
② 垃圾回收算法
标记清除
定义:Mark Sweep
- 速度较快
- 会造成内存碎片
标记整理
定义:Mark Compact
- 速度慢
- 没有内存碎片
复制
定义:Copy
- 不会有内存碎片
- 需要占用双倍内存空间
③ 分代垃圾回收
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to
- minor gc会引发stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15 (4 bit)
- 当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,那么触发full gc,STW的时间更长
相关JVM参数
- 堆初始大小 -Xms
- 堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
- 新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size)
- 幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
- 幸存区比例 -XX:SurvivorRatio=ratio
- 晋升阈值 -XX:MaxTenuringThreshold=threshold
- 晋升详情 -XX:+PrintTenuringDistribution
- GC详情 -XX:+PrintGCDetails -verbose:gc
- FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC
④ 垃圾回收器
- 串行
- 单线程
- 堆内存较小,适合个人电脑
- 吞吐量优先
- 多线程
- 堆内存较大,多核CPU
- 让单位时间内,STW的时间最短 0.2+0.2=0.4,垃圾回收时间占比最低,这样就称吞吐量高
- 响应时间优先
- 多线程
- 堆内存较大,多核CPU
- 尽可能让单次STW的时间最短 0.1+0.1+0.1+0.1+0.1=0.5
串行
-XX:+UseSerialGC = Serial + SerialOld
吞吐量优先
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
响应时间优先
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
G1
定义:Garbage First
- 2004 论文发布
- 2009 JDK 6u14 体验
- 2012 JDK 7u4 官方支持
- 2017 JDK 9 默认
使用场景:
- 同时注重吞吐量(Throughout)和低延迟(Low lantency),默认的暂停时间是200ms
- 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是标记+整理算法,两个区域之间是复制算法
相关JVM参数:
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=size
- -XX:MaxGCPauseMillis=time
(1) G1 垃圾回收阶段
(2) Young Collection
- 会STW
(3) Young Collection + CM
- 在Young GC时会进行GC Root的初始标记
- 老年代占用堆空间比例达到阈值,进行并发标记(不会STW),由下面的JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent (默认45%)
(4) Mixed Collection
会对E、S、O进行全面垃圾回收
- 最终标记(Remark)会STW
- 拷贝存活(Evacuation)会STW
-XX:MaxGCPauseMillis=ms
(5) Full GC
- SerialGC
- 新生代内存不足发生的垃圾收集 -minor gc
- 老年代内存不足发生的垃圾收集 -full gc
- ParallelGC
- 新生代内存不足发生的垃圾收集 -minor gc
- 老年代内存不足发生的垃圾收集 -full gc
- CMS
- 新生代内存不足发生的垃圾收集 -minor gc
- 老年代内存不足
- G1
- 新生代内存不足发生的垃圾收集 -minor gc
- 老年代内存不足
(6) Young Collection 跨代引用
- 新生代回收的跨代引用(老年代引用新生代)问题
- 卡表与 Remembered Set
- 在引用变更时通过 post-write barrier + dirty card queue
- concurrent refinement threads 更新 Remembered Set
(7) Remark
- pre-write barrier + satb_mark_queue
(8) JDK 8u20 字符串去重
- 优点:节省大量内存
- 缺点:略微多占用了CPU时间,新生代回收时间略微增加
-XX:+UseStringDeduplication
1 | String s1 = new String("hello"); // char[]{'h','e','l','l','o'} |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 怀民亦未寝。!