内存泄漏问题产生原因

  长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是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。

ThreadLocal

1. ThreadLocal 中维护了一个内部类ThreadLocalMap<ThreadLocal, Object>,所以必须要有ThreadLocal才能操作ThreadLocalMap变量

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

  每个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。每个线程都单独操作此变量,线程之间没有影响

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函数的正确理解

  

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。

java - 内存泄漏的更多相关文章

  1. java内存泄漏的几种情况

    转载于http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静态 ...

  2. java内存泄漏的定位与分析

    1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测,并发现内存泄漏问题,不然很容易发生down机问题. 编写java程序最为方便的地方就是我们不需要管理内存的分配和释放, ...

  3. java内存泄漏

    java内存泄漏主要分成两个方面: (1)堆中申请的空间没有被释放 (2)对象已不在被使用,但是仍然存在在内存当中 以下集中情况可能会导致内存泄漏 (1)静态集合的使用hashmap和vector,静 ...

  4. Java内存泄漏分析与解决方案

    Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历 ...

  5. (转)java内存泄漏的定位与分析

    转自:http://blog.csdn.net/x_i_y_u_e/article/details/51137492 1.为什么会发生内存泄漏 java 如何检测内在泄漏呢?我们需要一些工具进行检测, ...

  6. Java内存泄漏分析系列之五:常见的Thread Dump日志案例分析

    原文地址:http://www.javatang.com 症状及解决方案 下面列出几种常见的症状即对应的解决方案: CPU占用率很高,响应很慢 按照<Java内存泄漏分析系列之一:使用jstac ...

  7. Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析

    原文地址:http://www.javatang.com 一个典型的thread dump文件主要由一下几个部分组成: 上图将JVM上的线程堆栈信息和线程信息做了详细的拆解. 第一部分:Full th ...

  8. Java内存泄漏分析系列之一:使用jstack定位线程堆栈信息

    原文地址:http://www.javatang.com 前一段时间上线的系统升级之后,出现了严重的高CPU的问题,于是开始了一系列的优化处理之中,现在将这个过程做成一个系列的文章. 基本概念 在对J ...

  9. JRockit检测Tomcat内存溢出JAVA内存泄漏问题

    http://blog.csdn.net/liyanhui1001/article/details/8240473 公司的一个Java应用系统上线以来,基本每1天OutOfMemoryError: P ...

  10. OutOfMemoryError异常java内存泄漏(Memory Leak)和内存溢出(Memory Overflow)

    本篇文章理解源自于<深入理解java虚拟机>2.4章节 实战:OutOfMemoryError异常   在以下例子中,所有代码都可以抛出OutOfMemoryError异常,但是要区分到底 ...

随机推荐

  1. Linux内核中的队列 kfifo【转】

    转自:http://airekans.github.io/c/2015/10/12/linux-kernel-data-structure-kfifo#api 在内核中经常会有需要用到队列来传递数据的 ...

  2. 微信开发,调用js-SDK接口

    微信开发,调用js-SDK接口<!DOCTYPE html><html><head lang="en"> <meta charset=&q ...

  3. C/C++——[02] 运算符和表达式

    C/C++中表示数据运算的符号称为“运算符”.运算符所用到的操作数个数,称为运算符的“目数”. C/C++语言的运算符有赋值运算符.算术运算符.逻辑运算符.位运算符等多类. 将变量.常量等用运算符连接 ...

  4. .Net Core 部署到 CentOS7 64 位系统中的步骤

    建议使用 root 管理员账户操作 1.安装工具 1.apache 2..Net Core(dotnet-sdk-2.0) 3.Supervisor(进程管理工具,目的是服务器一开机就启动服务器 上发 ...

  5. 网页转图片,html生成图片,网页生成图片(基于linnux+phantomjs)和wkhtmltoimage

    安装扩展:  (1)下面是我在linux上的安装过程,如果没有安装git请先yum install git    安装casperjs    cd /    git clone git://githu ...

  6. v4l

    v4l 2011-11-08 11:01:54|  分类: 默认分类|举报|字号 订阅     第一个部分介绍一些v4l的基本概念和基本方法,利用系统API完成一系列函数以方便后续应用程序的开发和使用 ...

  7. 转载:Logistic回归原理及公式推导

    转载自:AriesSurfer 原文见 http://blog.csdn.NET/acdreamers/article/details/27365941 Logistic回归为概率型非线性回归模型,是 ...

  8. #ifndef的用法

    作用:防止头文件的重复包含和编译 定义 #ifndef x #define x ... #endif 这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说 ...

  9. 通过EPROCESS获取进程名

    上一篇写自我保护时用到了,主要是不同版本的位置不同.找了一下,发现XP和win7的情况分别如下. WIN7 lkd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS ...

  10. 【LOJ】 #2132. 「NOI2015」荷马史诗

    题解 k叉哈夫曼树,但是没有了二叉那样的最后一定能合并成一个树根的优秀性质,我们就不断模拟操作看看到了哪一步能用的节点数< k,然后先拿这些节点数合并起来 然后就可以k个k个合并了,大小一样先拿 ...