20

【案例演示】JVM之强引用、软引用、弱引用、虚引用

 4 years ago
source link: http://www.cnblogs.com/newAndHui/p/13168410.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1.背景

想要理解对象什么时候回收,就要理解到对象引用这个概念,于是有了下文

2.java中引用对象结构图

byANRzY.png!web

3.引用详解

3.1.什么是强引用

a.当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了00M也不会对该对象进行回收,死都不收。

b.强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。

在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。

当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。

因此强引用是造成Java内存泄漏的主要原因之一

c.对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了〈当然具体回收时机还是要看垃圾收集策略)。

案例:


package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class StrongRefer {
    /**
     * 强引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立强引用
        Object obj2 = obj1;
        // 观察obj1 和 obj2 的各种内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
        // obj1创建可以回收的条件
        obj1 = null;
        // gc回收
        System.gc();
        // 观察各对象情况
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
    }
}

View Code

u6BfYjF.png!web

从测试结果课程看出,obj1的实际对象别没有回收;

3.2.什么是软引用

a.软引用是用来描述一些还有用但并非必需的对象,需要用java.lang.ref.SoftReference类来实现。

b.对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了Soft Reference类来实现软引用。

c.软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

案例:


package com.wfd360.demo03GC.referDemo;

import java.lang.ref.SoftReference;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class SoftRefer {

    /**
     * 软引用的理解
     * 通过设置jvm参数,在不同的条件下观察
     *
     * @param -Xms5m -Xmx5m -XX:+PrintGCDetails
     * @param args
     */
    public static void main(String[] args) {
        // 测试内存充足(不回收软引用)
        //testSoftReferNOGc();
        // 测试内存不充足(回收软引用)
        testSoftReferGc();
    }

    /**
     * 模拟内存充足的情况
     */
    public static void testSoftReferNOGc() {
        Object obj1 = new Object();
        // 建立软引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 观察内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1创建可以回收的条件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次观察内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

    /**
     * 模拟内存不足
     * 1.设置较小的堆内存
     * 2.创建大对象
     * 3.jvm参
     * -Xms5m -Xmx5m -XX:+PrintGCDetails
     */
    public static void testSoftReferGc() {
        Object obj1 = new Object();
        // 建立软引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 观察内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1创建可以回收的条件
        obj1 = null;
        try {
            byte[] bytes = new byte[6 * 1024 * 1024];
        } catch (Throwable e) {
            System.out.println("===============>error:" + e.getMessage());
        } finally {
            // 再次观察内存地址
            System.out.println("obj1=" + obj1);
            System.out.println("softRefer=" + softRefer.get());
        }
    }
}

View Code

内存充足测试结果:

uYJnaiY.png!web

内存不充足测试结果:

YJremyE.png!web

实际案例

假如有一个应用需要读取大量的本地数据(图片、通讯率、临时文件等):

如果每次读取数据都从硬盘读取则会严重影响性能,

如果一次性全部加载到内存中又可能造成内存溢出。

此时使用软引用可以解决这个问题。

设计思路是:用一个HashMap来保存数据的路径和相应数据对象关联的软引用之间的映射关系,在内存不足时,

JVM会自动回收这些缓存数据对象所占用的空间,从而有效地避免了00M的问题。

Map<String,SoftReference>imageCache=new HashMap<String,SoftReference>();

3.3.什么是弱引用

a.弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。

b..当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供广Weak Reference类来实现弱引用。

c.弱引用需要用Java.lang.ref.WeakReference类来实现,它比软引用的生存期更短.

案例:


package com.wfd360.demo03GC.referDemo;

import java.lang.ref.WeakReference;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class WeakRefer {

    /**
     * 弱引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立弱引用
        WeakReference softRefer = new WeakReference<>(obj1);
        // 观察内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1创建可以回收的条件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次观察内存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

}

View Code

eYzQZrI.png!web

扩展知识-WeakHashMap

查看API介绍:

maIVJzB.png!web

测试代码:


package com.wfd360.demo03GC.referDemo;

import java.util.HashMap;
import java.util.WeakHashMap;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 5:10
 * @description <p>
 * 弱引用引用之:WeakHashMap
 * 以弱键 实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。
 * 更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,
 * 然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。
 * </p>
 */
public class WeakReferMap {
    /**
     * 测试 HashMap 与 WeakHashMap 区别
     * 测试逻辑:
     * 1.创建不同的map
     * 2.创建key  value值
     * 3.放入各自的map,并打印结果
     * 4.将key设置为null,并打印结果
     * 5.手动GC,并打印结果
     *
     * @param args
     */
    public static void main(String[] args) {
        hashMapMethod();
        System.out.println("--------华丽的分割线--------");
        weakHashMapMethod();
    }

    /**
     * HashMap测试(强引用)
     */
    private static void hashMapMethod() {
        HashMap<String, String> map = new HashMap<>();
        String key = "key1";
        String value = "HashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);
    }

    /**
     * 若引用(WeakHashMap测试)
     */
    private static void weakHashMapMethod() {
        WeakHashMap<String, String> map = new WeakHashMap<>();
        // 注意这里的new一个字符串与直接写key="key2"对测试结果是有区别的,详细原因可以看之前讲的内存分配
        String key = new String("key2");
        String value = "WeakHashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);

    }

}

View Code

测试结果:

f2mMVrV.png!web

从测试结果可以看出:弱引用的map数据已经被回收。

扩展知识-ReferenceQueue引用队列

JnAna2F.png!web

代码:


package com.wfd360.demo03GC.referDemo;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:23
 * @description
 */
public class QueueRefer {
    /**
     * 测试弱引用回收前,把数据放入队列中
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        // 当GC释放对象内存的时候,会将引用加入到引用队列
        WeakReference<Object> weakReference = new WeakReference<>(obj1, referenceQueue);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------华丽的分割线--------");
        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());
    }

}

View Code

采用弱引用的方式测试结果:

VbYN7nf.png!web

从测试结果可以看出,需要回收的对象已经进入队列。

采用软引用的方式测试结果:

jiyuiiV.png!web

从测试结果可以看出,软引用,没有到达回收的条件,并没有进行回收,也不会进入队列;

3.4.什么是虚引用

1.虚引用需要java.lang.ref.PhantomReference类来实现。

2.与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有

虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访

问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。

3.虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的

机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象己

经进入俑finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。

4.设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加

进一步的处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

代码:


package com.wfd360.demo03GC.referDemo;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:44
 * @description
 */
public class PhantomRefer {
    /**
     * 虚引用测试
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj1,referenceQueue);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------华丽的分割线--------");

        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }

}

View Code

测试结果:

6FruMjN.png!web

4.重要总结

对象是否存活判断流程:

1.可达性分析,看是否有GC Roots的引用链,如果没有将做第一次标记;

2.检查是否需要执行finalize()方法,

如果没必要(之前执行过了),直接回收内存;

如果要执行finalize()方法,这个时候对象如果再次建立引用链(唯一自救机会),对象不会被回收,否则直接回收;

总结:

1.对象回收满足两个条件:

a.没有引用链。

b.回收前会执行finalize()方法,如果执行finalize(),没有再次建立连接(如果重新与引用链上的任意对象建立连接,例如给对象赋值,该对象都不会被回收)

2.在gc回收前会执行finalize()方法,只执行一次,并且是异步执行不保证执行成功,线程优先级低

代码演示:


package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿势帝-博客园
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 8:34
 * @description
 */
public class FinalizeGC {
    public static FinalizeGC obj1 = null;

    /**
     * 重写finalize方法
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("执行finalize方法");
        // 自救,在回收时建立引用链
        FinalizeGC.obj1 = this;
    }

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

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第一次自救成功:"+obj1);

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第二次自救失败,不会再次执行finalize方法:"+obj1);
    }
}

View Code

测试结果:

EbuiAvB.png!web

完美!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK