【Java原理探索】夯实你对Java对象引用的认识

简介: 【Java原理探索】夯实你对Java对象引用的认识

前提概要


不知道我的新主题,大家喜不喜欢,我把它叫做风车主题,哈哈,有过喜欢的小伙伴们,可以大家一起讨论,做一些更好的主题给大家,接下来给大家介绍一下今天的主题内容:引用的分类,针对于GC来讲,引用方式是一种非常重要的知识点,这可以让我们更加深入的理解GC和JVM对对象的控制方式,接下来就让我们进入




Java引用的分类


Java对象主要的引用类型分为:强引用(new)软引用(soft)、弱引用(weak)、虚引用(plantom)、终结器引用(finalizer)


image.png




强引用


只要能够通过GC Roots的引用链找到就不会被垃圾回收,也就是说只有所有的GC Roots对象都不通过强引用引用该对象的时候,该对象才能被垃圾回收,否则即使出现OOM,也不能够回收对象。



案例分析


设置jvm的内存为20M:


image.png


public class Test2 {
    public static final int _4MB=4*1024*1024;
    public static void main(String[] args) throws IOException {
        List<byte[]> list =new ArrayList<>();
        for(int i=0;i<5;i++){
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }
}
复制代码



运行上面的程序会出现内存溢出问题,也就是说有的内存即使不太重要依旧会被占用:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at pers.zhb.test.Test2.main(Test2.java:12)
复制代码





软引用


Java中使用SoftRefence对象来表示软引用,如果某个对象与软引用关联,那么JVM只会在内存不足的情况下回收该对象





案例分析


image.png


public class Test2 {
    private static final int _4MB=4*1024*1024;
    public static void main(String[] args) throws IOException {
        soft();
    }
    public static void soft(){
        List<SoftReference<byte[]>> list=new ArrayList<>();
        for(int i=0;i<5;i++){
            SoftReference<byte[]> reference=new SoftReference<>(
            new byte[_4MB]);
            System.out.println(reference.get());
            list.add(reference);
            System.out.println(list.size());
        }
        System.out.println("循环结束:"+list.size());
        for(SoftReference<byte[]> reference:list){
            System.out.println(reference.get());
        }
    }
}
复制代码



运行结果:

[B@1b6d35861[B@4554617c2[B@74a144823[GC (Allocation Failure) [PSYoungGen: 1744K->488K(6144K)] 14032K->12932K(19968K), 0.0427814 secs] [Times: user=0.00 sys=0.00, real=0.05 secs] 
[B@1540e19d4[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17140K->17140K(19968K), 0.0030222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4696K->4574K(6144K)] [ParOldGen: 12444K->12414K(13824K)] 17140K->16988K(19968K), [Metaspace: 3118K->3118K(1056768K)], 0.0095409 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4574K->4574K(6144K)] 16988K->16996K(19968K), 0.0010492 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4574K->0K(6144K)] [ParOldGen: 12422K->587K(8704K)] 16996K->587K(14848K), [Metaspace: 3118K->3118K(1056768K)], 0.0076471 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[B@677327b65循环结束:5nullnullnullnull[B@677327b6HeapPSYoungGen  total 6144K, used 4546K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)eden space 5632K, 80% used [0x00000000ff980000,0x00000000ffdf0940,0x00000000fff00000)from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)ParOldGen  total 8704K, used 587K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)object space 8704K, 6% used [0x00000000fec00000,0x00000000fec92d20,0x00000000ff480000)Metaspace  used 3224K, capacity 4500K, committed 4864K, reserved 1056768Kclass space used 349K, capacity 388K, committed 512K, reserved 1048576K复制代码



结论


可以看出,在第四次和第五次的时候就触发了垃圾回收,在对集合进行遍历后只有第五个软引用对象存在,也就是说在内存不足的时候将前面的虚引用的对象进行了垃圾回收



软引用与引用队列


public class Test2 {
    private static final int _4MB=4*1024*1024;
    public static void main(String[] args) throws IOException {
        List<SoftReference<byte[]>> list=new ArrayList<>();
        //引用队列
        ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
        for(int i=0;i<5;i++){
            //关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己会加入到引用队列中
            SoftReference<byte[]> reference=new SoftReference<>(new byte[_4MB],queue);
            System.out.println(reference.get());
            list.add(reference);
            System.out.println(list.size());
        }
        //从队列获取无用的软引用对象并移除
        Reference<? extends byte[]> poll=queue.poll();
        while(poll!=null){
            list.remove(poll);
            poll=queue.poll();
        }
        System.out.println("循环结束");
        for(SoftReference<byte[]> reference:list){
            System.out.println(reference.get());
        }
    }
}
复制代码




测试

[B@1b6d35861[B@4554617c2[B@74a144823[GC (Allocation Failure) [PSYoungGen: 1866K->488K(6144K)] 14154K->12964K(19968K), 0.0010895 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d4[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17172K->17180K(19968K), 0.0011250 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4696K->4564K(6144K)] [ParOldGen: 12484K->12458K(13824K)] 17180K->17022K(19968K), [Metaspace: 3228K->3228K(1056768K)], 0.0055610 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4564K->4564K(6144K)] 17022K->17030K(19968K), 0.0009186 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4564K->0K(6144K)] [ParOldGen: 12466K->620K(8704K)] 17030K->620K(14848K), [Metaspace: 3228K->3228K(1056768K)], 0.0065061 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
[B@677327b65循环结束[B@677327b6HeapPSYoungGen      total 6144K, used 4546K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)eden space 5632K, 80% used [0x00000000ff980000,0x00000000ffdf0918,0x00000000fff00000)from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)ParOldGen       total 8704K, used 620K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)object space 8704K, 7% used [0x00000000fec00000,0x00000000fec9b160,0x00000000ff480000)Metaspace       used 3237K, capacity 4500K, committed 4864K, reserved 1056768Kclass space    used 351K, capacity 388K, committed 512K, reserved 1048576K复制代码



弱引用


如果某个对象与弱引用关联,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象


image.png


案例分析


public class Test2 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> reference = new WeakReference<>(new byte[_4MB]);
            list.add(reference);
            for (WeakReference<byte[]> w : list) {
                System.out.println(w.get());
            }
            System.out.println();
        }
        System.out.println("循环结束:" + list.size());
    }
}
复制代码



