什么是垃圾
ReferenceCounter 引用计数器
用来识别什么是垃圾的一种算法,对象被引用了说明就是有用的对象,如果对象没有被引用说明就是垃圾,但是有一种情况RC这种算法是不能正确识别对象是否是垃圾的,就是循环引用
如上图所示,几个对象循环引用,RC会有计数,但是这三个作为一个整体其实全部是垃圾。
GC Roots 根可达算法
一般而言,GC Roots 包括(但不限于)如下几种:
- Java 方法栈桢中的局部变量;
- 已经加载的静态变量;
- JNI Handles;
- 已经启动未停止的线程;
从根部一直开始寻找,所到之处全部是有用的,到不了的地方全是垃圾。
垃圾回收的三种方式
标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
copy复制 - 没有碎片,浪费空间,指针需要调整
标记整理(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)
JAVA内存分代模型
部分垃圾回收器使用的模型
- Epsilon ZGC Shenandoah既不是逻辑分代也不是物理分代
- G1逻辑分代,不是物理分代
- Young:Serial,ParallelScavenge,ParNew Old:SerialOld,ParallelOld,CMS以上垃圾回收器即是逻辑分代又是物理分代
新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace
- 永久代 元数据 - Class
- 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
- 字符串常量 1.7 - 永久代,1.8 - 堆
- MethodArea逻辑概念 - 永久代、元数据
新生代 = Eden + 2个survivor区
- YGC回收之后,大多数的对象会被回收,活着的进入s0
- 再次YGC,活着的对象eden + s0 -> s1
- 再次YGC,eden + s1 -> s0
- 年龄足够 -> 老年代 (一般15 CMS 6 注意最大15)
- s区装不下 -> 老年代
老年代
- 顽固分子
- 老年代满了FGC Full GC
GC Tuning (Generation)
- 尽量减少FGC
- MinorGC = YGC
- MajorGC = FGC
对象的动态分配过程
TLAB:JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。
动态年龄
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
分配担保
YGC期间 survivor区空间不够了 空间担保直接进入老年代
常见的垃圾回收器
- 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 进行老年代回收 想象一下: PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW) 几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC 算法:三色标记 + Incremental Update
- G1(10ms) 算法:三色标记 + SATB
- ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
- Shenandoah 算法:ColoredPointers + WriteBarrier
- Eplison
- 1.8默认的垃圾回收:PS + ParallelOld
常见垃圾回收器组合参数设定:(1.8)
-
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old
-
小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-
-XX:+UseParNewGC = ParNew + SerialOld 这个组合已经很少用(在某些版本中已经废弃)
-
-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
-
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
-
-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