2020-03-10-Java垃圾回收

2020-03-10  本文已影响0人  耿望

内存模型

Java内存模型.jpg

Java将内存划分为几个不同的数据区域,其中,程序计数器,虚拟机栈,本地方法栈是每个线程独立,线程之间隔离的区域,堆区域和堆外内存(本地内存)区域是线程之间共享的,但是在内存分配的过程中,JVM可以划分出多个线程私有的分配缓冲区,它们是分配私有,但是都有访问权限。
1.程序计数器
为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码指令地址。
2.虚拟机栈
每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,而且 每个方法从调用直至完成的过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程。
3.本地方法栈
本地方法栈为虚拟机执行 Native 方法服务,类似虚拟机栈,只不过虚拟机栈是Java方法。
4.堆内存
堆用于存放对象实例,几乎所有的对象实例(和数组)都在这里分配内存。根据分代收集算法,将堆区域分为新生代和老年代。
5.本地内存
本地内存也叫堆外内存,其中一部分称为方法区,用于存储已被虚拟机加载的 类信息、常量、静态变量和即时编译器编译后的代码等数据。

垃圾识别

1.引用计数法
一个对象没有被引用,引用计数为0,即可被回收。
这种方法有个弊端,即互相引用的对象,无法被回收。
2.可达性算法
如果把所有对象当做树的节点,把虚拟机栈,本地方法栈和方法区中的对象当做根节点,叫GC Roots。那么GC Roots对象不可达的节点即可被回收。
以下四种通常被作为GC Roots。
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中常量和静态变量引用的对象
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象


Java垃圾回收 (1).jpg

垃圾回收

1.标记清除算法
直接将标记为可回收的对象进行回收,优点是快速高效,缺点是会产生很大内存碎片。
2.复制算法
把堆等分成两块区域,A区域执行GC时将A区域存活对象全部复制到B区域,然后A区域全部清理,交替进行。优点是解决了内存碎片问题,缺点是损失一半可用内存,且复制开销大。
3.标记整理法
在标记清除的基础上,将存活对象移动到一端,优点是解决了内存碎片问题,缺点是移动开销大。
4.分代收集算法
根据对象存活周期的不同选择最合适的垃圾回收算法。
堆分成新生代和老生代,默认比例为 1 : 2,新生代又分为 Eden 区, from Survivor 区(简称S0),to Survivor 区(简称 S1),三者的比例为 8: 1 : 1,把新生代发生的 GC 称为 Young GC(也叫 Minor GC),老年代发生的 GC 称为 Old GC(也称为 Full GC)。
当 Eden 区将满时,触发 Minor GC。
经过 Minor GC 后只有少部分对象会存活,它们会被移到 S0 区,同时对象年龄加一。
当触发下一次 Minor GC 时,会把 Eden 区的存活对象和 S0中的存活对象一起移到 S1。
若再触发下一次 Minor GC,则重复上一步,只不过此时变成了 从 Eden,S1 区将存活对象复制到 S0 区。
在 Eden 区的垃圾回收我们采用的是复制算法。
当对象的年龄达到了我们设定的阈值,则会从S0(或S1)晋升到老年代。
特殊情况:
大对象:当某个对象分配需要大量的连续内存时,此时对象的创建不会分配在 Eden 区,会直接分配在老年代。
如果老年代满了,会触发 Full GC, Full GC 会同时回收新生代和老年代(即对整个堆进行GC),它会导致 Stop The World(简称 STW),造成挺大的性能开销。
谓的 STW, 即在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。

四种引用算法

前面提到了引用计数算法,一个对象没有引用的时候才会被回收。
强引用
通常我们使用的引用类型都是强引用。

Object obj = new Object();

这样一个对象,只要在生命周期范围内都不会被回收。
如果需要回收,需要指定为null;

obj = null;

软引用
当发生OOM时会回收只具有软引用的对象。
弱引用
当发生gc时会回收只具有弱引用的对象。
虚引用
当发生gc时会回收只具有虚引用的对象。
下面给出一个例子。

    private class GCTarget {
        
        public String name;
        private byte[] buffer = new byte[1024];
        
        public GCTarget(String name) {
            System.out.println("create " + name);
            this.name = name;
        }
        
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize " + name);
            super.finalize();
        }
    }

当手动调用gc后,弱引用和虚引用的对象都会被回收。

    public void testGC() {
        GCTarget strongRef = new GCTarget("strongRef");
        ReferenceQueue<GCTarget> softQueue = new ReferenceQueue<GCTarget>();
        ReferenceQueue<GCTarget> weakQueue = new ReferenceQueue<GCTarget>();
        ReferenceQueue<GCTarget> phantomQueue = new ReferenceQueue<GCTarget>();
        SoftReference<GCTarget> softReference = new SoftReference<GCTarget>(new GCTarget("softReference"), softQueue);
        WeakReference<GCTarget> weakReference = new WeakReference<GCTarget>(strongRef, weakQueue);
        PhantomReference<GCTarget> phantomReference = new PhantomReference(strongRef, phantomQueue);
        strongRef = null;
        System.out.println("weakQueue.poll=" + weakQueue.poll());
        sleep(2000);
        System.gc();
        sleep(2000);
        getObject(softReference);
        getObject(weakReference);
        getObject(phantomReference);
        System.out.println("weakQueue.poll=" + weakQueue.poll());
    }

    private void getObject(java.lang.ref.Reference<GCTarget> reference) {
        if (reference.get() == null) {
            System.out.println("getObject " + reference.getClass().getSimpleName() + " Null");
        } else {
            System.out.println("getObject "+ reference.getClass().getSimpleName() + " " + reference.get().name);
        }
    }

参考

JVM 内存模型概述
图解Java 垃圾回收机制
理解Java的强引用、软引用、弱引用和虚引用

上一篇 下一篇

猜你喜欢

热点阅读