江阴规划建设局网站,wordpress广告位代码,广州天河区景点,唐山建设工程信息网站预备知识#xff08;引用#xff09;
Object o new Object();
这个o#xff0c;我们可以称之为对象引用#xff0c;而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下 onull时#xff0c;只是表示o不再指向堆中object的对象实例#xff0c;不代表这个…预备知识引用
Object o new Object();
这个o我们可以称之为对象引用而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下 onull时只是表示o不再指向堆中object的对象实例不代表这个对象实例不存在了。 强引用: 就是指在程序代码之中普遍存在的类似“Object objnew Object”这类的引用只要强引用还存在垃圾收集器永远不会回收掉被引用的对象实例。 软引用: 是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象在系统将要发生内存溢出异常之前将会把这些对象实例列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存才会抛出内存溢出异常。在JDK 1.2之后提供了SoftReference类来实现软引用。 弱引用: 也是用来描述非必需对象的但是它的强度比软引用更弱一些被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时无论当前内存是否足够都会回收掉只被弱引用关联的对象实例。在JDK 1.2之后提供了WeakReference类来实现弱引用。 虚引用: 也称为幽灵引用或者幻影引用它是最弱的一种引用关系。一个对象实例是否有虚引用的存在完全不会对其生存时间构成影响也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知。在之后提供了类来实现虚引用
内存泄漏的现象
/*** 类说明ThreadLocal造成的内存泄漏演示*/
public class ThreadLocalOOM {private static final int TASK_LOOP_SIZE 500;final static ThreadPoolExecutor poolExecutor new ThreadPoolExecutor(5, 5,1,TimeUnit.MINUTES,new LinkedBlockingQueue());static class LocalVariable {private byte[] a new byte[1024*1024*5];/*5M大小的数组*/}final static ThreadLocalLocalVariable localVariable new ThreadLocal();public static void main(String[] args) throws InterruptedException {Object o new Object();/*5*525*/for (int i 0; i TASK_LOOP_SIZE; i) {poolExecutor.execute(new Runnable() {public void run() {//localVariable.set(new LocalVariable());new LocalVariable();System.out.println(use local varaible);//localVariable.remove();}});Thread.sleep(100);}System.out.println(pool execute over);}}
首先只简单的在每个任务中new出一个数组 可以看到内存的实际使用控制在25M左右因为每个任务中会不断new出一个5M的数组5*525M这是很合理的。 当我们启用了ThreadLocal以后 内存占用最高升至150M一般情况下稳定在90M左右那么加入一个ThreadLocal后内存的占用真的会这么多
于是我们加入一行代码 再执行看看内存情况: 可以看见最高峰的内存占用也在25M左右完全和我们不加ThreadLocal表现一样。
这就充分说明确实发生了内存泄漏。
分析
根据我们前面对ThreadLocal的分析我们可以知道每个Thread 维护一个 ThreadLocalMap这个映射表的 key 是 ThreadLocal实例本身value 是真正需要存储的 Object也就是说 ThreadLocal 本身并不存储值它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察ThreadLocalMap这个map是使用 ThreadLocal 的弱引用作为 Key 的弱引用的对象在 GC 时会被回收。
因此使用了ThreadLocal后引用链如图所示 图中的虚线表示弱引用。
这样当把threadlocal变量置为null以后没有任何强引用指向threadlocal实例所以threadlocal将会被gc回收。这样一来ThreadLocalMap中就会出现key为null的Entry就没有办法访问这些key为null的Entry的value如果当前线程再迟迟不结束的话这些key为null的Entry的value就会一直存在一条强引用链
Thread Ref - Thread - ThreaLocalMap - Entry - value而这块value永远不会被访问到了所以存在着内存泄露。
只有当前thread结束以后current thread就不会存在栈中强引用断开Current Thread、Map value将全部被GC回收。最好的做法是不在需要使用ThreadLocal变量后都调用它的remove()方法清除数据。
其实考察ThreadLocal的实现我们可以看见无论是get()、set()在某些时候调用了expungeStaleEntry方法用来清除Entry中Key为null的Value但是这是不及时的也不是每次都会执行的所以一些情况下还是会发生内存泄露。只有remove()方法中显式调用了expungeStaleEntry方法。
从表面上看内存泄漏的根源在于使用了弱引用但是另一个问题也同样值得思考为什么使用弱引用而不是强引用
下面我们分两种情况讨论
key 使用强引用引用ThreadLocal的对象被回收了但是ThreadLocalMap还持有ThreadLocal的强引用如果没有手动删除ThreadLocal的对象实例不会被回收导致Entry内存泄漏。
key 使用弱引用引用的ThreadLocal的对象被回收了由于ThreadLocalMap持有ThreadLocal的弱引用即使没有手动删除ThreadLocal的对象实例也会被回收。value在下一次ThreadLocalMap调用setgetremove都有机会被回收。
比较两种情况我们可以发现由于ThreadLocalMap的生命周期跟Thread一样长如果都没有手动删除对应key都会导致内存泄漏但是使用弱引用可以多一层保障。
因此ThreadLocal内存泄漏的根源是由于ThreadLocalMap的生命周期跟Thread一样长如果没有手动删除对应key就会导致内存泄漏而不是因为弱引用。 为什么ThreadLocalMap的key要设置为弱引用
在 ThreadLocalMap 中的set和get方法中会对 key为null进行判断如果key为null会把value也置为null。 这样就算忘记调用remove方法对应的value在下次调用get、set、remove方法中的任意一个都会被清除从而避免内存泄漏相当于多了一层保障但是如果后续一直不调用这些方法依然存在内存泄漏的风险所以最好是及时remove。 总结
JVM利用设置ThreadLocalMap的Key为弱引用来避免内存泄露。
JVM利用调用remove、get、set方法的时候回收弱引用。
当ThreadLocal存储很多Key为null的Entry的时候而不再去调用remove、get、set方法那么将导致内存泄漏。
使用线程池 ThreadLocal 时要小心因为这种情况下线程是一直在不断的重复运行的从而也就造成了value可能造成累积的情况。 错误使用ThreadLocal导致线程不安全
/*** 非安全的ThreadLocal 演示*/
public class ThreadLocalUnsafe implements Runnable {public static ThreadLocalNumber numberThreadLocal new ThreadLocalNumber();/*** 使用threadLocal的静态变量*/public static Number number new Number(0);public void run() {//每个线程计数加一number.setNum(number.getNum() 1);//将其存储到ThreadLocal中numberThreadLocal.set(number);//延时2mstry {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//输出num值System.out.println(内存地址numberThreadLocal.get() Thread.currentThread().getName() numberThreadLocal.get().getNum());}public static void main(String[] args) {for (int i 0; i 5; i) {new Thread(new ThreadLocalUnsafe()).start();}}/*** 一个私有的类 Number*/private static class Number {public Number(int num) {this.num num;}private int num;public int getNum() {return num;}public void setNum(int num) {this.num num;}}
} 输出
内存地址com.test.thread.ThreadLocalUnsafe$Number5658172eThread-25
内存地址com.test.thread.ThreadLocalUnsafe$Number5658172eThread-05
内存地址com.test.thread.ThreadLocalUnsafe$Number5658172eThread-45
内存地址com.test.thread.ThreadLocalUnsafe$Number5658172eThread-15
内存地址com.test.thread.ThreadLocalUnsafe$Number5658172eThread-35
为什么每个线程都输出5难道他们没有独自保存自己的Number副本吗为什么其他线程还是能够修改这个值仔细考察下我们的代码我们发现我们的number对象是静态的所以每个ThreadLoalMap中保存的其实同一个对象的引用这样的话当有其他线程对这个引用指向的对象实例做修改时其实也同时影响了所有的线程持有的对象引用所指向的同一个对象实例。这也就是为什么上面的程序为什么会输出一样的结果5个线程中保存的是同一Number对象的引用在线程睡眠的时候其他线程将num变量进行了修改而修改的对象Number的实例是同一份因此它们最终输出的结果是相同的。
而上面的程序要正常的工作应该去掉number的static 修饰让每个ThreadLoalMap中使用不同的number对象进行操作。
总结ThreadLocal只保证线程隔离不保证线程安全。