测试

[B@1b6d3586
[B@1b6d3586
[B@4554617c
[B@1b6d3586
[B@4554617c
[B@74a14482
[GC (Allocation Failure) [PSYoungGen: 1865K->488K(6144K)] 14153K->12992K(19968K), 0.0014680 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
[B@1540e19d
[GC (Allocation Failure) [PSYoungGen: 4809K->496K(6144K)] 17313K->13044K(19968K), 0.0013554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
null
[B@677327b6
[GC (Allocation Failure) [PSYoungGen: 4702K->472K(6144K)] 17250K->13020K(19968K), 0.0017348 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
null
null
[B@14ae5a5
[GC (Allocation Failure) [PSYoungGen: 4678K->488K(6144K)] 17226K->13036K(19968K), 0.0016909 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
null
null
null
[B@7f31245a
[GC (Allocation Failure) [PSYoungGen: 4694K->488K(6144K)] 17242K->13044K(19968K), 0.0008993 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
null
null
null
null
[B@6d6f6e28
[GC (Allocation Failure) [PSYoungGen: 4694K->472K(5120K)] 17250K->13028K(18944K), 0.0011246 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1b6d3586
[B@4554617c
[B@74a14482
null
null
null
null
null
[B@135fbaa4
[GC (Allocation Failure) [PSYoungGen: 4657K->32K(5632K)] 17214K->13012K(19456K), 0.0012595 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(5632K)] [ParOldGen: 12980K->638K(7680K)] 13012K->638K(13312K), [Metaspace: 3232K->3232K(1056768K)], 0.0084781 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
null
null
null
null
null
null
null
null
null
[B@45ee12a7
循环结束:10
Heap
PSYoungGen      total 5632K, used 4371K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 4608K, 94% used [0x00000000ff980000,0x00000000ffdc4e70,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen       total 7680K, used 638K [0x00000000fec00000, 0x00000000ff380000, 0x00000000ff980000)
object space 7680K, 8% used [0x00000000fec00000,0x00000000fec9fa38,0x00000000ff380000)
Metaspace       used 3239K, capacity 4500K, committed 4864K, reserved 1056768K
class space    used 351K, capacity 388K, committed 512K, reserved 1048576K
复制代码





虚引用


创建的时候会关联一个引用队列


例如


创建ByteBuffer的时候会创建一个名为Cleaner的虚引用对象当ByteBuffer没有被强引用所引用就会被jvm垃圾回收,虚引用Cleaner就会进入引用队列,会有专门的线程扫描引用队列,被发现后会调用直接内存地址的方法将直接内存释放掉,保证直接内存不会导致内存泄漏


应用场景


可以用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾回收器回收之前会收到一条系统通知。



实例


Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker,
   ReferenceQueue<? super Object> queue) {
    //marker是具体的虚引用对象
    super(marker, queue);
    this.path = path;
    this.deleteStrategy = deleteStrategy == null ?    
    FileDeleteStrategy.NORMAL : deleteStrategy;
}
复制代码



结论


虚引用是所有引用类型中最弱的,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例,一个持有虚引用的对象,和没有引用几乎是一样的,随时可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。




终结器引用


创建的时候会关联一个引用队列,当A4对象没有被强引用所引用时,A4被垃圾回收的时候,会将终结器引用放入到一个引用队列(被引用对象暂时还没有被垃圾回收),有专门的线程(优先级较低,可能会造成对象迟迟不被回收)扫描引用队列并调用finallize()方法,判断是否仍可实现可达性机制,真正执行GC的时候才会回收掉被引用对象




总结


  • 强引用:从来不会被回收


  • 软引用:当内存不足时会被回收


  • 弱引用:正常垃圾回收时回收


  • 虚引用:任何时刻都会被垃圾回收器回收





相关文章
|
16天前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
33 5
|
6天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
6天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
8天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
15天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
19天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
39 17
|
14天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
31 2
|
17天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
18天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
14天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
29 1