JVMjava进阶干货程序员

垃圾收集策略

2017-12-16  本文已影响52人  紫霞等了至尊宝五百年

程序计数器、虚拟机栈、本地方法栈都是线程私有的,会随着线程而生,随线程而灭
栈中的栈帧随着方法的进入和退出有条不紊的执行着出栈和入栈操作.

静态内存分配和回收

动态内存分配和回收

堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一点心思.

1 Java堆内存的回收

1.1 判定回收的对象

在对堆进行对象回收之前,首先要判断哪些是无效对象即一个对象不被任何对象或变量引用,需要被回收.一般有两种判别方式:

GC Roots并不包括堆中对象所引用的对象!这样就不会出现循环引用.

因此,目前主流语言均使用可达性分析方法来判断对象是否有效.

2 回收无效对象的过程

当经可达性算法筛选出失效的对象之后,并不是立即清除,而是再给对象一次重生的机会,具体过程如下:

注意:强烈不建议使用finalize()进行任何操作!
如果需要释放资源,请用try-finally或者其他方式都能做得更好.
因为finalize()不确定性大,开销大,无法保证各个对象的调用顺序.

以下代码示例看到:一个对象的finalize被执行,但依然可以存活

/**
 * 演示两点:
 * 1.对象可以在被GC时自救
 * 2.这种自救机会只有一次,因为一个对象的finalize()最多只能被系统自动调用一次,因此第二次自救失败
 * @author sss
 * @since 17-9-17 下午12:02
 *
 */
public class FinalizeEscapeGC {

    private static FinalizeEscapeGC SAVE_HOOK = null;

    private void isAlive() {
        System.out.println("yes,I am still alive :)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize methodd executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }


    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();

        // 对象第一次成功自救
        SAVE_HOOK = null;
        System.gc();
        // 因为finalize方法优先级很低,所以暂停0.5s以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,I am dead :(");
        }

        // 自救失败
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,I am dead :(");
        }
    }
}

运行结果

finalize methodd executed!
yes,I am still alive :)
no,I am dead :(

3 方法区的内存回收

如果使用复制算法实现堆的内存回收,堆就会被分为新生代和老年代

由于方法区中存放生命周期较长的类信息、常量、静态变量.
因此方法区就像堆的老年代,每次GC只有少量垃圾被清除.

方法区中主要清除两种垃圾

3.1 回收废弃常量

回收废弃常量和回收对象类似,只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除.

3.2 回收无用类

判定无用类的条件则较为苛刻

4 垃圾收集算法

知道了判定方法,也就知道了垃圾收集器会清除哪些数据,那么接下来介绍如何清除这些数据.

4.1 标记-清除算法(Mark-Sweep)

最基础的收集算法,后续算法也都是基于此并改进其不足而得.

首先利用刚才介绍的方法判断需要清除哪些数据,并给它们做上标记;然后清除被标记的数据.

不足

4.2 复制算法(Copying)

将内存分成大小相等两份,只将数据存储在其中一块上

4.2.1 分析

4.2.2 解决空间利用率问题

在新生代中,由于大量对象都是"朝生夕死",也就是一次垃圾收集后只有少量对象存活
因此我们可以将内存划分成三块

分配内存时,只使用Eden和一块Survior1.

通过这种方式,只需要浪费10%的内存空间即可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题.

4.2.3 分配担保

准备为一个对象分配内存时,发现此时Eden+Survior中空闲的区域无法装下该对象
就会触发MinorGC,对该区域的废弃对象进行回收.

但如果MinorGC过后只有少量对象被回收,仍然无法装下新对象

在发生 minor gc 前,虚拟机会检测老年代最大可用连续空间是否大于新生代所有对象总空间,
若成立,minor gc 可确保安全
若不成立,JVM会查看 HandlePromotionFailure是否允许担保失败

4.3 标记-整理算法(Mark-Compact)

在回收前,标记过程仍与"标记-清除"一样
但后续不是直接清理可回收对象,而是

分析

这是一种老年代垃圾收集算法.
老年代中对象一般寿命较长,每次垃圾回收会有大量对象存活
因此如果选用"复制"算法,每次需要較多的复制操作,效率低

而且,在新生代中使用"复制"算法
当 Eden+Survior 都装不下某个对象时,可使用老年代内存进行"分配担保"

而如果在老年代使用该算法,那么在老年代中如果出现 Eden+Survior 装不下某个对象时,没有其他区域给他作分配担保

因此,老年代中一般使用"标记-整理"算法

4.4 分代收集算法(Generational Collection)

当前商业虚拟机都采用此算法.
根据对象存活周期的不同将Java堆划分为老年代和新生代,根据各个年代的特点使用最佳的收集算法.

5 Java中引用的种类

Java中根据生命周期的长短,将引用分为4类

上一篇下一篇

猜你喜欢

热点阅读