JVM(三):GC回收

GC设计出发点:

  • 那些内存需要回收?
  • 什么时候需要回收?
  • 如何回收?

应用:

目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,那为什么我们还要去了解GC和内存分配呢?答案很简单:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节

程序基数器、虚拟机栈、本地方法栈、随着线程的生命周期,分配和xiao销毁确定。但是heap堆的很多内存运行时才能确定,所以本节聚焦于heap堆


##对象是否有效
####引用计数法 Reference Counting

描述:为对象添加一个引用计数器,引用就+1,引用失效-1,GC定期清理为0的对象。
优点: 简单易实现
缺陷:无法解决两个对象互相引用
应用: 主流java虚机没有使用应用计数算法来管理内存,python和游戏脚本领域被广泛用来做内存管理。
####可达性分析 reachability analysis
描述:GC Roots,当一个对象到GC Roots没有任何引用链(reference chain)相连,证明此对象不可用
范围: java中可以作为GCroots的对象包括这几种 虚拟机栈(栈桢中的本地变量表)中引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象本地方法栈中JNI(即一般说的Native方法)引用的对象
应用:主流
####如何记录引用
|引用类型||||
|:—😐:—😐:—😐:—😐
|强引用|软引用|弱引用|虚引用|
|strong reference|soft reference|weak reference|phantom reference|
|Object obj =new Object()|有用分非必需,内存紧张时会回收|生存到下次GC收集前|被回收时会有个系统通知|
####回收过程
|回收过程||
|:—😐:—😐
|GCroots不可达|被第一次标记并筛选一次|
|是否要执行的finalize()方法|有待执行的finalize(),被放入F-Queue,jvm会起一个低优先线程自动执行:即JVM会触发该方法,但不承诺等待它结束。除非finalize()中被使用(如把this指针给其他对象),可脱离被销毁|
|||
|回收方法区|hotspot的永久代|
|回收对象|废弃常量无用的类,String常量,等|
|废弃常量|判断是否无用和堆常量的GC机制类似|
|废弃类|条件实例均已被回收加载该类的ClassLoadera被回收对应的java.lang.Class对象没有在任何地方被引用
|使用|在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出 |


##垃圾回收算法

标记清除 Mark-Sweep回收过程中的标记过程+清除
不足:标记和清除效率都不高、标记清除后会产生大量不连续的内存碎片
标记清除
复制copying将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,太高了
复制
应用主流的都采用这种回收新生代。将内存分为一块较大的Eden和两块较小的Survivor,默认比例E:S=8:1
描述当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费"。我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)
标记-整理 Mark-Compact标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
标记-整理
分代收集 Generational Collection根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代
新生代,大批对象死去,选用复制算法
老年代,对象存活率高使用标记-清理或者标记-整理

##HotSpot的算法实现

  • 枚举根节点
    GC停顿 用来枚举根节点,确保收集到某一刻的系统快照,而不是正在变化着的,所以必须暂停下所有java线程
    OopMap Hotpot记录对象引用的数组,GC领顿时能快速找到所有对象信息。
  • 安全点 safepoint

程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。Safepoint的选定既不能太少以致于让GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷。所以,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定。例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。

两种方案让GC发生时,所有线程都到最近的安全点停下来

抢占式中断GC时,暂停所有线程,让非安全点线程继续,直到跑到安全点,但是没有虚拟机采用这种方式
主动式中断当GC需要中断线程时,设立一个flag,线程自己去轮询flag,然后中断自己到安全点
  • 安全区域 safe region

sleep或block线程(挂起,没有被分配cpu)无法响应GC,不能进入到Safepoint。就需要安全区域
描述
安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。
行为
在线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。


##垃圾收集器
算法是内存回收的方法论,收集器是具体实现。
7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用
收集器们

备注:
●并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
●并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
GCTimeRatio GC时间停顿

serial收集器单线程《深入理解Java虚拟机》
ParNew收集器多线程版serial*
Parallel Scavenge收集器新生代*
Serial Old收集器老年代*
CMS收集器Concurrent Mark Sweep 以最短停顿为目标*
G1收集器与CMS比更先进,但目前差距不大*

理解GC日志

见《深入理解Java虚拟机》


##内存分配与回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存

对象优先在Eden分配XX:+PrintGCDetails GC是打印日志Eden空间不够时,JVM发起Minor GC1
大对象直接进入老年代很长的字符串和数组避免私用一堆"朝生夕死"的大对象
长期存活的对象进入老年代记"岁",minor存活一次则长一岁
动态对象年龄判定年老是相对的,空闲时相对老的进入老年代
空间分配担保minor GC之前会检查 老年代最大连续可用空间是否大于新生代所有对象的空间,成立说明MinorGC安全,否则将尝试一次有风险的MinorGC,如果不允许冒险,讲改为进行一次Full GC
冒险指的是,GC发现老年代给survivor预留的空间不够,需要再进行Full GC,让老年代腾出更多空间,FullGC时间长

后面作为个人理解后笔记性质的内容,详细参考《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值