java - 内存泄漏

内存泄漏问题产生缘由java

  长生命周期的对象持有短生命周期对象的引用就极可能发生内存泄露,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能建立了一个对象,之后一直再也不使用这个对象,这个对象却一直被引用,即这个对象无用可是却没法被垃圾回收器回收的,这就是java中可能出现内存泄露的状况程序员

        Vector v = new Vector(10);
        for (int i = 1; i < 100; i++) {
            Object o = new Object();
            v.add(o);
            o = null;
        }

  在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,若是仅仅释放引用自己(o=null),那么Vector 仍然引用该对象,因此这个对象对GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。数组

 

ThreadLocalide

1. ThreadLocal 中维护了一个内部类ThreadLocalMap<ThreadLocal, Object>,因此必需要有ThreadLocal才能操做ThreadLocalMap变量函数

2. Thread类中持有一个ThreadLocalMap 的引用。每一个线程中能够由多个ThreadLocal 变量this

  每一个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每一个key都弱引用指向threadlocal. 当把threadlocal实例置为null之后,没有任何强引用指向threadlocal实例,因此threadlocal将会被gc回收. 可是,咱们的value却不能回收,由于存在一条从current thread链接过来的强引用. 只有当前thread结束之后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将所有被GC回收. 
  因此得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了咱们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的状况,这就发生了真正意义上的内存泄露。好比使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

 

开5个线程,每个线程中放入ThreadLocal的变量,初始值为0。每一个线程都单独操做此变量,线程之间没有影响spa

public class ThreadLocalTest {  
        //建立一个Integer型的线程本地变量  
        public static final ThreadLocal<integer> local = new ThreadLocal<integer>() {  
            @Override 
            protected Integer initialValue() {  
                return 0;  
            }  
        };  
        //计数  
        static class Counter implements Runnable{  
            @Override 
            public void run() {  
                //获取当前线程的本地变量,而后累加100次  
                int num = local.get();  
                for (int i = 0; i < 100; i++) {  
                    num++;  
                }  
                //从新设置累加后的本地变量  
                local.set(num);  
                System.out.println(Thread.currentThread().getName() + " : "+ local.get());  
            }  
        }  
        public static void main(String[] args) throws InterruptedException {  
            Thread[] threads = new Thread[5];  
            for (int i = 0; i < 5; i++) {          
                threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");  
                threads[i].start();  
            }   
        }  
    } 

输出:

CounterThread-[2] : 100
CounterThread-[0] : 100
CounterThread-[3] : 100
CounterThread-[1] : 100
CounterThread-[4] : 100线程

 

对initialValue函数的正确理解code

  

public class ThreadLocalMisunderstand {  
   
    static class Index {  
        private int num;   
        public void increase() {  
            num++;  
        }  
        public int getValue() {  
            return num;  
        }  
    }  
    private static Index num=new Index();  
    //建立一个Index型的线程本地变量  
    public static final ThreadLocal<index> local = new ThreadLocal<index>() {  
        @Override 
        protected Index initialValue() {  
            return num;  
        }  
    };  
    //计数  
    static class Counter implements Runnable{  
        @Override 
        public void run() {  
            //获取当前线程的本地变量,而后累加10000次  
            Index num = local.get();  
            for (int i = 0; i < 10000; i++) {  
                num.increase();  
            }  
            //从新设置累加后的本地变量  
            local.set(num);  
            System.out.println(Thread.currentThread().getName() + " : "+ local.get().getValue());  
        }  
    }  
    public static void main(String[] args) throws InterruptedException {  
        Thread[] threads = new Thread[5];  
        for (int i = 0; i < 5; i++) {          
            threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");  
        }   
        for (int i = 0; i < 5; i++) {      
            threads[i].start();  
        }  
    }  
}

输出:
CounterThread-[0] : 12019
CounterThread-[2] : 14548
CounterThread-[1] : 13271
CounterThread-[3] : 34069
CounterThread-[4] : 34069

  如今获得的计数不同了,而且每次运行的结果也不同,说好的线程本地变量呢?对象

  以前提到,咱们经过覆盖initialValue函数来给咱们的ThreadLocal提供初始值,每一个线程都会获取这个初始值的一个 副本。而如今咱们的初始值是一个定义好的一个对象,num是这个对象的引用。换句话说咱们的初始值是一个引用。引用的副本和引用指向的不就是同一个对象吗?
 
  若是咱们想给每个线程都保存一个Index对象应该怎么办呢?那就是建立对象的副本而不是对象引用的副本。
private static ThreadLocal<index> local = new ThreadLocal<index>() {  
    @Override 
    protected Index initialValue() {  
        return new Index(); //注意这里,新建一个对象  
    }  
}

 

 

ThreadLocal源码分析

存储结构

public class ThreadLocal<t> {
......
    static class ThreadLocalMap {//静态内部类
        static class Entry extends WeakReference<threadlocal> {//键值对
            //Entry是ThreadLocal对象的弱引用,this做为键(key)
            /** The value associated with this ThreadLocal. */
            Object value;//ThreadLocal关联的对象,做为值(value),也就是所谓的线程本地变量
 
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        ......
        private Entry[] table;//用数组保存全部Entry,采用线性探测避免冲突
    }
......
}

 

内存泄露与WeakReference

static class Entry extends WeakReference<threadlocal> {
    /** The value associated with this ThreadLocal. */
    Object value;
 
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

  一旦threadLocal的强引用断开,key的内存就能够获得释放。只有当线程结束后,value的内存才释放。

  每一个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap。Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每一个key都弱引用指向threadlocal。当把threadlocal实例置为null之后,没有任何强引用指threadlocal实例,因此threadlocal将会被gc回收。可是,咱们的value却不能回收,由于存在一条从current thread链接过来的强引用。

  只有当前thread结束之后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将所有被GC回收.

  因此得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露。可是value在threadLocal设为null线程结束这段时间不会被回收,就发生了咱们认为的“内存泄露”。

  所以,最要命的是线程对象不被回收的状况,这就发生了真正意义上的内存泄露。好比使用线程池的时候,线程结束是不会销毁的,会再次使用的,就可能出现内存泄露。  
  为了最小化内存泄露的可能性和影响,在ThreadLocal的get,set的时候,遇到key为null的entry就会清除对应的value。

  因此最怕的状况就是,threadLocal对象设null了,开始发生“内存泄露”,而后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又再也不调用get,set方法,或者get,set方法调用时依然没有遇到key为null的entry,那么这个期间就会发生真正的内存泄露。

  使用ThreadLocal须要注意,每次执行完毕后,要使用remove()方法来清空对象,不然 ThreadLocal 存放大对象后,可能会OMM。

相关文章
相关标签/搜索