1 运行时数据区
1.1 程序计数器
程序计数器是一块较小的内存空间,主要功能是作为当前线程所执行字节码当前行号指示器。当其运行java方法时,指向的是虚拟机字节码指令的地址,当其运行native方法时,则为空。程序计数器是java虚拟机规范中唯一没有OutOfMemoryError情况的区域.
1.2 虚拟机栈
虚拟机栈描述的主要是java方法执行的内存模型,用于存储方法调用时的创建的栈帧,栈帧存储了局部变量表,操作数栈,动态连接,方法出口等信息。
线程请求的栈深度大于虚拟机所允许的深度时,抛出stackoverflowError异常,如果虚拟机可以动态扩展,但扩展时无法申请足够的内存,则会抛出OutOfMemoryError异常。
1.3 本地方法栈
本地方法栈实现的功能和虚拟机栈类似,不过它处理的是native方法。
1.4 堆
堆的主要功能是存放对象实例,几乎所有的数组和对象实例都是在堆中分配.并且它也是垃圾回收的主要区域.如果堆中没有内存来分配实例,并且不能扩展,则会抛出outOfMemoryError
1.5 方法区
方法区用于存放已被虚拟机加载的类信息,常量,静态变量,即时编译编译后的代码(动态代理等)等数据。当方法区无法满足分配需求时,将抛出OutOfMemoyError异常
1.6 直接内存
jdk1.4中新加入了NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样避免了java堆和native堆之间来回复制数据,提高了性能。
2 对象管理
2.1 对象的创建
虚拟机遇到一条new指令时,首先将先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号是否被加载,解析,初始化过。如果没有,则执行类加载过程。在类加载后,就是对对象分配内存,对于java内存规整的情况,使用的是"指针碰撞",而不规整的则采用"空闲列表"的分配方式。
经过上面的操作后,执行<init>将对象按照程序员的意愿进行初始化。
2.2 对象的内存布局
在hotspot虚拟机中,对象的内存中的存储的布局可以分为三个部分对象头 , 实例数据 ,对齐填充。
对象头主要有两部分信息,第一部分为用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。另一部分为类型指针,指向类的元数据信息。对于数组还有一部分为数组长度信息。
实例数据存储的即为各种类型的字段内容。
2.3 对象的访问定位
主要是使用句柄和直接指针两种方式。句柄是java堆中会分出一块内存来作为句柄池,reference存储的是对象的句柄地址,而句柄中存储的是对象数据和类数据地址。直接地址方式是reference直接指向的就对象地址句柄的优势是对象移动不用修改reference指针,只需要修改句柄指针,而直接指针的优势是访问速度更快.
3 OutOfMemoryError异常例子
3.1 堆内存溢出
import java.util.ArrayList;
import java.util.List;
/**
* -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* -Xms设置堆的最小值为20MB
* -Xmx设置堆的最大值为20MB
* -XX:+HeapDumpOnOutOfMemoryError内存溢出时dump堆信息
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>() ;
while(true){
list.add(new OOMObject()) ;
}
}
}
输出:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid6992.hprof ...
Heap dump file created [27980588 bytes in 0.197 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at com.HeapOOM.main(HeapOOM.java:20)
3.2 虚拟机栈和本地方法栈溢出
/**
* -Xss128k
* -Xss设置堆栈内存
*
*/
public class JVMSOF {
private int stackDepth = 1;
public void stackLeak(){
stackDepth++ ;
stackLeak();
}
public static void main(String[] args) {
JVMSOF jvmsof = new JVMSOF() ;
try{
jvmsof.stackLeak();
}catch(Throwable e){
e.printStackTrace();
System.out.println(jvmsof.stackDepth);
}
}
}
结果:
986
Exception in thread "main" java.lang.StackOverflowError
at com.JVMSOF.stackLeak(JVMSOF.java:13)
at com.JVMSOF.stackLeak(JVMSOF.java:14)
at com.JVMSOF.stackLeak(JVMSOF.java:14)
3.3 方法区OOM
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* -XX:PermSize=10M -XX:MaxPermSize=10M
* -XX:PermSize设置方法区的大小
* 这两个参数在java8中忽略,并且下面代码在java8后没有memoryOutOfError异常
*
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
while(true){
Enhancer enhancer = new Enhancer() ;
enhancer.setSuperclass(RCPOOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create() ;
}
}
static class RCPOOMObject{
}
}