理解Java四种引用类型——强软弱虚

Java中引用分为4种类型,按照引用强度递减分别是强引用、软引用、弱引用、虚引用。
这里引用强度指的是被gc回收的存活倾向。

  • 强引用 只要存在、指向的对象就不会被gc回收。
  • 软引用 发生gc时若内存不足,则会回收指向的对象。
  • 弱引用 只要发生gc、所指向的对象就会被回收。
  • 虚引用 所指向的对象获取不到、拿出来是null,因此也叫幽灵对象。只起个标识的作用。

先定义一个用来测试的对象:

public class SomeObject {
    
    @Override
    protected void finalize() throws Throwable{
        System.out.println("OneObject对象即将回收...");
    }
}

强引用

public class StrongReferenceTest {
    public static void main(String[] args) {
        SomeObject object = new SomeObject();
        System.out.println("SomeObject实例:" + object);
        System.gc(); //命令jvm尝试一次fgc
        System.out.println("SomeObject实例:" + object);
        object = null; //使得上面的SomeObject对象失去gc root引用
        System.gc();
    }
}

输出结果:

SomeObject实例:com.wangan.javaref.SomeObject@2a139a55
SomeObject实例:com.wangan.javaref.SomeObject@2a139a55
SomeObject即将被垃圾回收...

第一次手动fgc时,由于存在object = new SomeObject()这个强引用关系,所以SomeObject对象没有被回收,之后object=null相当于使得object引用指向了null,这样一来SomeObject对象就没有gc root引用了,所以第二次fgc它被回收了,被gc之前触发了finalize()方法。

软引用

/**
 * -Xms100m -Xmx100m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
 * */
public class SoftReferenceTest {
    private static final int M = 1024*1024;
    public static void main(String[] args) {
        SoftReference<SomeObject> softRef = new SoftReference<>(new SomeObject());
        SoftReference<byte[]> byteArraySoftRef = new SoftReference<>(new byte[50*M]);
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byte[]:" + byteArraySoftRef.get());
        System.gc();//手工gc
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byte[]:" + byteArraySoftRef.get());
        byte[] anotherByteArray = new byte[50*M]; //再来5M的byte数组,堆内存空间不足,触发gc
        System.out.println("softRef:" + softRef);
        System.out.println("SomeObject:" + softRef.get());
        System.out.println("byteArraySoftRef:" + byteArraySoftRef);
        System.out.println("byte[]:" + byteArraySoftRef.get());
    }
}

输出

SomeObject:SomeObject@2a139a55
byte[]:[B@15db9742
SomeObject:SomeObject@2a139a55
byte[]:[B@15db9742
SomeObject即将被垃圾回收...
softRef:java.lang.ref.SoftReference@6d06d69c
SomeObject:null
byteArraySoftRef:java.lang.ref.SoftReference@7852e922
byte[]:null

我们首先固定住堆大小为100M,然后分配了一个对象和一个50M数组都是软引用,然后System.gc()手工触发gc,这时候由于堆内存还够用,软引用指向的对象没有被回收。之后我们再尝试分配50M的byte数组,大数组直接进入老年代、老年代不足触发gc,gc之后仍然堆内存不足,所以触发回收软引用对象。
值得一提的是,触发gc回收的是软引用指向的对象,也就是那个SomeObject和byte[],而不是回收软引用SoftReference这个实例本身,SoftReference是在所在方法体执行完、对应的栈帧被弹出之后由于失去gc root被回收。
gc日志

Java HotSpot(TM) 64-Bit Server VM (25.60-b23) for windows-amd64 JRE (1.8.0_60-b27), built on Aug  4 2015 11:06:27 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8303348k(4172232k free), swap 14332660k(7197788k free)
CommandLine flags: -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.638: [GC (System.gc()) [PSYoungGen: 2064K->800K(29696K)] 53264K->52008K(98304K), 0.0039739 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.642: [Full GC (System.gc()) [PSYoungGen: 800K->0K(29696K)] [ParOldGen: 51208K->51734K(68608K)] 52008K->51734K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0250932 secs] [Times: user=0.13 sys=0.02, real=0.03 secs] 
0.669: [GC (Allocation Failure) [PSYoungGen: 512K->0K(29696K)] 52246K->51734K(98304K), 0.0025944 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.672: [GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] 51734K->51734K(98304K), 0.0028220 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.675: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] [ParOldGen: 51734K->51734K(68608K)] 51734K->51734K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0081899 secs] [Times: user=0.06 sys=0.02, real=0.01 secs] 
0.683: [GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] 51734K->51734K(98304K), 0.0029494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.686: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(29696K)] [ParOldGen: 51734K->523K(68608K)] 51734K->523K(98304K), [Metaspace: 2642K->2642K(1056768K)], 0.0212280 secs] [Times: user=0.09 sys=0.00, real=0.02 secs] 
Heap
 PSYoungGen      total 29696K, used 1280K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
  eden space 25600K, 5% used [0x00000000fdf00000,0x00000000fe0401c0,0x00000000ff800000)
  from space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
  to   space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
 ParOldGen       total 68608K, used 51723K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
  object space 68608K, 75% used [0x00000000f9c00000,0x00000000fce82cd8,0x00000000fdf00000)
 Metaspace       used 2649K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 286K, capacity 386K, committed 512K, reserved 1048576K

