JVM运行时内存区域
jvm在运行java程序时,会把它管理的内存划分为五个模块
程序计数器
程序计数器占JVM内存很小的一部分,主要记录jvm执行当前线程字节码文件的行号指示器,字节码解释器会根据计数器的值执行对应行号的字节码。在多线程的时候,JVM是采用线程轮流切换并分配处理器时间的方式来实现的,那么为了切换后线程能找到正确的执行位置,所以每个线程都需要一个程序计数器。所以程序计数器是每个线程私有的。
Java虚拟机栈
java虚拟机栈线程私有,描述的是JAVA方法执行的内存模型,所以他与线程的生命周期相同。那么在每个线程执行调用某一个方法的时候都会创建一个栈帧,同来存储局部变量表等等,线程每调用一个方法,从执行到结束,对应着栈帧从Java虚拟机栈入栈到出栈的过程。
局部变量表存放编译器可知的基本数据类型和对象的引用类型(地址而不是对象本身),所以局部变量表的内存在编译时分配内存
long和double类型的数据占用两个局部变量表,其他占一个。
本地方法栈
与Java虚拟机栈不同的地方在于,它是为JVM中的native方法服务的,有的虚拟机会把这两个栈合二为一。
Java堆
堆是jvm管理的最大的内存的区域,是所有线程共享的区域,在虚拟机启动时创建。用来存放对象实例。堆是垃圾回收器管理的主要区域,所以又称为gc堆。
为了防止多线程情况下,给对象分配内存时,出现问题,线程共享的java堆可能划分多个线程私有的分配缓存区。
方法区
所有线程共享,存储被虚拟机加载的类信息,常量,静态变量还有即时编译后的代码等数据
运行时常量池
它属于方法区的一部分,用于存放编译期间生成的各种字面量和符号引用。之所以叫运行时常量,就是它具备动态性,运行期间也可以将新的常量放入池中
直接内存
不属于jvm运行时数据区一部分,在jdk1.4之后引入一种基于通道与缓冲区的I/O方式。可以使用native的方法分配堆外内存,然后通过DirectByteBuffer对象作为这块内存的引用进行操作。
OutOfMemoryError异常(OOM)
上述所有的内存区域都会发生OOM异常,那么什么样的代码或者操作会导致不同区域的OOM异常呢,以及我么该怎么处理?
java堆溢出
堆是存放对象实例的,那么当对象数据达到最大堆的容量,且没有回收,就会内存溢出异常。
为了实验,设置eclipse的堆内存大小,-Xms堆内存的最小值,-Xmx堆内存的最大值,相等时说明不支持自动扩展
-XX:+HeapDumpOnOutOfMemoryError 让JVM出现内存溢出异常时Dump出详细信息便于分析
package xidian.lili.OOMtest;
import java.util.ArrayList;
import java.util.List;
/**
* -verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
*
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> lists=new ArrayList<OOMObject>();
while(true){
lists.add(new OOMObject());
}
}
}
结果:
那么堆内存异常该怎么解决呢?
(1)首先分析是内存溢出还是泄露
(2)内存泄露的话用工具找到泄露对象到GCroot的引用链,定位泄露代码的位置
(3)溢出的话,就检查-Xms,-Xmx参数设置,看可不可以增大Xmx,然后从代码上检查是否有些对象的生命周期过长,持有状态时间太长
虚拟机栈和本地方法栈溢出
栈是线程私有的,每个方法有自己的栈帧。
-Xoss设置本地方法栈的大小
-Xss设置虚拟机栈的大小
在jvm栈中有两种异常
StackOverflowError:当一个线程申请的请求的栈深度大于JVM所允许的最大栈深度
OutOfMemoryError:当运行时动态扩展栈失败,即没有足够的空间
但是以下两个实验均是StackOverflowError,单线程操作
(1)用-Xss设置一个很小的栈内存 -Xss128k,抛出StackOverflowError异常
(2)定义大量本地变量,增大该方法的栈帧的本地变量表的长度,(这里以为会是动态扩展栈容量)结果抛出StackOverflowError异常
所以结论:在单线程的情况下,不论是栈帧太大还是虚拟机栈内存太小,内存无法分配时,都会抛出StackOverflowError异常
那么在多线程的情况下,产生OutOfMemoryError,也和虚拟机栈内存大小没有多大关系
原因:在机器中分配给一个进程的内存大小是固定的,程序计数器及其他内存消耗忽略不计
那么-Xss=进程的固定大小减去-Xmx-再减去-XX:MaxPermSize
可以抽象的理解为-Xss=栈帧的大小*线程数,-Xss一定,线程数越多,能分配的栈帧就会越小,就需要动态扩展栈,但是空间也不够了,就会抛出OutOfMemoryError异常
那么虚拟机栈异常该怎么解决呢?
(1)减少-Xmx
(2)减少进程数
方法区和运行时常量池溢出
-XX: PermSize -XX:MaxPermSize限制方法区的大小
主要以String.intern()作为测试方法,这个方法是一个Native方法,在jdk1.7对该方法进行改进
jdk1.7之前:
如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,则复制这个字符串常量到常量池,并返回在常量池的引用
jdk1.7:
如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,并返回该字符串对象在堆中的引用
做了如下实验,我的jdk是1.7,如果是1.7以下就会输出false
方法区的溢出也是很常见的溢出,我们知道方法区存放的是Class相关信息,类名,修饰符,常量池,字段描述,方法描述等,但是一个类并不容易被垃圾回收器回收
本机直接内存溢出
可以用-XX MaxDirectMemorySize来指定直接内存的大小,如果不指定,与-Xmx一样大小。
当直接内存发生OOM异常,它不会直接向操作系统申请分配内存,而是通过计算知道无法分配后就手动抛出异常,所以在Heap Dump中不会看出明显的异常,所以如果OOM之后发现dump很小,程序又使用的NIO,就可以检查是不是直接内存溢出。