JVM--垃圾收集算法
1、为什么了解GC(垃圾收集)和内存分配
需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,需要对这些”自动化”的技术实施必要的监控和调节。
2、内存回收区域
程序计数器,虚拟机栈、本地方法栈这3个区域是线程私有的,方法结束或者线程结束时,内存跟着回收。栈中的栈帧随着随着方法的而进入与退出执行着入栈和出栈的操作,栈帧分配多少内存在类结构确定下来就是已知的。
JAVA堆和方法区,内存分配是动态的,只有在程序处于运行期间才知道会创建那些对象。
方法区回收
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
无用的类判断条件:
1、类的所有实例都已被回收,Java堆中不存在任何实例。
2、加载该类的ClassLoader已经被回收。
3、该类的Class对象没有在任何地方被引用,无法在任何地方通过反射引用该类。
3、对象是否死亡
3.1、引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加1;引用失效时,计数器值就减1。计数器的为0的对象就不可能再被使用。
缺点:存在对象之间相互循环引用的问题,虚拟机并不采用引用计数算法来判断对象是否存活。
3.2、可达性分析算法
可达性分析算法,判断对象是否存活。
基本思想:”GC Roots”对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为”引用链”,当一个的对象到GC Roots没有任何引用链相连时,证明此对象是不可用的。
JAVA中可视为GC Roots的对象有以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈JNI引用的对象。
4、四大引用类型
4.1、强引用
new对象时的引用就是强引用,只要引用还在,垃圾收集器就不会回收掉被引用的对象。
4.2、软引用
描述一些有用但非必须的对象。
发生内存溢出之前,会把这些对象列进回收范围之中进行第二次回收。
4.3、弱引用
描述非必须对象。
强度比软引用要弱一些,被弱引用关联的对象只能生存到下一次垃圾回收之前。
4.4、虚引用
最弱的一种引用关系。
目的:在这个对象被收集器回收时收到一个系统通知。
5、垃圾收集算法
5.1、标记-清除算法
算法分为两个阶段“标记”、“清除”。
标记:标记出所有需要回收的对象。清除:在标记完成后同意回收所有被标记的对象。
缺陷:
1、效率问题
标记和清除两个过程效率并不高。
2、空间问题
标记清除之后会产生大量不连续的内存碎片。空间碎片太多可能会导致,在分配较大对象时,无法找到足够的连续内存而提前触发垃圾收集动作。
5.2、标记-整理算法(老年代算法)
标记整理算法,标记与标记清除算法一样,整理则是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
5.3、复制算法
复制算法为了解决效率问题,将可用内存划分为大小相等的两块,每次只使用其中一块。当其中一块的内存用完了,就将还存活着的对象复制到另一块上面,然后已经使用过的内存空间一次清理掉。
这种算法的代价是将内存缩小为原来的一半。
采用复制算法回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,Eden和Survivor的大小比例是8:1,90%的空间作为新生代的容量,10%会浪费。当Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。
分配担保机制作用:如果另一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。
5.4、分代收集算法
当前虚拟机垃圾收集都采用“分代收集”。
一般Java堆分为新生代和老年代。
新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,采用复制算法。付出少量存活对象的复制成本就可完成收集。
老年代中,对象存活率高、没有额外空间对它进行分配担保,必须使用“标记-清理”或者“标记-整理”算法来回收。
6、HotSpot的算法实现
上面描述了对象存活判定算法和垃圾收集算法。
6.1、枚举根节点
GC Roots节点找引用链的操作。可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(栈帧中的本地变量表)。
可达性分析需耗时的体现:
1、方法区比较大,逐个检查引用比较耗时。
2、GC停顿,因为可达性分析工作必须在一个能确保一致性的快照中进行。
“一致性”:在整个分析期间整个执行系统看起来就像被冻结在某一个时间点上,不可以出现分析过程中对象引用关系还在不断的变化的情况,该点不满足的话分析结果准确性就无法得到保证。
可达性分析的准确性是导致GC进行是必须停顿所有Java执行线程(Stop the World)的重要原因。
在类加载完成后,HotSpot把对象的引用记录在OopMap的数据结构中,在GC开始,执行系统停顿下来,不需要一个不漏地检查完所有执行上下文和全局的引用位置,只需在特定位置查找即可。
6.2、安全点(Safepoint)
问题一:
在OopMap的协助下,HotSpot可以很快的完成枚举。但是OopMap的内容变化的指令非常多,若为每一条指令都生成对应的OopMap,将会需要大量的额外空间,GC空间的成本将会很高。
解决方案:
HotSpot只是在“特定位置”记录这些信息,这些位置成为安全点。程序执行时并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。
问题二:
如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。
解决方案:
方案一:(几乎不用)
抢先式中断:把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。
方案二:
主动式中断:当GC需要中断线程的时候,不直接对线程操作,仅仅简单设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己挂起。轮询标志的地方和安全点是重合的。
6.3、安全区域(Safe Region)
Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。
当程序“不执行”时,不使用SafePoint机制。
程序不执行:没有分配CPU时间,典型的例子是线程处于Sleep状态或者Blocked状态,此时线程无法响应JVM的中断请求,“走”安全的地方去中断挂起,JVM不太可能等待线程重新被CPU分配时间,这种情况下,需要用安全区域来解决。
安全区域是指一段代码片段中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)