JVM中的内存管理
摘要:本模块分为四个部分讲解,分别为(1)内存空间;(2)内存分配;(3)内存回收【重点】;(4)内存状况分析。
内存空间:
内存空间是指我们在运行Java程序时,Java源文件通过编译器产生字节码文件在JVM中的运行与存储情况。
内存空间从线程是否共享分为:
线程私有:
1)程序计数器
- 一块较小的空间,是当前线程所执行的字节码行号指示器,每条线程都有自己的程序计数器,因此它也被称为“线程私有”内存。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
2)虚拟机栈
- 是描述Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表:一片连续的内存空间,用来存放方法参数,以及方法内定义的局部变量,存放着编译期间已知的数据类型(八大基本类型和对象引用类型(reference类型),returnAddress类型)
reference类型:与基本类型不同的是它不等同本身,即使是String,内部也是char数组组成。它可能是指向对象的一个起始位置指针,也可能指向一个代表对象的句柄或其他与该对象有关的位置。
returnAddress类型:
- 栈帧:是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接、方法返回值和异常分派。栈帧随着方法的调用而创建随着方法结束而销毁——无论方法是正常完成还是(抛出了在方法内未被捕获的异常)都算作方法结束。

局部变量表所需的内存空间在编译期完成分配。
Java虚拟机栈可能出现两种异常:
1、线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
2、虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
3)本地方法栈
- 本地方法区和Java Stack作用相似,区别是虚拟机栈为执行Java方法服务,区别是本地方法栈则为Native方法服务,如果一个VM实现使用C-linkage模型来支持Native调用,那么栈将会是一个C栈,但HopSpot VM直接就将本地方法栈和虚拟机栈合二为一。
线程共享:
4)方法区/永久代
- 我们常说的永久代(元空间),用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpotVM 把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样HopSpot的垃圾收集器就可以像管理Java堆一样管理这一部分内存,而不必为方法区专门开发内存管理器。
- 运行时常量池是方法区的一部分。class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容在类加载后存放到方法区的运行常量池中。
5)类实例区(Java堆)——运行时数据区
- 是被线程共享的一块区域,创建的对象和数组都保存在Java堆内存中,也是垃圾收集的最重要的内存区域。由于现代VM采用分代收集算法,因此Java堆从GC角度还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年区。
直接内存:不受JVM GC管理
内存溢出
一、堆栈溢出
出现关键字:java.lang.OutOfMemoryError:....java heap sapce.....(堆栈溢出)
如果代码没问题,适当调整-Xmx和-Xms是可以避免的,不过一定是代码没问题,溢出的原因要么代码有问题,要么访问量太多并且每个访问的时间太长或者数据太多,导致数据释放不掉
引用使用不当导致、申请大对象导致
二、PermGen溢出
出现关键字:java.lang.OutOfMemoryError:PermGen sapce
原因:系统的代码非常多或引用的第三方的包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法导致常量池膨胀,解决方式:增加-XX:Permsize和-XX:MaxPermsize的大小。
三、在使用ByteBuffer中的allocateDirect()的时候会使用到,很多javaNIO的框架中被封装成其他的方法
出现关键字:java.langOutOfMemoryError:Direct buffer memory
在直接或间接使用ByteBuffer中的allocateDirect()方法的时候,而不做clear的时候就会出现类似问题。原因:不详,待深入了解
解决方式设置参数-XX:MaxDirectMemorySize
四、
出现关键字:java.lang.OutOfMemoryError:unable create new native thread
这种错误说明线程的内存空间不足,但是线程基本只占用heap以外的内存区域,也就是说这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,要么内存本身就不够,要么是heap设置得太大了,导致剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了。
五、
出现关键字:java.lang.StackOverflowError
这个错误说明-Xss太小了,我们申请很多局部调用的栈帧等内容是存放在用户当前所持有的。
六、
出现关键字:java.lang.OutOfMemoryError:request {} byte for {} out of swap
这类错误一般是由于地址空间不够而导致的。
内存泄漏
原因:当一个对象已经不需要再使用本该被回收时,另一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
它是造成OOM(内存溢出)的一个重要原因。
常见的内存泄漏及解决方法
1、静态集合类引起的内存泄漏,像HashMap、Vector等的使用最容易出现内存泄漏,这些静态变量的生命周期和应用程序一致,他们所引用的Object也不能释放,因为他们也将一直被vector等引用着。
Static Vector v = new Vector(10); for(int i=0;i<100;i++) { Object o = new Object(); v.add(o); o=null; }
在这个例子中,循环申请了Object对象,并将所申请的对象放入一个Vector中,如果仅仅释放引用本身(o=null),那么Vector仍然引用该对象,所以这个对象对于GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法是将Vector设置为null.
2、监听器
在java编程中,我们调用一个控件addXXXListener()等方法来增加监听器,但往往在注释对象的时候却没有记得去删除这些监听器,从而增加内存泄漏的机会。
3、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式调用其close()方法将其关闭,否则是不会自动被GC回收的。对于Result和Statement对象可以不进行显式回收,但Connection一定要显式回收,因为Connection在任何时候都无法自动回收,而一旦Connection一旦被回收,resultset和statement对象就会被立即为NULL。但如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭resultset statement对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的statement对象无法释放,从而引起内存泄漏。这种情况一般
2、非静态内部类创建静态实例造成的内存泄漏
3、handler造成的内存泄漏
4、线程造成的内训泄漏
5、资源为关闭造成的泄露
6、使用ListView时造成的内存泄漏
7、集合容器中的内存泄漏
8、WebView造成的泄露

浙公网安备 33010602011771号