欢迎光临北大青鸟华大校区

  Java GC 的那些事(1)

  前言

  与C语言不同,Java内存(堆内存)的分配与回收由JVM垃圾收集器自动完成,这个特性深受大家欢迎,能够帮助 程序员 更好的编写代码,本文以HotSpot虚拟机为例,说一说Java GC的那些事。

  Java堆内存

  在 JVM内存的那些事 一文中,我们已经知道Java堆是被所有线程共享的一块内存区域,所有对象实例和数组都在堆上进行内存分配。
  新生代

  新生代由 Eden 与 Survivor Space(S0,S1)构成,大小通过-Xmn参数指定,Eden 与 Survivor Space 的内存大小比例默认为8:1,可以通过-XX:SurvivorRatio 参数指定,比如新生代为10M 时,Eden分配8M,S0和S1各分配1M。

  Eden:希腊语,意思为伊甸园,在圣经中,伊甸园含有乐园的意思,根据《旧约·创世纪》记载,上帝耶和华照自己的形像造了男人亚当,再用亚当的一个肋骨创造了一个女人夏娃,并安置他们住在了伊甸园。

  大多数情况下,对象在Eden中分配,当Eden没有足够空间时,会触发一次Minor GC,虚拟机提供了-XX:+PrintGCDetails参数,告诉虚拟机在发生垃圾回收时打印内存回收日志。

  Survivor:意思为幸存者,是新生代和老年代的缓冲区域。

  当新生代发生GC(Minor GC)时,会将存活的对象移动到S0内存区域,并清空Eden区域,当再次发生Minor GC时,将Eden和S0中存活的对象移动到S1内存区域。

  存活对象会反复在S0和S1之间移动,当对象从Eden移动到Survivor或者在Survivor之间移动时,对象的GC年龄自动累加,当GC年龄超过默认阈值15时,会将该对象移动到老年代,可以通过参数-XX:MaxTenuringThreshold 对GC年龄的阈值进行设置。

 
  如何判断对象是否存活

  GC动作发生之前,需要确定堆内存中哪些对象是存活的,一般有两种方法:引用计数法和可达性分析法。

  1、引用计数法

  在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。

  引用计数法实现简单,但不能解决对象之间相互引用的问题。

  public class GCtest {    private Object instance = null;    private static final int _10M = 10 * 1 << 20;    // 一个对象占10M,方便在GC日志中看出是否被回收    private byte[] bigSize = new byte[_10M];    public static void main(String[] args) {        GCtest objA = new GCtest();        GCtest objB = new GCtest();        objA.instance = objB;        objB.instance = objA;        objA = null;        objB = null;        System.gc();    }}

  通过添加-XX:+PrintGC参数,运行结果:

  [GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]

  从 GC日志 中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。

  2、可达性分析法

  通过一系列称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 引用链,以下对象可作为GC Roots:

  本地变量表中引用的对象

  方法区中静态变量引用的对象

  方法区中常量引用的对象

  Native方法引用的对象

  当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。

  在可达性分析法中,判定一个对象objA是否可回收,至少要经历两次标记过程:

  1、如果对象objA到 GC Roots没有引用链,则进行标记。

  2、如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法。finalize()方法是对象逃脱死亡的机会,GC会对队列中的对象进行标记,如果objA在finalize()方法中与引用链上的任何一个对象建立联系,那么标记时,objA会被移出即将回收集合。

  看看具体实现

  public class FinalizerTest {    public static FinalizerTest object;    public void isAlive() {        System.out.println(I'm alive);    }    @Override    protected void finalize() throws Throwable {        super.finalize();        System.out.println(method finalize is running);        object = this;    }    public static void main(String[] args) throws Exception {        object = new FinalizerTest();        // ,finalize方法会自救        object = null;        System.gc();        Thread.sleep(500);        if (object != null) {            object.isAlive();        } else {            System.out.println(I'm dead);        }        // 执行,finalize方法已经执行过        object = null;        System.gc();        Thread.sleep(500);        if (object != null) {            object.isAlive();        } else {            System.out.println(I'm dead);        }    }}

  执行结果:

  method finalize is runningI'm aliveI'm dead

  从执行结果可以看出:

  发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;

  发生GC时,由于finalize方法只会被JVM调用一次,object被回收。

  当然了,在实际项目中应该尽量避免使用finalize方法。

  Java GC 的那些事(1)

  Java GC的那些事(2)

我的位置: 首页 >> Java GC 的那些事(1)

2018-08-07

来源:


 

在线答疑更多++

热门专题更多++

北大青鸟佛山华大IT学院

地址:广东省佛山市禅城区佛山大道北143号

电话:4006-989-522  0757-88726000

网址: www.foshanbdqn.com

佛山校区乘车路线:张槎路口站、白燕公园站、轻工路口站、金沙新城南门站

北大青鸟华大校区公众平台

佛山北大青鸟