ThreadLocal
OOM
-
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。看下关于的官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
-
为什么会OOM
- 分配少了,分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
- 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
- 内存泄漏:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
- 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
- 在之前没有垃圾自动回收的日子里,比如C语言和C++语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完后又忘记了释放,比如C++中的new了但是没有delete,那么就可能造成内存泄露。偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出。
- 而在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。
- Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。
-
JVM内存模型:
按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:
程序计数器:当前线程执行的字节码的行号指示器,线程私有
JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。
运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分。
按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。
强软弱虚引用
强引用:指有确切的标识符引用该对象
软引用:指一个对象是间接被引用,当内存足够,不会被gc,内存不够时会被gc
弱引用:当垃圾回收器发现弱引用,即回收
虚引用:当垃圾回收时发现虚引用,不会立即回收,而是放在一个引用队列中,等待处理者去真正回收。主要用于管理直接内存(堆外内存)
ThreadLocal
当前线程只能拿到当前线程的数据
每个Thread对象都包含一个threadLocals的ThreadLocal.ThreadLocalMap类型成员
线程通过set方法其实是通过获取当前线程,再获取当前线程的threadLocals成员,通过存入一对键值对,key:this(当前ThreadLocal对象),value:传入的值
每个Thread对象中的threadLocals可以拥有多个ThreadLocal键值对
ThreadLocal.ThreadLocalMap中的的Entry是继承于WeakReference类,是一个弱引用,每次向map中添加数据就会建立一个与ThreadLocal对象对应的弱引用Entry对象
因为有了弱引用的entry,当我们不需要使用ThreadLocal对象时,直接置为null,当gc发现entry为弱引用立即回收,该做法能防止内存泄漏
此时还是未能完全的防止内存泄露,因为ThreadLocal.ThreadLocalMap里面的value还存在,此时我们应该使用ThreadLocal::remove()方法