软引用的特性比较适合用于构建进程内缓存。

弱引用

public class WeakReferenceTest {
    public static void main(String[] args) {
        WeakReference<SomeObject> weakRef = new WeakReference<>(new SomeObject());
        System.out.println("weakRef: " + weakRef);
        System.out.println("SomeObject: " + weakRef.get());
        System.gc();
        System.out.println("weakRef: " + weakRef);
        System.out.println("SomeObject: " + weakRef.get());
    }
}

输出

weakRef: java.lang.ref.WeakReference@2a139a55
SomeObject: SomeObject@15db9742
weakRef: java.lang.ref.WeakReference@2a139a55
SomeObject: null
SomeObject即将被垃圾回收...

内存足够的情况下发生gc(这里我们是用手工System.gc,实际情况下更可能是年轻代分配对象空间不够触发的ygc),这时候弱引用指向的对象也会被回收。也就是弱引用不管内存是否足够都会在下一次gc中被回收。
ThreadLocal中有关于弱引用WeakReference的应用。

虚引用

我们无法获取虚引用所指向的对象,虚引用一般被用来释放堆外内存
因此它所指向的一般是堆外内存。
jvm无法直接管理和回收堆外内存,因此在gc的时候如果要顺带触发堆外内存的回收的话,必须找到个办法能知道这时候要回收哪块堆外内存。
比如某个方法内创建了一个虚引用临时变量,指向堆外内存,随着线程栈的弹出,理论上我们是要回收这个内存的,因为没有root引用了。
这时候就将这个虚引用放入创建它时候指定的那个引用队列,做个记录。之后再从引用队列里找到这个引用来释放相应的直接内存。

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

/**
 * 我们无法获取虚引用所指向的对象,虚引用一般被用来释放堆外内存
 * 因此它所指向的一般是堆外内存。
 * jvm无法直接管理和回收堆外内存,因此在gc的时候如果要顺带触发堆外内存的回收的话,必须找到个办法能知道这时候要回收哪块堆外内存。
 * 比如某个方法内创建了一个虚引用临时变量,指向堆外内存,随着线程栈的弹出,理论上我们是要回收这个内存的,因为没有root引用了。
 * 这时候就将这个虚引用放入创建它时候指定的那个引用队列,做个记录。之后再从引用队列里找到这个引用来释放相应的直接内存。
 * */
public class PhantomReferenceTest {
    private static final ReferenceQueue<SomeObject> queue = new ReferenceQueue<>();
    
    public static void main(String[] args) {
        /**
         * SomeObject oneObject = new SomeObject();
         * PhantomReference<SomeObject> oneObjectRef = new PhantomReference<SomeObject>(oneObject, queue);
         * 这么搞的话底下System.gc()无法回收SomeObject对象,因为这里用oneObject指向了这个对象,
         * 且后者是个强引用,线程没执行完,栈帧没从线程栈里弹出来、线程栈也没销毁。
         * 所以不回收。
         * */
        //SomeObject oneObject = new SomeObject(); //创建一个SomeObject对象;
        PhantomReference<SomeObject> oneObjectRef = new PhantomReference<SomeObject>(new SomeObject(), queue);//创建这个对象的虚引用
        System.out.println("虚引用oneObjectRef的地址:" + oneObjectRef);
        System.out.println("虚引用oneObjectRef关联的SomeObject对象的地址:" + oneObjectRef.get());
        
        while(true) {
            System.gc(); //命令jvm尝试一次fgc,上边的new OneObject()没有强引用,要被回收了
            Reference<? extends SomeObject> ref = queue.poll();
            if(null!=ref) {
                System.out.println("gc过后,在引用队列中找到了虚引用,一般来说其所指向的是直接内存,无法gc,需要做特殊处理。" + ref);
                break;
            }
        }
    }
}
package com.wangan.direct;
import java.lang.reflect.Field;

import sun.misc.Unsafe;
public class UnsafeUtil {
    
    /**
     * 返回Unsafe实例
     * */
    public static Unsafe getUnsafeInstance() throws Exception{
        //return Unsafe.getUnsafe();
        Field unsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeInstance.setAccessible(true);
        return (Unsafe)unsafeInstance.get(Unsafe.class);
    }
}

参考文章:
https://blog.csdn.net/u011291072/article/details/106315905
https://blog.csdn.net/aitangyong/article/details/39455229

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容