美文网首页
JVM系列:垃圾收集器与内存分配策略

JVM系列:垃圾收集器与内存分配策略

作者: 内卷星球 | 来源:发表于2018-03-20 13:14 被阅读0次

    判断对象是否存活

    引用计数算法

    实现简单,判定效率高

    Java虚拟机没有使用,主要原因是此算法很难解决对象之间相互循环引用的问题

    可达性分析算法

    通过一系列的称为"GC Roots"的对象作为起始点,从这些节点向下搜索,搜索走过的路径称为引用链;当一个对象跟"GC Roots"没有任何引用链的关系,则证明此对象不可达。

    在进行可达性分析后发现此对象没有与"GC Roots"相连的引用链,则会被第一次标记并且进行一次筛选,条件是此对象是否有必要执行finalize()方法。
    当对象没有覆盖finalize()方法,或者说finalize()方法已被虚拟机调用过,则没有必要执行
    如果该对象判定有必要执行,则会放到F-Quene队列,并在稍后由一个虚拟机自动建立、低优先级的Finalize线程去执行(会触发,但并不一定会等这个方法执行结束,以避免该方法执行缓慢或者死循环)
    finalize方法是对象逃脱死亡的最后一次机会,GC会对F-Quene中的对象进行第二次小规模标记,只有重新与引用链上的任何一个对象建立关联, 那么第二次标记,将会被移出"即将回收"集合,否则,就进行回收。
    一个对象的finalize方法只会被虚拟机调用一次

    在Java中,可作为"GC Roots"的对象包括下面几种:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(一般说的是Native方法)引用的对象

    引用又分为:强、软、弱、虚

    回收方法区

    永久代的垃圾收集主要是:废弃常量和无用的类

    无用的类:

    • 该类所有的实例都已经被回收
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
      满足以上3个条件,可以进行回收,而不是必然回收。

    垃圾收集算法

    标记-清除---->老年代

    效率不高,产生大量不连续内存碎片

    复制---->新生代

    实现简单,运行高效,但是将内存缩小了一半

    将可用内存划分为(A,B)2块,每次只用A块,将A块存活对象复制到B块,然后将A块一次清理,每次都对半区进行回收

    标记-整理---->老年代

    垃圾收集器

    Serial收集器

    单线程:进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。
    在Client模式下,简单高效,没有线程交互的开销

    ParNew收集器

    Serial收集器的多线程版本

    Parallel Scavenge收集器

    使用复制算法,达到可控制的吞吐量

    Serial Old收集器

    使用标记-整理算法

    Parallel Old收集器

    使用标记整理算法

    CMS收集器

    以获取最短回收停顿时间为目标的收集器

    步骤:

    • 初始标记(stop the world)
    • 并发标记(stop the world)
    • 重新标记
    • 并发清除
      缺点:
    • 对CPU资源非常敏感
    • 无法处理浮动垃圾,可能导致Full GC产生
    • 标记-清除算法,会导致大量内存碎片,从而引起Full GC

    G1收集器

    面向服务器应用的垃圾收集器

    特点:

    • 并行与并发
    • 分代收集
    • 空间整合(标记-整理算法)
    • 可预测的停顿:明确指定在一个长度为M毫秒的时间片段里,消耗在垃圾收集上的时间不得超过N毫秒

    将Java堆划分为多个大小相等的独立区域Region,G1跟踪各个Region里面的价值大小(回收获得的空间大小以及需要花费的时间的经验值),在后台维护一个优先列表,每一次根据允许的收集时间(可预测的停顿)优先回收价值最大的Region(每个Region都有Remembered Set 避免全堆扫描)

    步骤:

    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收

    内存分配与回收策略

    内存分配的2种方式:

    选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

    • 指针碰撞
      Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离
    • 空闲列表
      Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录

    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值

    对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下分配到老年代

    对象优先在Eden区分配

    当Eden区没有足够的空间分配时,虚拟机将会发起一次Minor GC(新生代GC,速度较快)

    大对象直接进入老年代

    长期存活对象将进入老年代

    年龄计数器:在Survivor没熬过一次Minor GC,年龄加1,当达到阈值,则进入老年代;阈值设置(-XX:MaxTenuringThreshold)

    动态对象年龄判定

    如果Survivor 中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象都直接进入老年代

    空间分配担保

    在Minor GC,虚拟机会检查老年代中最大可用的连续空间是否大于新生代所有对象总空间。如果成立,则Minor GC安全;不成立,查看HandlePromotionFailure是否允许担保失败;如果允许,则检查老年代可用连续空间是否大于之前每次晋升老年代的平均值大小,如果大于,则冒险进行Minor GC,如果小于或者设置不允许担保失败的话,则进行Full GC。

    相关文章

      网友评论

          本文标题:JVM系列:垃圾收集器与内存分配策略

          本文链接:https://www.haomeiwen.com/subject/rsavqftx.html