• JVM中的垃圾收集算法和Heap分区简记


    如何判断垃圾对象?

    垃圾收集的第一步就是先需要算法来标记哪些是垃圾,然后再对垃圾进行处理。
     
    引用计数(ReferenceCounting)算法
    这种方法比较简单直观,FlashPlayer/Python使用该算法,简单高效。核心思路是,给每个对象添加一个被引用计数器,被引用时+1,引用失效-1,等于0时就表示该对象没有被引用,可以被回收。但是,Java/C#并不采用该算法,因为该算法没有解决对象相互引用的问题,即:当两个对象相互引用且不被其它对象引用时,各自的引用计数为1,虽不为0,但仍然是可被回收的垃圾对象。
     
    根搜索(GC Roots Tracing)算法
    基本原理是:GCRoot对象作为起始点(根)。如果从根到某个对象是可达的,则该对象称为“可达对象”(存活对象,不可回收对象)。否则就是不可达对象,可以被回收。
     

    垃圾收集算法

    垃圾收集器通常会假设大部分的对象的存活时间都非常短,只有少数对象的存活时间比较长。
    垃圾收集算法在JVM中主要是复制算法(新生代GC)和标记/整理算法(老年代GC)。
     
    标记-清除(Mark-Sweep)算法
    算法过程:
    1. 先判定对象是否可回收,对其标记。
    2. 统一回收(简单地删除对垃圾对象的内存引用)。
    优点:简单直观容易实现和理解。缺点:效率不高,内存空间碎片化。
     
    复制(Copying)算法
    将内存平均分成A、B两块,算法过程:
    1. 新生对象被分配到A块中未使用的内存当中。当A块的内存用完了, 把A块的存活对象对象复制到B块。
    2. 清理A块所有对象。
    3. 新生对象被分配的B块中未使用的内存当中。当B块的内存用完了, 把B块的存活对象对象复制到A块。
    4. 清理B块所有对象。
    5. goto 1。
    优点:简单高效。缺点:内存代价高,有效内存为占用内存的一半。
     
    对复制算法进一步优化:使用Eden/S0/S1三个分区
    平均分成A/B块太浪费内存,采用Eden/S0/S1三个区更合理,空间比例为Eden:S0:S1==8:1:1,有效内存(即可分配新生对象的内存)是总内存的9/10。
    算法过程:
    1. Eden+S0可分配新生对象;
    2. 对Eden+S0进行垃圾收集,存活对象复制到S1。清理Eden+S0。一次新生代GC结束。
    3. Eden+S1可分配新生对象;
    4. 对Eden+S1进行垃圾收集,存活对象复制到S0。清理Eden+S1。二次新生代GC结束。
    5. goto 1。
     
    标记-紧凑(Mark-Compact)
    算法过程:
    1. 标记:标记可回收对象(垃圾对象)和存活对象。
    2. 紧凑(也称“整理”):将所有存活对象向内存开始部位移动,称为内存紧凑(相当于碎片整理)。完毕后,清理剩余内存空间。
     
    分代收集策略
    由于不同的对象适合使用不同的垃圾收集算法,所以引入“代”这个概念。不同的代有不同的分区,一般分为新生代区和老年代区。
    新生代:适合采用复制算法进行垃圾收集,对象分布在Eden/S0/S1三个区。
    老年代:适合采用标记-紧凑算法进行垃圾收集。
     

    Heap分区和分代概念

    Heap分区的目的
    1. 为了分代:不同代的对象放到不同的内存分区中,实现“代提升”,也方便实现对不同分代采用不同的垃圾收集算法。
    2. 垃圾收集算法需要:新生代GC使用到复制算法,该算法需要将对应的分区划分成三个分区:Eden/S0/S1。
     
    术语
    Generation代
     - YongGeneration/NewGeneration:新生代,在Eden/S0/S1的存活的对象。
     - OldGeneration:老年代,在Tenured区存活的对象。
     - PermanentGeneration:永久代。
    Space 区
     - Eden:伊甸园区,是新生代的一个区。
     - Survivor:幸存区,属于新生代,为了复制算法的需要。一般分成大小相等的两个区(S0/S1或者From/To)。
     - Tenured:存放老年代的区域。
     - Permanent:终身区。

     
    下图:Hotspot 的 Heap 分区
     
     
    下图:VisualVM 中通过 VisualGC插件显示的分区
     
     
    Eden/S0/S1 新生代
    [Eden                 ][S0     ][S1    ]
    S0/S1是大小相当的两个区域,共同组成Survivor区。
    空间比例:Eden:S0==8:1。设定方法:-XX:SurvivorRatio=8。
    新生对象在Eden/S0或者Eden/S1中分配,Eden区的对象量达到一个阈值后,发生一次新生代GC。
     
    Old 老年代
    每个对象有“对象年龄计数器”。对象由Eden收集到Survivor区后,年龄+1。进行新生代GC后,年龄+1。依次,当年龄>=15后进入老年代。
    最大年龄阈值设定:-XX:MaxTenuringThreshold。
    动态年龄:如果在Survivor中所有相同年龄对象占用了空间的一半多,大于等于上述年龄的对象直接进入老年代。
    大对象(比如大的数组)直接进入老年代。阈值设定:-XX:PretenureSizeThreshold。
     
    Perm 永久代(PermanentGeneration)
    用于存放不变对象,如类、方法、字符串等。
    Java7把驻留字符串(intentd string)放到了老年代区。Java8中移除了Hotspot的永久代区。
     

    Refs

    《深入理解Java虚拟机》
  • 相关阅读:
    如何统计一个字符串中某个字符出现的次数
    从GitHub克隆项目到本地
    nginx能做什么
    dubbo的使用实例
    zookeeper单机安装
    Http发送post(x-www-form-urlencoded)请求
    集群与分布式的区别
    cas的客户端应用是负载均衡,单点退出怎么办?
    mybatis执行DDL语句
    sql server 行列互换
  • 原文地址:https://www.cnblogs.com/caca/p/jvm_gc_algorithm_heap_space.html
Copyright © 2020-2023  润新知