当前位置: 首页 > news >正文

淄博网站建设报价百度公司电话热线电话

淄博网站建设报价,百度公司电话热线电话,access是不是网页制作工具,西安网站建设雄账号预备知识(引用) Object o new Object(); 这个o,我们可以称之为对象引用,而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下 onull时,只是表示o不再指向堆中object的对象实例,不代表这个…

预备知识(引用)

Object o = new Object();

这个o,我们可以称之为对象引用,而new Object()我们可以称之为在内存中产生了一个对象实例。

当写下 o=null时,只是表示o不再指向堆中object的对象实例,不代表这个对象实例不存在了。

  • 强引用: 就是指在程序代码之中普遍存在的,类似“Object obj=new 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 ThreadLocal<LocalVariable> localVariable= new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {Object o = new Object();/*5*5=25*/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*5=25M,这是很合理的。

当我们启用了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调用set,get,remove都有机会被回收。

​ 比较两种情况,我们可以发现:由于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 ThreadLocal<Number> numberThreadLocal = new ThreadLocal<Number>();/*** 使用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$Number@5658172e,Thread-2=5
内存地址:com.test.thread.ThreadLocalUnsafe$Number@5658172e,Thread-0=5
内存地址:com.test.thread.ThreadLocalUnsafe$Number@5658172e,Thread-4=5
内存地址:com.test.thread.ThreadLocalUnsafe$Number@5658172e,Thread-1=5
内存地址:com.test.thread.ThreadLocalUnsafe$Number@5658172e,Thread-3=5

​ 为什么每个线程都输出5?难道他们没有独自保存自己的Number副本吗?为什么其他线程还是能够修改这个值?仔细考察下我们的代码,我们发现我们的number对象是静态的,所以每个ThreadLoalMap中保存的其实同一个对象的引用,这样的话,当有其他线程对这个引用指向的对象实例做修改时,其实也同时影响了所有的线程持有的对象引用所指向的同一个对象实例。这也就是为什么上面的程序为什么会输出一样的结果:5个线程中保存的是同一Number对象的引用,在线程睡眠的时候,其他线程将num变量进行了修改,而修改的对象Number的实例是同一份,因此它们最终输出的结果是相同的。

而上面的程序要正常的工作,应该去掉number的static 修饰,让每个ThreadLoalMap中使用不同的number对象进行操作。

总结:ThreadLocal只保证线程隔离,不保证线程安全。

http://www.khdw.cn/news/28749.html

相关文章:

  • 开发小程序商城多少钱做网站怎么优化
  • 电视直播网站开发培训班学员培训心得
  • 官方网站建设源码系统山东百度推广代理
  • 手机怎样翻墙上外国网seo运营经理
  • 深圳公司车牌申请要求win10优化大师
  • 网站 代理 备案 费用网上推广怎么做
  • 网站信用认证可以自己做吗网页浏览器
  • 网站浏览器兼容测试互联网推广平台有哪些公司
  • 杭州网站建设设计公司哪家好网站很卡如何优化
  • 卫生局网站建设网络营销怎么做?
  • 惠东做网站最新app推广项目平台
  • 那些网站反爬做的好网络营销电子版教材
  • wordpress add_action do_action北京seo优化哪家好
  • 如何通过axure做网站常州网络推广平台
  • 校园网站建设测试目的seo店铺描述例子
  • 大学一学一做视频网站宁宁网seo
  • 网站策划需要什么百度商家平台
  • 昆山专业网站建设公司哪家好焦作seo公司
  • 大连网站建设培训班免费网站建站平台
  • 科技类网站设计特点如何做网站推广私人
  • 网站的设计方法有哪些桂平网络推广
  • 女孩学建筑学好找工作吗百度seo不正当竞争秒收
  • 宝安自适应网站建设seo 工具
  • 网站建设网络公司最优化方法
  • 湖州市南浔区建设局网站昆山优化外包
  • 东莞百度提升优化百度热搜关键词排名优化
  • 自适应网站内容区做多大合适网络推广员岗位职责
  • 做网站需要什么手续百度指数官网
  • 二手网站建设邀请注册推广赚钱的app
  • 做棋盘游戏辅助的网站seo管理工具