让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?

简介: 本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。

@TOC

一、⭐⭐⭐工具🌙🌙🌙

Eclipse+JDK1.8
注:测试前需配置eclipse的JVM运行参数:-Xmx10m -Xms10m([如何配置eclipse的JVM运行参数](https://blog.csdn.net/u010425839/article/details/115468207)),只分配10M的内存。因为会使用创建指定大小字节数组的方式,来触发内存临界值(10M),进而观察内存足够和不足两种场景下不同引用类型的具体表现。

二、⭐⭐⭐概念🌙🌙🌙

1 强引用类型

强引用类型的对象无特定类型,硬气!宁折不弯,即便在内存不足报OutOfMemmoryError(OOM)的情况下,也不肯被回收。如:Object obj = new Object()。

2 软引用类型

软引用类型的对象SoftReference,软骨头,内存足够的情况下,尚且不怂;内存资源一紧张(债主逼上门)就立马把自己的内存空间释放了(缴械投降,把家底卖了)。如:SoftReference softRef=new SoftReference(obj)。

3 弱引用类型

弱引用类型的对象WeakReference,体弱气虚,即使内存足够的情况下,也会把自己所拥有的内存空间上交。如:WeakReference softRef=new WeakReference(obj)。

4 虚引用类型

虚引用类型的对象PhantomReference,同弱引用类型,在内存足够时也会把内存空间上交,但是与弱引用类型不同的时,虚引用类型必须要与ReferenceQueue一起搭配使用。如:PhantomReference softRef=new PhantomReference(obj,queue),其中queue为ReferenceQueue,用来存放被回收的引用类型对象(软引用弱引用可不与之搭配使用,虚引用必须与之一起搭配使用)。

三、⭐⭐⭐结论🌙🌙🌙

1 在内存足够的情况下,不会回收强引用对象;
2 在内存不够的情况下,即便是报OutOfMemoryError(OOM)错误,也不会回收强引用对象;
3 在内存足够的情况下不会回收软引用对象
4 在JVM认为内存不够的(可能并非实际内存真的不够用于创建新对象了,而是JVM判断如果再不回收一些对象的话,剩余空间再去创建新对象会太紧张了)情况下才会回收软引用对象
5 在内存还足够的情况下就会回收弱引用对象
6 在内存不够的情况下,会回收弱引用对象
7 在内存足够的情况下也会回收虚引用对象(jdk8 jdk9)
8 在内存不够的情况下,也会回收虚引用对象(jdk8 jdk9)

四、⭐⭐⭐代码🌙🌙🌙

注1:以下测试代码,分为8部分,每次请只执行一部分,因为每次执行就是一个具体的场景,同时执行多个部分会造成不同场景之间的干扰,影响正确地观察及推理测试结果。
注2:JDK8中对这么一段对PhantomReference类的描述:与软弱引用不同,虚引用在垃圾收集器排入队列时不会自动清除。 通过虚引用可访问的对象将一直保持到所有这样的引用被清除或者自身变得不可访问。所以需要加入将虚引用置为null的处理,否则不会回收虚引用指向的对象的内存空间。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.text.DecimalFormat;
import java.util.Arrays;

/**
 * @author Dylaniou
 * 定义引用类型枚举类,用于结合switch case完成不同类型的引用对象的创建
 */
enum ReferenceType{
             STRONG,SOFT,WEAK,PHANTOM}
class Obj{
              
    private byte[] id;
    private String msg;
    public Obj(byte[] id,ReferenceType referenceType) throws InterruptedException {
            
        System.out.println("⭐⭐⭐Creating:" + referenceType+ "[" + this.getClass().getSimpleName() + this.hashCode() + "]⭐⭐⭐");
        this.id = id;  
    }  
    public void finalize() {
            //此处用于告知对象被回收了
        System.out.println("🌙🌙🌙Finalizing[" + this.getClass().getSimpleName() + this.hashCode()  +"]🌙🌙🌙");  
    }
    public String getMsg() {
            
        return msg;
    }
    public void setMsg(String msg) {
            
        this.msg = msg;
    }
    @Override
    public String toString() {
            
        return "Obj [id=" + Arrays.toString(id) + ", msg=" + msg + "]";
    }  

}
/**
 * @author Dylaniou
 * 测试4种引用类型分别在内存足够和内存不足的情况下,面对System.gc()时,引用对象被回收的情况如何。
 * 注:测试前需配置eclipse的JVM运行参数:-Xmx10m -Xms10m,只分配10M的内存。
 *       使用创建指定大小字节数组的方式,来触发内存临界值(10M),进而观察内存足够和不足两种场景下不同引用类型的具体表现。
 */
public class TestPhantom {
            
    static ReferenceQueue<Obj> queue = new ReferenceQueue<>();
    static Object o,r1,r2,r3,r4;//用于指向测试过程中生成的引用对象
    static int unit = 1024 * 1024;//1M=1024K=1024*1024byte
     //显示JVM总内存,JVM最大内存和总空闲内存
    public static void displayAvailableMemory(ReferenceType referenceType,int num,int capacity) throws InterruptedException {
            
        Object obj = null;
        System.out.println("-----------------" + referenceType +"[第" + num + "次运行,create " + capacity + "M] ----------------");
        int size = unit*capacity;
        //switch case搭配上面定义的引用类型的枚举类,完成不同类型的引用对象的创建
        switch(referenceType){
            
            case STRONG:
                obj = new Obj(new byte[size],referenceType);
                break;
            case SOFT:
                obj = new SoftReference<Obj>(new Obj(new byte[size],referenceType), queue);
                break;
            case WEAK:
                obj = new WeakReference<Obj>(new Obj(new byte[size],referenceType), queue);
                break;
            case PHANTOM:
                obj = new PhantomReference<Obj>(new Obj(new byte[size],referenceType), queue);
                break;
        }
        //一定要把创建的各种引用对象赋给变量,否则没变量指向的引用对象,无论是哪种引用类型,在gc的时候可能都会被清除掉,体现不出来引用类型之间的不同特点
        if(num == 0){
            o=obj;}
        if(num == 1){
            r1=obj;}
        if(num == 2){
            r2=obj;}
        if(num == 3){
            r3=obj;}
        if(num == 4){
            r4=obj;}
        System.gc();System.gc();//此处如果不加两次gc,软引用在内存不够的情况下,可能还没回收对象成功就报OOM错误了,体现不出效果,故加两次gc。【不影响其他引用类型的结果】
        Thread.sleep(50);
        checkMem();
        checkQueue();
    }

    public static void checkMem(){
            
        DecimalFormat df = new DecimalFormat("0.00") ;
        Runtime runtime = Runtime.getRuntime();
        //显示JVM总内存
        long totalMem = runtime.totalMemory();
        System.out.println("totalMem:" + df.format(totalMem/unit) + "MB");
        //显示JVM尝试使用的最大内存
        long maxMem = runtime.maxMemory();
        System.out.println("maxMem:" + df.format(maxMem/unit) + "MB");
        //空闲内存
        long freeMem = runtime.freeMemory();
        System.out.println("freeMem:" + df.format(freeMem/unit) + "MB");
    }

    /**
     * 用于检查是否有引用类型的对象被回收,若被回收,则会出发入队列操作
     */
    public static void checkQueue() {
              
        Reference inq = queue.poll();  
        // 从队列中取出一个引用 
        if (inq != null)  
            System.out.println("In queue: " + inq.getClass().getSimpleName() +  inq.hashCode() + " : " + inq.get());  
    }  
    public static void main(String[] args) throws InterruptedException {
            
        //注:以下测试代码,分为8部分,每次请只执行一部分,因为每次执行就是一个具体的场景,同时执行多个部分会造成不同场景之间的干扰,影响正确地观察及推理测试结果。
        /*
        //1.1 从运行结果来看,在内存足够的情况下,不会回收强引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.STRONG,0,0);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,1,1);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,2,1);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,3,1);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,4,1);Thread.sleep(500);
        */
        /*
        //1.2 从运行结果来看,在内存不够的情况下,即便是报OutOfMemoryError(OOM)的错误,也不会回收强引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.STRONG,0,0);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,1,5);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,2,1);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,3,1);Thread.sleep(500);
        displayAvailableMemory(ReferenceType.STRONG,4,1);Thread.sleep(500);
        */
        /*
        //2.1 从运行结果来看,在内存足够的情况下不会回收软引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.SOFT,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,1,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,2,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,4,1);Thread.sleep(1000);
        */
        /*
        //2.2 从运行结果来看,在JVM认为内存不够的(可能并非实际内存真的不够用于创建新对象了,而是JVM判断如果再不回收一些对象的话,剩余空间再去创建新对象会太紧张了)情况下才会回收软引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.SOFT,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,1,5);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,2,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.SOFT,4,1);Thread.sleep(1000);
        */
        /*
        //3.1 从运行结果来看,在内存还足够的情况下就会回收弱引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.WEAK,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,1,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,2,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,4,1);Thread.sleep(1000);
        */
        /*
        //3.2 从运行结果来看,在内存不够的情况下,会回收弱引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.WEAK,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,1,5);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,2,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.WEAK,4,1);Thread.sleep(1000);
        */
        /*
        //4.1 从运行结果来看,在内存足够的情况下也会回收虚引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.PHANTOM,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,1,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,2,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,4,1);Thread.sleep(1000);
        //JDK8中对这么一段对PhantomReference类的介绍:与软弱引用不同,虚引用在垃圾收集器排入队列时不会自动清除。 通过虚引用可访问的对象将一直保持到所有这样的引用被清除或者自身变得不可访问。
        //所以需要加入将虚引用置为null的处理,否则不会回收虚引用指向的对象的内存空间。
//        System.out.println("引用对象不为空,队列不为空,不会回收虚引用指向的对象所占有的内存空间");System.gc();checkMem();
//        queue = null;System.out.println("引用对象不为空,队列为空,不会回收虚引用指向的对象所占有的内存空间");System.gc();checkMem();
        o = null;r1 = null;r2=null;r3=null;r4=null;System.out.println("引用对象为空,队列不为空,会回收虚引用指向的对象所占有的内存空间");System.gc();checkMem();
//        o = null;r1 = null;r2=null;r3=null;r4=null;queue = null;System.out.println("引用对象为空,队列为空,会回收虚引用指向的对象所占有的内存空间");System.gc();checkMem();
        */
        /*
        //4.2 从运行结果来看,在内存不够的情况下,也会回收虚引用指向的对象所占有的内存空间
        displayAvailableMemory(ReferenceType.PHANTOM,0,0);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,1,5);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,2,1);Thread.sleep(1000);
        o = null;r1 = null;r2=null;r3=null;r4=null;System.out.println("引用对象为空,队列不为空,会回收虚引用指向的对象所占有的内存空间");System.gc();checkMem();
        displayAvailableMemory(ReferenceType.PHANTOM,3,1);Thread.sleep(1000);
        displayAvailableMemory(ReferenceType.PHANTOM,4,1);Thread.sleep(1000);
        */
    }
}

目录
相关文章
|
3天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
16 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
13 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
12天前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
22 0
深入理解java对象的内存布局
|
14天前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
5天前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
14天前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(三)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
14天前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(一)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
3月前
|
存储 分布式计算 Hadoop
HadoopCPU、内存、存储限制
【7月更文挑战第13天】
244 14
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
232 0
|
5天前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
22 4