Java四种引用以及ThreadLocal内存泄露问题

    我们调用方法会在栈中开辟新的空间,然后在栈或堆中创建对象,当方法调用完毕之后,方法的栈空间被释放,创建的所有对象
也失去了引用,都会被GC回收。那为什么需要有多种引用类型?

一、引入多种引用原因:

①当有些数据对内存敏感,例如缓存(图片、网页等的缓存)。
    我们对这类数据的要求就是就是在内存足够时,希望保留;内存不够时,则进行删除。
   即当内存不足时,JVM 会尽量保留软引用指向的对象,只有在内存不足时才会回收这些对象。
   使用软引用能够有效地避免内存溢出的问题,同时也能够提高程序的性能。
   这个时候就可以使用软引用。
②如果一个对象是偶尔的使用,并且 希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集
   或者想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期。
   这时候就应该用弱引用。
③需要跟踪对象是否已经被垃圾回收,并在对象被回收时执行某些特定的操作。
   设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
   Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
换种解释:
    对于我们来说,在收拾家中杂物时,一定有一些食之无味,弃之可惜的物品;
    JVM收集回收对象时,对于我们来说,也有类似的情况:
        例如某些东西需要保存着,但不是必须的,能保存最好,例如缓存的图片等。
    对于这种情况,如果一直保存着,会占用内存等资源,内存不够需要手动删除;
    违背了JAVA的垃圾自动回收机制。

二、JAVA的四种引用类型:

1、强引用
    当内存不足,JVM开始垃圾回收,对于强引用的对象, 就算是出现了OOM也不会对该对象进行回收,死都不收。
例如:
创建一个对象并把这个对象赋给一个引用变量:        
Object object =new Object();
String str ="hello";
2、软引用:
    软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说:
     当系统内存充足时它不会被回收,
     当系统内存不足时它会被回收。
例如:    
    首先创建一个强引用的MyObject;
    然后将其添加到弱引用对象aSoftRef中;
    最后将强引用给删掉,置为null。
    这样,new出来的MyObject对象就只有一个软引用了。
MyObject aRef = new  MyObject();  
SoftReference aSoftRef=new SoftReference(aRef);  
aRef = null;
3、弱引用:
    弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。
    对于只有弱引用的对象来说, 只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存
例如:    
    先创建一个弱引用了 Integer类型的弱引用对象。
    然后主动调用gc()
    最后发现获取不到改对象了。
WeakReference<Integer>reference=new WeakReference<People>(new Integer(10000));  
System.gc();//通知GVM回收资源  
System.out.println(reference.get());
4、虚引用:
    虚引用需要java.lang.ref.PhantomReference类来实现。
    顾名思义,就是形同虚设,与其他几种引用都不同, 虚引用并不会决定对象的生命周期
     如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收;
    它 不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用
    虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
    PhantomReference的get方法总是返回nul,因此无法访问对应的引用对象。
    其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
   通常情况下,PhantomReference 要与 ReferenceQueue 配合使用,当对象被垃圾回收时,JVM 会将其加入到 ReferenceQueue 中,通过检查 ReferenceQueue 中的引用对象,可以得知对象何时被回收。

三、实现原理

1、SoftReference
    SoftReference 的实现原理与 JVM 的垃圾回收机制(GC)密切相关。
    JVM 的 GC 会根据对象的引用情况来判断对象是否可以被回收。
     当一个对象被 SoftReference 引用时,虚拟机并不会立即将其回收。
     相反,它会等到系统内存不足时才回收被 SoftReference 引用着的对象,从而释放更多的内存。
    在 JVM 的 GC 执行过程中,如果发现一个对象只被 SoftReference 引用着,并且系统内存不足,那么 JVM 就会将该对象标记为“即将回收”,并将该对象添加到回收队列中。当 GC 完成后,虚拟机会回收那些标记为“即将回收”的对象。
2、WeakReference
    WeakReference 的实现原理也与 JVM 的垃圾回收机制(GC)密切相关。
    JVM 的 GC 在回收对象时,会判断该对象是否有强引用指向它。
    如果没有,就将其标记为“可回收”,并添加到回收队列中。当 GC 完成后,虚拟机会回收那些标记为“可回收”的对象。
     当对象只被 WeakReference 引用时,该对象会变成“弱可达”
     即当 GC 发现该对象只被 WeakReference 引用时,就将其标记为“可回收”,并添加到回收队列中。
    由于 WeakReference 引用的对象没有强引用指向它,因此在 GC 的回收过程中,可以被顺利回收。
