GC和GC Tuning
作者:fcl
GC的基础知识
1.什么是垃圾
C语言申请内存:malloc申请内存, free释放内存
C++: new申请内存, delete释放内存
C/C++: 手动回收内存
Java::new 申请内存 ?(JVM自动回收内存)
自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种类型的问题:
- 忘记回收:造成内存泄漏和内存溢出等问题
- 多次回收:垃圾回收方法可能封装在if/else等判断语句中,多次回收造成将有用的数据回收掉…
没有任何引用指向的一个对象或者多个对象循环引用被称为垃圾
2.如何定位垃圾
- 引用计数(ReferenceCount)
- 根可达算法(RootSearching)
3.常见的垃圾回收算法
- 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
- 拷贝算法 (copying) - 没有碎片,浪费空间
- 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整:第一遍和标记清楚类似找出非垃圾的对象,第二遍调整指针,注意在调整指针时如果是单线程效率偏低,如果是多线程需要线程同步也会影响效率,所以说最终结论是效率偏低,但是没有产生碎片化)
- 分代收集算法() - 是将内存分为年轻代-老年代(然后配合上述三种垃圾回收算法使用)
4.JVM内存分代模型(用于分代垃圾回收算法)
-
部分垃圾回收器使用的模型
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代
-
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
- 永久代:方法区(在java虚拟中)
- 元数据 - 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存
- 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存,动态分配)
- 字符串常量 1.7 - 在永久代,1.8 - 在堆
- MethodArea逻辑概念 - 1.8之前叫永久代、1.8之后叫元数据
- 元数据区不受堆内存的管理,操作系统直接参与管理元数据区。
永久代变更为元数据区的大概原因:
1、字符串存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。 -
新生代 = Eden + 2个suvivor区
- YGC回收之后,大多数的对象会被回收,活着的进入s0
- 再次YGC,活着的对象eden + s0 <相互移动> s1
- 再次YGC,eden + s1 -> s0
- 年龄足够 -> 老年代 (15 CMS 6)分代年龄占4个Bit所以最大值为15
- s区装不下 -> 老年代(分配担保机制)
-
老年代
- 顽固分子
- 老年代满了 Full GC
-
GC Tuning 的核心思想(Generation)
- 尽量减少FGC(full gc将会把老年代and年轻代全部gc一遍,耗时巨大,造成长时间的STW)
- MinorGC = YGC
- MajorGC = FGC
-
对象分配过程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-acE0cm6H-1597474966815)(对象分配过程详解.png)] -
动态年龄:(不重要)
https://www.jianshu.com/p/989d3b06a49d -
分配担保:(不重要)
YGC期间 survivor区空间不够了 空间担保直接进入老年代
参考:https://cloud.tencent.com/developer/article/1082730
5.常见的垃圾回收器
-
垃圾回收器的发展路线,是随着内存越来越大的过程而演进
从分代算法演化到不分代算法
Serial算法 几十兆
Parallel算法 几个G
CMS 几十个G - 承上启下,开始并发回收 -
.- 三色标记 -
G1 几百个G(Region小区块)
ZGC 几个T(Region小区块) -
JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS
并发垃圾回收是因为无法忍受STW -
Serial 年轻代 串行回收
-
PS 年轻代 并行回收
-
ParNew 年轻代 配合CMS的并行回收
-
SerialOld
-
ParallelOld
-
ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题比较多会产生漏标和错标,所以现在没有一个版本默认是CMS,只能手工指定
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收(SerialOld单线程STW回收大内存的垃圾时效率低)
算法:三色标记(黑:已经标记完成,灰:标记完自身,自身属性的引用未标记,白:未标记) +
Incremental Update:当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。 -
G1(200ms - 10ms)
算法:三色标记(黑:已经标记完成,灰:标记完自身,自身属性的引用未标记,白:未标记) + SATB(起始快照算法)和CMS类似,在最后重新标记时采用STW方式,同时使用STW方式clear
G1详解:https://blog.youkuaiyun.com/coderlius/article/details/79272773 -
ZGC (10ms - 1ms) PK C++ (整合了CMS和G1的优势)
算法:(彩色指针+读屏障)ColoredPointers + LoadBarrier -
Shenandoah
算法:(彩色指针+写屏障)ColoredPointers + WriteBarrier
彩色指针详解:https://blog.youkuaiyun.com/xiaolyuh123/article/details/103937164
读写屏障详解:https://blog.youkuaiyun.com/kuaipao19950507/article/details/106821010 -
Eplison
-
PS 和 PN区别的延伸阅读:
▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73 -
垃圾收集器跟内存大小的关系
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 20G
- G1 - 上百G
- ZGC - 4T - 16T(JDK13)
1.8默认的垃圾回收:PS + ParallelOld
常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
- 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-
-XX:+UseParNewGC = ParNew + SerialOld
- 这个组合已经很少用(在某些版本中已经废弃)
- https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
-
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
-
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-
-XX:+UseG1GC = G1
-
Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC
- java +XX:+PrintCommandLineFlags -version
- 通过GC的日志来分辨
-
Linux下1.8版本默认的垃圾回收器到底是什么?
- 1.8.0_181 默认(看不出来)Copy MarkCompact
- 1.8.0_222 默认 PS + PO
JVM调优第一步,了解JVM常用命令行参数
-
JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-
HotSpot参数分类
标准: - 开头,所有的HotSpot都支持
非标准:-X 开头,特定版本HotSpot支持特定命令
不稳定:-XX 开头,下个版本可能取消
java -version
java -X
java -XX:+PrintFlagsWithComments //只有debug版本能用