• JVM的GC初步理解


    那些对象需要回收?

    什么时候回收?

    如何回收?

    程序计数器、本地方法栈、虚拟机栈是线程隔离的,随线程的消亡而销毁

    GC的范围 :堆、方法区。

    一、回收之前:如何判断对象可以回收?

    1、算法:引用计数算法

    引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

    优缺点

    优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

    缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。

    2、算法:可达性分析算法

    可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,搜索的路径称为引用链,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

    当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

    即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

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

      a) 虚拟机栈中引用的对象(栈帧中的本地变量表);

      b) 方法区中类静态属性引用的对象;

      c) 方法区中常量引用的对象;

      d) 本地方法栈中JNI(Native方法)引用的对象。

    二、如何回收?

    1、最基本的算法:标记-清除算法(Mark-Swap)

    标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

    标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

    存在问题:1、效率低 2、造成大量内存碎片

    2、复制算法(Copying)

    为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

    存在问题:将内存缩小了一部分

    现在解决方式:目前商业虚拟机的新生代是使用的复制算分,新生代98%的对象存活时间极短

    3、标记-整理算法

    复制算法在对象存活率高的时候,效率就很低,如果不想浪费50%的空间,就需要有额外额空间做担保。老年代一般不能使用这种算法。

    根据老年代的特点,提出了“标记-整理算法”,过程与“标记-清理算法一样”,不过不是直接对对象进行清理,而是让所有存活的对象想一边移动,然后直接清理掉边界以外的对象

    4、分代收集算法

    根据对象存活周期不同,将内存分成几个区域,一般分为新生代和老年代。新生代对象存活周期较低,使用“复制算法”,将存活对象复制到老年代,老年代使用“标记-清理算法”或“标记-整理算法”

    三、内存分配与回收策略 

    对象在堆上分配空间,主要在新生代分配,如果使用了本地线程缓冲,则在TLAB份分配。也有在老年代分配

    1、对象优先在eden分配

    对象主要在eden分配,如果空间不足的话。jvm将发起一次Minor GC

    2、大对象直接进入老年代

    所谓大对象,就是需要大量连续内存空间的对象,典型的就是一串很长的字符串或数组

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

    jvm为每个对象定义一个Age计数器,如果在eden出生,并经过一次MinorGC仍然存活,并且可以背Survivor接纳的话,将被移入Survivor,并且年龄设置为1,在Survivor没“熬过”一次MinorGC年龄加一,再经过15次(默认15次)MinorGC仍然存活,将被移入老年代

    4、动态对象年龄判定

    为了适应不同的年龄情况,jvm并不一定等到15次MinorGC,当Survivor内存空间中相同年龄对象综合大于Survivor空间的一般,则将大于或等于该年龄的对象全部移入老年代

    5、空间分配担保

    在发生MinorGC之前,如果老年代的连续空间大于新生代的所以对象总和,则说明此次MinorGC是安全的。不成立,则看条件HandlePromotionFailure是否允许担保失败,如果允许,则继续检查此次晋升老年代的对象大小是否大于老年代连续空间大小,如果大于,则尝试进行一次晋升,如果失败或HandlePromotionFailure为不允许,则进行一次FullGC.

    担保:MinorGC空间不足的对象需要移入老年代,因此老年代需要担保存储这些对象的空间是足够的

  • 相关阅读:
    MySQL按照汉字的拼音排序
    js prepend() 和append()区别
    php获取当月天数及当月第一天及最后一天、上月第一天及最后一天实现方法
    (转)对《30个提高Web程序执行效率的好经验》的理解
    打印数组
    php创建文件并写入信息
    关于iOS地图定位中点击设置->隐私->定位服务 闪退问题
    解决WAMP搭建PHP环境后后局域网其他机器无法访问的问题
    用php怎么改文件名
    JSP HTTP 状态码
  • 原文地址:https://www.cnblogs.com/mlfz/p/11778612.html
Copyright © 2020-2023  润新知