• Java GC 垃圾回收算法 内存分配


    垃圾回收(Garbage Collection, GC)是Java不同于c与c++的重要特性之一。

    他帮助Java自动清空堆中不再使用的对象。

    由于不需要手动释放内存,程序员在编程中也可以减少犯错的机会。

    利用垃圾回收,程序员可以避免一些指针和内存泄露相关的bug(这一类bug通常很隐蔽)。

    垃圾回收实际上是将原本属于程序员的责任转移给计算机。

     

    GC需要完成的3件事情:

      哪些内存需要回收

      什么时候回收

      如何回收

    1 回收那些对象?

     

       在Java中采用可达性分析算法来判定对象是否存活,是否可以被回收。

       这个算法通过一系列的被称为”GC Root”的对象作为根节点,

       从他们开始向下搜索,搜索走过的路径被称为引用链(Reference Chain).

       当一个对象没有一条引用链与GC Root 连接时,

       即从GC Root 到这个对象是不可达的,说明这个对象是不可用的。

       如图示:

     

    object5 object6 object7 虽然互联互通 

    但是他们到GC Root是不可达的 

    所以他们将被判定为可以回收的对象

     

    那么有一个重要的问题是 what is GC Root?

    在Java语言中,可以看做是GC Root的是:

      虚拟机栈中引用的变量 (可理解为方法中的局部变量)

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

      方法区中的常量引用的对象

      本地方法栈中的JNI(native方法)引用的对象

     

    2 垃圾回收算法

     

     2.1 标记-清除算法

          

      顾名思义,该方法分为标记和清除2个过程

      标记:将所有需要回收的对象区域进行标记

      清除:清除所有配标记的区域里的对象

    算法不足之处:

     

    效率问题:标记和清除的效率都不高

    空间问题:清除后产生的空间是不连续的碎片

              无法满足后续运行中大对象的需求

    2.2 复制算法

     

    将整个空间划分2个相等的区域,每次只使用其中一个区域

    当一块内存不够时,就将活着的对象复制到另一块内存

    然后将第一块的内存全部回收。

    这样每次对整个半区回收,就不会有内存碎片的情况,实现简单,运行高效

    问题:  该算法的代价就是可以内存大小缩小为原来的一半

     

    解决:现在商用的虚拟机都采取复制算法.

          但由于所有的对象是朝生夕死的,所以并不是按照1:1的比例来划分内存的

          而是将内存划分为一块较大的Eden区(new一个对象是就是在这里面分配空间)

          和2块Survivor区域。每次使用Eden和一块Survivor。

          当回收的时候,将Eden和Survivor区中还存活的对象一次性赋值到另一块Survivor中

          最后清理掉原来使用过的Eden和Survivor区

          这3个区域也被称为新生代。HotSpot的默认新生代各区域比例如下:

       

         每次新生代中可用的空间为整个新生代的90%, 即80% Eden + 10% 1个Survivor

         此时只有10% 1个Survivor 会被’浪费’

     

         如果另一块Survivor区域存放不下Eden和Survivor区存活下来的对象

         就要依靠其他区域来存放 即老年代

    2.3 标记-整理算法

     

    类似于标记-清除算法,先标记所有可以回收的区域,然后不是直接回收,

    而是把所有存活的对象都移动到一端,然后直接清理掉端边界以外的区域

    标记-整理算法和标记-清除算法常用于老年代的回收

    老年代存放的对象存活的时间较长

    而且垃圾回收的频率不如新生代的频繁


    *对象分配规则


    1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
    2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
    3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
    4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
    5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查
       HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。

    *GC 类型

    新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具
    备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

    老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常
    会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里
    就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10
    倍以上。

    *触发Full GC执行的情况 


    除直接调用System.gc外,触发Full GC执行的情况有如下四种。

    1. 旧生代空间不足
    旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

    2. Permanet Generation空间满
    Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,
    Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
    java.lang.OutOfMemoryError: PermGen space
    为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

    3. CMS GC时出现promotion failed和concurrent mode failure
    对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
    promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
    应对措施为:增大survivor space、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。

    4. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
    这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
    例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
    当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java -Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。


    参考博客:Java垃圾回收机制
    不积跬步无以至千里
  • 相关阅读:
    【备忘】(可持久化)线段树
    和别人一起搞的模拟赛 (1) 题解
    和别人一起搞的模拟赛 (1) 题面
    【讲课】基础的数论知识
    斐波那契
    luogu P6222
    luogu P4240
    二分图网络流做题记录
    ds 瞎做
    P6943 [ICPC2018 WF]Conquer The World 解题报告
  • 原文地址:https://www.cnblogs.com/wihainan/p/4592323.html
Copyright © 2020-2023  润新知