美文网首页JavaJava 程序员
java多线程——ThreadLocal内存泄露问题

java多线程——ThreadLocal内存泄露问题

作者: 程序花生 | 来源:发表于2022-05-25 17:30 被阅读0次

    1、背景

    ThreadLocal是线程内部的数据存储类,该存储类存储的对象只能在指定线程内部才能使用,其他线程无法获取

    2、依赖的基础——常见的引用类型

    JDK 1.2版本开始,对象的引用被划分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用软引用弱引用虚引用

    Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

    引用类型 被垃圾回收时间 用途 生存时间
    强引用 从来不会 对象的一般状态 JVM停止运行时终止
    软引用 当内存不足时 对象缓存 内存不足时终止
    弱引用 正常垃圾回收时 对象缓存 垃圾回收后终止
    虚引用 正常垃圾回收时 跟踪对象的垃圾回收 垃圾回收后终止

    2.1 强引用(StrongReference)

    强引用:某个对象没有指向它了,该对象就会被回收。否则,它永远不会被回收,直到OOM(OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”)

    • 测试代码
    public class StrongReference {
    
        public static class Strong{
            @Override
            protected void finalize() throws Throwable {
                //Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
                System.out.println("finalize ....");
            }
        }
    
    
        public static void main(String[] args) throws IOException {
            Strong strong = new Strong();
            // 此时没有指向strong对象,则会被垃圾回收机制回收掉
    //        strong = null;
            System.out.println("s=" + strong);
            System.gc();
            System.in.read();// 阻塞main线程,给垃圾回收线程时间执行
        }
    }
    
    • 结果
    s=null
    finalize ....
    s=com.ysl.domain.StrongReference$Strong@2e817b38
    

    2.2 软引用(SoftReference)

    软引用:空间够的时候任意分配,空间不够的时候软引用会被回收。

    为了测试方便设置JVM启动参数-Xms20M -Xmx20M

    • 测试代码
    public class SoftRef {
        public static void main(String[] args) throws InterruptedException {
            SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
            System.out.println(softReference.get());
            Thread.sleep(500);
            System.out.println(softReference.get());
            // 若heap空间不够,会先引用软引用回收掉
            // 假如是强引用的话,会OOM
            byte[] b = new byte[1024 * 1024 * 10];
            System.out.println(softReference.get());
        }
    }
    
    • 结果
    [B@2e817b38
    [B@2e817b38
    null
    

    2.3 弱引用(WeakReference)

    弱引用:每次进行GC时会回收弱引用

    弱引用软引用的区别在于:只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。

    /**
     * @date: 2022/5/24
     * @FileName: WeakRef
     * @author: Yan
     * @Des:
     */
    public class WeakRef {
        public static class MyObject{
            @Override
            protected void finalize() throws Throwable {
                System.out.println("finalizing myObject");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            WeakReference<MyObject> wr = new WeakReference<>(new MyObject());
            System.out.println(wr.get());
            System.gc();
            Thread.sleep(1000);
            System.out.println(wr.get());
        }
    }
    
    com.ysl.threadlocal.entity.WeakRef$MyObject@8efb846
    finalizing myObject
    null
    

    2.4 虚引用(PhantomReference)

    1. 虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用不会决定对象的生命周期
    2. 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    3. 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

    为了测试方便设置JVM启动参数-Xms5M -Xmx5M

    • 测试代码

      /**
       * @date: 2022/5/24
       * @FileName: PhantomRef
       * @author: Yan
       * @Des:
       */
      public class PhantomRef {
          private static final List<byte[]> LIST = new ArrayList<>();
      
          private static final ReferenceQueue<Person> QUEUE = new ReferenceQueue<>();
      
          public static void main(String[] args) {
              PhantomReference<Person> personPhantomReference = new PhantomReference<>(new Person(), QUEUE);
              System.out.println(personPhantomReference.get());
      
              ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
              // 业务线程
              new Thread(() -> {
                  while (true){
                      LIST.add(new byte[1024 * 1024]);
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e){
                          e.printStackTrace();
                      }
                      System.out.println("业务对象的personPhantomReference"+ personPhantomReference.get());
                  }
              }, "BussinessThread").start();
      
              // gc线程
              new Thread(() -> {
                  while (true) {
                      Reference<? extends Person> poll = QUEUE.poll();
                      if (poll != null) {
                          System.out.println("--虚引用对象被jvm回收" + poll);
                      }
                  }
              }, "GCThread").start();
          }
      }
      
    • 结果

      null
      --虚引用对象被jvm回收java.lang.ref.PhantomReference@2d46c162
      业务对象的personPhantomReferencenull
      业务对象的personPhantomReferencenull
      

    3.ThreadLocal的应用场景

    1. 在同一个线程内参数的传递
    2. @Transactional
      在同一个数据库中要进行install和update操作,就要获取数据库连接的connection,而当前线程是在同一个事务中,要保证事务的一致性,则conncetion必须是同一个才能保证事务,这个时候就可以用ThreadLocal,把Connection放在ThreadLocal中来进行存储和传递

    4.内存泄露

    内存泄露分两种

    4.1 ThreadLocal自身的内存泄露问题

    解决方案:ThreadLocal定义成弱引用(在某种程度上解决key不为null导致的内存泄露问题),这样就可以成功被gc回收

    解释:

    正常情况下我们的线程Thread里面会定义一个map,而这个map就是我们的ThreadLocalMap (通过阅读Thread的源码可以看到这一点)

    而这个ThreadLocalMap的key指向的是我们的ThreadLocal对象,而这个ThreadLocal中的set方法,在set的时候会指向一个this,this做为key,这个key指向的正是ThreadLocal

    若把key的指向设置成强引用,然后把tl(ThreadLocal的一个对象实例)这个对象置为null,则此时,key是不会被gc回收的,这种情况下就会导致key的一个内存泄露问题,若设置成弱引用,每次gc的时候都可以被回收到;

    而value值一般都是设置成强引用的,因为value存储的是我们的业务变量,一般情况下,业务变量是不允许其丢失的,所以是要设置为强引用,同样的这也会造成内存的泄露

    4.2线程池下的内存泄露

    这种内存泄漏并不是ThreadLocal tl =null导致,而是因为线程没有被回收,但是我们又不想使用该变量了,进而造成了对象一直存在于ThreadLocalMap中没有被回收,从而导致了内存泄漏。

    解决方案:ThreadLocal使用完成后,及时调用remove方法(主要是解决value强引用导致的内存泄漏问题),将其从ThreadLocaMap中移除,从而避免内存泄漏。

    注意∶

    使用线程池时,线程用完后一定要对ThreadLocalMap (每个线程都有一个ThreadLocalMap) 进行清除操作(ThreadLocalMap map= null,彻底清除,回收整个map),否则后面再拿到该线程的人都可以读到之前的数据,时间长了,ThreadLocalMap也会被填满。

    作者:我是你下药都得不到的男人
    链接:https://juejin.cn/post/7101337995571101726
    来源:稀土掘金

    相关文章

      网友评论

        本文标题:java多线程——ThreadLocal内存泄露问题

        本文链接:https://www.haomeiwen.com/subject/vdtyprtx.html