1.什么是垃圾
没有任何引用指向的一个对象或者多个对象(循环引用)
2.如何定位垃圾
-
引用计数(ReferenceCount)
就是对每个对象都追踪指向它们的引用数,如果引用数为0,就说明这个对象是内存垃圾了。但是这个方法存在缺陷,如果多个对象之间存在循环引用,但是这些对象没有被外部引用,这些对象实质上也是垃圾,但是没有办法被标记
-
根可达算法(RootSearching),jvm采用的是这种
指从根对象开始扫描,所有可以被扫到的对象都被标记为存活对象,而扫不到的对象就是垃圾对象。对扫到的对象一般是在该对象的header中打上一个标识,表示当前对象还是存活对象。
那么什么是根对象呢?主要由下面四种对象构成:
- main方法的栈帧中的对象
- class文件中的静态变量
- 常量池中的对象
- JNI用到的对象
3.常见的垃圾回收算法
-
标记清除(mark sweep)
- 执行流程:标记阶段,从根对象开始进行遍历,对根对象可以访问到的对象都打上一个标识,一般在对象头中,将其记录为可达对象;在清除阶段对堆内存从头到尾进行遍历,如果发现某个对象没有标记为可达对象(通过读取对象头信息)则就将其回收
- 优点:算法简单,存活对象比较多的时候效率较高,适合old区
- 缺点:位置不连续容易产生碎片,两遍扫描效率偏低
-
拷贝算法 (copying)
- 执行流程:从根对象开始进行遍历,对根对象可以访问到对象移动到一个专门的内存区域(java中叫s0、s1),然后回收这块内存区域之外的其他内存区域
- 优点:没有碎片,只扫描一次效率提高,适合存活对象较少的情况,比如年轻代
- 缺点:浪费空间,移动复制对象需要调整对象引用
-
标记-整理(mark compact)
-
执行流程:标记阶段,从根对象开始进行遍历,对根对象可以访问到的对象都打上一个标识,一般在对象头中,将其记录为可达对象;整理阶段,遍历整个堆,将存活对象全部向一端移动,然后直接清理掉边界以外的内存
-
优点:不会产生碎片方便对象分配,不会存在内存减半问题,适合old区
-
缺点:扫描2次,需要移动对象,效率偏低
-
4.JVM内存分代模型
- 内存分代模型
- GC概念
MinorGC/YGC:年轻代空间耗尽时触发
MajorGC/FGC:在老年代无法继续分配空间时触发,新生代老年代同时进行回收
-
部分垃圾回收器使用的模型
除Epsilon、ZGC、Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外其他垃圾回收器不仅逻辑分代,而且物理分代
-
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
永久代 元数据 - Class
永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
字符串常量 1.7 - 永久代,1.8 - 堆
MethodArea逻辑概念 - 永久代、元数据具体实现
-
新生代 = Eden + 2个suvivor区
YGC回收之后,大多数的对象会被回收,活着的进入s0
再次YGC,活着的对象eden + s0 -> s1
再次YGC,eden + s1 -> s0
年龄足够 -> 老年代 (年龄限制XX:MaxTenuringThreshold :其他垃圾回收器15,CMS 6)
s区装不下 -> 老年代
-
老年代
顽固分子
老年代满了FGC Full GC
-
GC Tuning (Generation)
尽量减少FGC
MinorGC = YGC
MajorGC = FGC
-
栈上分配
- 什么是栈上分配:对象在栈上分配而不是堆
- 栈生分配优点:不需要GC介入去回收这个对象,出栈即释放资源,可以提高性能
- 哪些对象可以在栈上分配:线程私有小对象、无逃逸、支持标量替换
- 如何开启栈上分配:jvm默认支持,可以通过 -XX:-DoEscapeAnalysis关闭逃逸分析, -XX:-EliminateAllocations关闭标量替换,从而关闭栈上分配,一般情况下无需调整
-
线程本地分配TLAB(Thread Local Allocation Buffer)
- 什么是本地分配:JVM为每一个线程在初始化时先单独分配一块Buffer,该空间是线程私有的,当线程执行时需要为对象分配内存时,就是用自己的Buffer空间分配,这样就解决了多线程因共用同一JVM堆空间而产生的同步操作冲突问题。TLAB使用的堆内存空间其实还是eden区,默认1%
- 本地分配优点:多线程的时候不用竞争eden就可以申请内存,提高效率
- 如何开启本地分配:默认开启,可以通过-XX:-UseTLAB来关闭TLAB,一般情况下无需调整
-
动态年龄
-
年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio的时候,就从这个年龄段往上的年龄的对象进行晋升。比如年龄1的占用了33%,年龄2的占用了33%,累加和超过默认的TargetSurvivorRatio(50%),年龄2和年龄3的对象都要晋升。
-
-XX:TargetSurvivorRatio这个参数来控制,默认为50%
-
-
分配担保
- YGC期间 survivor区空间不够了,空间担保直接进入老年代(当在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。)
- https://cloud.tencent.com/developer/article/1082730
-
对象何时进入老年代
-
超过-XX:MaxTenuringThreshold指定次数(YGC),Parallel Scavenge 15,CMS 6,G1 15
-
动态年龄 s1->s2超过50%
-
-
对象分配过程图
new一个对象,先进行线程栈上分配判断,如果能进行则进入栈上分配,然后结束
如果无法进行栈上分配则判断是否是大对象,如果是大对象则直接进入老年代分配,然后结束
如果不是大对象则进行TLAB判断,不过是否TLAB都是在eden区进行分配
然后执行gc,判断对象是否存活,没有存活则结束;如果对象存活则进入s1;再次执行gc再次判断对象是否存活,如果存活判断年龄是否大于指定参数,如果大于指定配置则进入老年代(cms 6,其他 15);如果年龄小于指定参数则进入s2;然后反复此步骤直接到结束
5. 垃圾回收器类型
红色虚线表示常见组合
-
JDK诞生 Serial追随;为了提高效率诞生了PS;为了配合CMS诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS;并发垃圾回收是因为无法忍受STW
-
Serial,用于年轻代,会有STW,采用复制算法,使用单线程进行GC也就是串行回收(黄色箭头表示GC)
-
Parallel Scavenge(PS),用于年轻代,会有STW,采用复制算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC)
-
ParNew ,用于年轻代,会有STW,采用复制算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC),与PS不同的是有增强功能配合CMS,例如它可以在CMS的并发阶段运行ParNew执行所需的同步
- SerialOld ,用于老年代,会有STW,采用标记-清除-压缩(Mark-Sweep-Compat,多次GC后才压缩是标记清除和标记压缩的结合。但不算在GC四大算法中),使用单线程进行GC也就是串行回收(黄色箭头表示GC)
- ParallelOld,用于老年代,会有STW,采用标记整理算法,使用多线程进行GC也就是并行回收(黄色箭头表示GC)
-
ConcurrentMarkSwee,用于老年代,并发执行,垃圾回收和应用程序同时运行,降低STW的时间(200ms)。CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定。CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收。算法采用三色标记 + Incremental Update
-
G1,设计目标在10ms内,算法采用三色标记 + SATB
-
ZGC ,设计目标在1ms内,算法采用ColoredPointers + LoadBarrier
-
Shenandoah,算法ColoredPointers + WriteBarrie
-
Eplison,debug用的,不会做什么事的GC
-
PS 和 PN区别:https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html
-
垃圾收集器跟内存大小的关系:
Serial 几十兆
PS 上百兆 - 几个G
CMS - 20G
G1 - 上百G
ZGC - 4T - 16T(JDK13)
-
1.8默认的垃圾回收:PS + ParallelOld
5. 常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
适用于小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-
-XX:+UseParNewGC = ParNew + SerialOld
这个组合已经很少用(在某些版本中已经废弃)
https://*.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