第一步 判断对象是否要存活
引用计数算法
首先给对象添加一个引用计数器 如果有人引用了这个对象 计数器加1 ,
当引用失效时 计数器减一;任何时刻计数器都为0的对象就是不能被使用的。
优点:实现简单 效率高
缺点:很难解决对象循环引用的问题 A引用B B的计数器加一 B也引用A A的计数器加一 AB的就都不为1 但实际上两个对象都没用
根搜索算法 JAVA C#
通过一系列名为 “GC Roots”的对象作为起点 从这些节点向下搜索,搜索走过的路径称为引用链,
当一个对象对到GC Roots没有任何引用链相连时 (GC Roots 到这个对象不可达),证明对象不可用
Java中 GC Roots对象包括
- 虚拟机栈(
本地变量表
)中的引用对象 - 方法区中
静态类属
性引用的对象 - 方法区中
常量
引用的对象 - 本地方法栈中
JNI
(Native方法)的引用对象
何为引用
如果reference类型的数据中存储的数值是另一块内存的起始地址,就称这块内存代表这一个引用。
- 强引用 类似 Object obj=new Object() 只要强引用还存在,垃圾回收永远不会回收被引用的对象。
- 软引用 还有用 并非必须 在系统将要发生内存溢出时,将会把这些对象列进回收范围并进行二次回收。
- 弱引用 非必须对象 被弱引用的对象只能生存到下一次垃圾收集发生之前。
- 虚引用 一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用来获取一个对象实例。
生存还是死亡?
真正需要回收到对象需要经过两次标记
方法区 垃圾回收性价比低 两个内容 废弃的常量,无用的类。
- 废弃的常量 没有引用。
- 无用的类 该类所有实例都被回收 加载该类的ClassLoader已经被回收 没有任何地方引用 无法通过反射访问该类方法。
大量使用反射 动态代理 CGLib 等 bytecode框架的场景 以及频繁自定义ClassLoader的场景都需要具备类卸载功能。
垃圾回收算法
标记-清除算法
标记清除法是最基础的算法 分为两个阶段
- 首先标记处所需要回收的对象
- 在完成标记后统一回收掉所有被标记的对象
缺点 1. 效率问题 标记和清除效率不高 2. 空间问题 清除后会产生大量不了连续的空间碎片。
复制算法
复制算法将可用的内存按容量分为大小相等的两块, 只使用其中一块,当使用完了以后把活着的对象复制到另外一块上,把已使用的内存空间全部清掉。
优点 实现简单 运行高效
缺点 把内存缩小为原来的一半
复制算法用来回收新生代。
新生代中的对象98%朝生夕死 所有并不需要有按1:1 比例分配空间
新生代分为Eden 空间和两块较小的Survivor空间 每次用Eden 和一个Survivor 。
回收时把Eden 与Survivor的活着的对象一次性拷贝到另一款Survivor上 最后清理掉Eden 与Survivor空间。
标记-整理算法
标记整理算法是把存活的对象都向一段移动,然后清理掉边界以外的内存。
分代收集算法
根据对象的存活周期不同将内存分为几块,一般分为新生代和老年代,这样可以根据各个代的特点采取最适当的手机算法。
新生代采取复制算法
老年代采取标记清理或标记整理算法。
垃圾回收器
收集算法是内存回收的方法论,垃圾回收器就是内存回收的具体实现。
Serial收集器 (stop the world)
Serial收集器是新生代收集器,是单线程收集器,在垃圾回收的同时会暂停掉所有工作线程。
虽然会停掉其他所有工作线程 但是有显著的有点 简单高效,目前在client模式下的虚拟机里还有应用。
ParNew收集器
ParNew收集器是serial收集器的多线程版本,除了多线程外和secrial完全一样。
是server模式下虚拟机中首选新生代收集器 因为除了serial收集器 只有它能和CMS收集器配合。
Parallel Scavenge 收集器
Parallel Scavenge 收集器是也新生代收集器,也使用复制算法,是并行多线程收集器。
Parallel Scavenge收集器目的但是达到一个可控吞吐量,代码运行时间/(gc时间+代码运行时间)。
主要是为了给虚拟机设定一个可控目标,会自适应调节吞吐量。
Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,是 单线程收集器,使用标记整理算法。
主要意义是用在Client模式下。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。
CMS收集器
CMS收集器是一种以获取最短收集停用时间为目标的收集器。基于标记清楚算法实现。
CMS收集器需要四个步骤
- 初始标记 只是标记一下CG Roots直接关联到的对象,速度快。
- 并发标记 跟搜索算法(CG Roots Tracing)确定存活和死亡的对象。
- 重新标记 修正并发标记时间内程序导致标记变动的记录,需要时间但是比重新标记快。
- 并发清除 清除标记内容。
初始标记,重新标记需要stop the world
优点:并发收集,低停顿
缺点:
- 对CPU资源相对敏感 默认启动线程数为(CPU+3)/4。在GC期间降低工作效率。
- 无法处理浮动垃圾,初始标记和重新标记后会有浮动垃圾无法回收。
- 标记清楚算法会有大量碎片空间。
G1收集器
我理解的G1收集器是分区域、分先级,在规定时间内完成优先级最高的GC。
G1收集器是基于标记-整理算法实现的,不会产生碎片空间。
G1收集器能精确控制停顿时间,规定总时间M,GC时间N,毫秒级,无停顿。
G1收集器将整个java堆分成多个大小固定的独立区域(Region),根据垃圾堆积数量,后台维护一个优先级列表,每次根据允许时间 优先回收垃圾最多的区域。
内存分配策略
java技术中自动化内存管理可以归结为自动化的解决了两个问题,
1. 给对象分配内存
2. 回收给对象分配的内存
对象优先在Eden分配
对象在新生代Eden中分配,没有足够空间时虚拟机将发起Minor GC。
Minor GC 新生代的垃圾回收动作,java对象大多生命周期短,Minor GC非常频繁,速度也比较快。
Full GC 老年代的垃圾回收,经常会伴随至少一次的MinorGC ,速度比MinorGC慢10倍以上。
大对象之间进入老年代
大对象是指需要大量连续内存空间的JAVA对象,放入老年代的目的是为了避免Eden和两个Survivor之间发生大内存拷贝。
所以要避免生命周期短的大对象,FullGC 慢。
长期存活的对象进入老年代
虚拟机采用分代收集思想,内存回收时就需要明确哪些对象放在新生代,哪些对象放在老年代。
对象年龄计数器 对象在Eden中经历Minor GC存活后转移到Survivor中,对象年龄加一,没过一次Minor GC 对象不死亡年龄加一,当年龄加到一定程度(默认15,可以设置)时移到老年代。
动态对象年龄判断
15次是默认标准,但是如果Survivor区中的对象年龄普遍偏大(相同年龄的对象大小大于Survivor空间的一半),那就都移到老年代,给Survivor区域腾地方。
空间分配担保
发生minorGC时 会检查晋升到老年代的大小平均值是否大于老年代的剩余空间,如果直接进行一次Full GC,小于就看是否允许担保失败,如果允许就进行Minor GC 否则就进行Full GC。
- Post link: http://dongkw.github.io/2020/03/18/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/
- Copyright Notice: All articles in this blog are licensed under unless stating additionally.
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues