当一个对象没有任何引用指向的时候被称为垃圾,如图下面那个没有线的方框就是垃圾了


标记内存空间被多少个对象引用

变成0的时候表示这个对象是个垃圾

但是不能解决循环引用问题:
对象之间相互引用,引用计数都是1

由上图的引用计数法不能解决循环引用问题,从而推出根可达算法:

哪些对象属于是根对象呢:
1、JVM stack
2、native method
3、stack
4、run-time constant pool
5 、static references in method area【方法区的静态引用】
6、Clazz
总而言之就是程序启动起来马上就需要拿到的对象是根对象。

找到没用的对象给它标记起来,然后清除,但是会有一些小问题:

黄色的部分是可回收的,回收后的状态为:
用蓝色标记的。

这种算法相对比较简单,存活对象较多的情况下效率较高。
当然需要两遍扫描【第一遍扫描找出哪些是有用的,第二遍扫描找出没用的进行清理】:
它的执行效率比较低,容易产生较多内存碎片
将内存一分为2,把有用的拷贝到一边,拷贝完成之后上面的全部都清除掉,得到回收后的结果,没有产生碎片

问题:

适用于存活对象较少的情况,只扫描一次执行效率高,没有碎片。
但是会造成空间浪费,且引用也要跟着对象的调整而调整。


通过GC root找到不可回收的,然后将不可回收的对象给往前挪,需要扫描两次,需要移动对象,并且
执行效率偏低。
优点是不会产生内存碎片,方便对象分配,不会产生内存减半

eden区new出来的对象先进入eden区里面,然后survivor是代表经历过垃圾回收之后进去
对象从出生到消亡的过程:

一个对象新建的时候会尝试在栈上分配,如果栈上放不下会进入eden区里面去,如果经历一次垃圾回收
之后进入S1里面,再经历一次垃圾回收之后进入S2里面,再经历垃圾回收又回到S1里面去,当年龄够了
以后则进入Old区里面去。

-Xms 初始化java堆的大小
-Xmx 最大的堆的大小
-Xss java栈的大小
凡是在年轻代空间耗尽触发的叫:MinorGC/YGC
在老年代无法继续分配空间时触发,新生代老年代同时进行回收:MajorGC/FullGC
栈上分配
java对标C语言的struct,设置对象在栈上分配
不会被其他线程所共享
无逃逸:是指就在我某一段代码使用
标量替换:用普通的属性来代替整个对象
栈上分配不下,会优先执行线程本地分配:
每个线程从eden区里面取1%的空间,每个线程独有的,就先往这个独有的空间分配,这样就不会和其他
线程产生争用,所以效率就会变高。【调优一般不会调】

代码验证:
-EliminateAllocations:去掉标量替换
-XX:-DoEscapeAnalysis:去掉逃逸分析
-XX:-UseTLAB:去掉栈上分配
栈上分配要比堆上分配快很多:栈一弹出就没了,不需要垃圾回收,也不需要和其他线程去争用。
//-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
// 逃逸分析 标量替换 线程专有对象分配
public class TestTLAB {
//User u; 这样就会有逃逸
class User {
int id;
String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
void alloc(int i) {
//无逃逸
new User(i, "name " + i);
}
public static void main(String[] args) {
TestTLAB t = new TestTLAB();
long start = System.currentTimeMillis();
for(int i=0; i<1000_0000; i++) t.alloc(i);
long end = System.currentTimeMillis();
System.out.println(end - start);
//for(;;);
}
}
除了CMS其他都是15

动态年龄:
当垃圾回收的两个S之间,拷贝来拷贝去超过50%的时候会把年龄最大的给放入老年代,不一定非得到
15或者6岁。

如果s1里面的对象拷贝到了s2里面,超过50%的话,s1里面再加上eden区经过一次垃圾回收,拷贝
到s2里面,这个时候对象超过s2的一半了,则进入到老年代。
动态年龄参考:
https://www.jianshu.com/p/989d3b06a49d
分配担保:
在YGC期间survivor区空间不够了-通过空间担保直接进入老年代。
参考:https://cloud.tencent.com/developer/article/1082730

首先new对象会尝试在栈上面放置,如果栈上面放不下,且这个对象足够大的话则进入Old区,否则
调用线程本地分配,如果能分下,放入线程本地内存空间,如果放不下进入eden区,然后回收调用
GC清除算法,决定是进入S1或者S2或者进入老年代。
除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
G1是逻辑分代,物理不分代
除此之外不仅逻辑分代,而且物理分代

常见组合为以下三种:
1、Serial和Serial Old
2、Parallel Scavenge 和 Parallel Old【1.8默认使用垃圾回收器】
3、ParNew和CMS
分类:
JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS
并发垃圾回收是因为无法忍受STW,内存空间太大,清理耗时高,10G PS+PO 大概需要十几秒
Serial 年轻代 串行回收:当我工作的时候其他线程全部停掉【STW】
safe point :线程停止的安全点,

PS 年轻代 并行回收
目前很多生产环境在用,默认的PS+PO,清理线程是多个的,然后一起清理垃圾。

ParNew【parallel new】 年轻代 配合CMS的并行回收
是Parallel Scavenge的一个变种,是和CMS配合使用的,CMS的某个阶段的时候ParNew会配合运行

SerialOld
在老年代清除算法用的是单线程的

ParallelOld
整理算法采用多线程执行的

CMS【老年代垃圾回收器】 简称:ConcurrentMarkSweep 作用在老年代的并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
1、首先初始标记(STW)
2、然后并发标记哪些能清理的对象,和我们应用程序同时运行
3、重新标记,又是一个STW,对上一步并发标记新产生的垃圾进行标记
4、执行并发清理的过程,会产生浮动垃圾,等待下一次CMS运行时清理

图解流程:

找到根对象初始标记:

执行并发标记:

重新标记【工作线程产生的垃圾】:

最后再清理垃圾:

CMS产生的问题:
1、内存的碎片化
CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收【让它一个线程在里面做标记压缩】。
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩
想象一下:
PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)
几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
2、产生浮动垃圾
Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
解决方案:降低触发CMS的阈值
PromotionFailed
解决方案类似,保持老年代有足够的空间
–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间
ConCurrentMark阶段算法
CMS算法【在concurrent mark阶段】 :三色标记 + Incremental Update

优化问题:
有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G
的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G
的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了
1. 为什么原网站慢?
很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢
2. 为什么会更卡顿?
内存越大,FGC时间越长
3. 咋办?
PS -> PN + CMS 或者 G1
G1(10ms)
算法:三色标记 + SATB
ZGC (1ms) PK C++
算法:ColoredPointers + LoadBarrier
Shenandoah
算法:ColoredPointers + WriteBarrier
Eplison
PS和PN的延申阅读:
https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html
垃圾收集器与内存大小的关系
1、Serial 几十兆
2、PS 上百兆—几个G
3、CMS几个G-20G左右
4、G1 - 上百G
5、ZGC - 4T最大