3、ReferenceQueue
    PhantomReference 与 GC 相关。
    在 Java 中,垃圾回收器负责回收对象,并通过引用队列(ReferenceQueue)通知应用程序对象的回收情况。
    PhantomReference 是一种弱引用类型,它指向的对象已经被 JVM 标记为可回收对象,当对象被垃圾回收器回收时,JVM 会将其加入到引用队列中,供应用程序使用。
    PhantomReference 的主要作用是跟踪对象是否已经被垃圾回收,并在对象被回收时执行某些特定的操作。
    在实际应用中,通常将 PhantomReference 与 ReferenceQueue 配合使用,通过检查 ReferenceQueue 中的引用对象,可以得知对象何时被回收。
    这种机制可以用于一些特殊的应用场景,例如资源释放、对象监视等。

四、ThreadLocal

1、如下图所示,每一个线程都有一个ThreadLocal.ThreadLocalMap类型的对象
    即 ThreadLocal 的内部类ThreadLocalMap
    
2、ThreadLocal.ThreadLocalMap介绍
    左图为ThreadLocalMap类中的方法和属性,其中最重要的就是其也有一个内部类Entry
    右图介绍Entry类:
        可见其继承了WeakReference类,说明这是一个弱引用类型的类;
        其构造方法中,传入两个值, 其中k就是 ThreadLocal类型,且使用了super(k)进行初始化;
            由上面四种引用类型可知,该k为弱引用,随时可能被回收
            value则是保存的值。
    
3、set()方法
    首先获取当前线程,然后调用getMap()方法获取本线程的threadLocals对象(1、中介绍每个线程都有)
    然后判断是否为空,为空则初始化构建一个。
    
createMap():
    注意,这里传递参数是this,即当前threadlocal对象
    
ThreadLocalMap():
    INITIAL_CAPACITY的值初始化为16;
    创建一个16容量的Entry[]对象,然后根据hash值存放到对应的位置
    键为threadlocal对象,值为传入的值
    
    
4、get()方法
    首先获取当前线程对象
    然后根据当前线程对象,获取本线程的threadLocals对象,说明一个线程有多个threadlocal
    然后使用threadLocals(ThreadLocal.ThreadLocalMap)的getEntry()获取其中存储的值,this代表本类,即threadlocal对象
    最后返回值中的value
    
getEntry()方法介绍:
    可见找到之后会直接返回,
    如果找不到就会执行getEntryAfterMiss()方法尝试清理
    
getEntryAfterMiss()方法:
    e就是上面方法中传入的entry,就是一个弱引用
    通过e.get(),也就是Entry.get()获取它的key,也就是ThreadLocal对象,判断该对象是否被回收
    如果找到的k对应的ThreadLocal就返回该Entry
    没有找到,即key为null,说明弱引用已经被回收了 ,那么就要在这里回收里面的value了
        调用expungeStaleEntry()方法删除value
    如果key不是要找的那个,那说明有hash冲突,这里是处理冲突,找下一个entry
    
5、父线程、子线程的threadLocal一致性:
    经过上面分析可知,无论是获取还是添加都是获取当前线程,因此子线程和父线程的threadLocal是不同的。
    如下图:
        在本线程的threadLocal对象中添加String类型的对象"test"
        然后创建子线程,尝试获取父线程的threadLocal对象
        最后父线程输出threadLocal的对象
        
        
6、内存泄露问题
由上面可知:
    虽然ThreadLocalMap中的 key是弱引用,当不存在外部强引用的时候,就会自动被回收;
    但是 Entry中的value依然是强引用
这个value的引用链条如下:
可以看到, 只有当Thread被回收时,这个value才有被回收的机会,否则,只要线程不退出,value总是会存在一个强引用
但是,要求每个Thread都会退出,是一个极其苛刻的要求, 对于线程池来说,大部分线程会一直存在在系统的整个生命周期内,那样的话,就会造成value对象出现泄漏的可能
处理的方法是:
    在ThreadLocalMap进行set(),get(),remove()的时候,都会进行清理。
    
    
  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值