一、虚拟机内存分布
1、程序计数器,当前线程执行字节码行号指示器,线程独占
2、java虚拟机栈,为虚拟机执行java方法(字节码)服务,一次方法调用对应一个栈桢,栈桢中包含局部变量表,存储基本类型和引用
3、本地方法栈,为执行native方法服务
4、java堆,线程共享,存放几乎所有实例和数组,垃圾收集器管理的主要区域
5、方法区(包含运行时常量池),线程共享,存放已加载类信息,常量,静态变量、即时编译器编译代码
6、直接内存,非jvm运行时数据区,也非jvm规范定义内存区,通过NIO操作
二、创建对象
1、类加载检查,new指令首先定位常量池类符号引用,并检查类是否被加载、解析及初始化,没有则执行类加载
2、分配内存,分配大小在内加载时确定,分配方式有指针碰撞(Bump the pointer)和空闲列表(Free List),取决于GC是否有压缩整理功能
3、本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),每个线程预先分配的内存,通过参数可设置
4、初始化零值,使实例对象可直接使用
5、对象必要设置(所属类、如何查找元数据、哈希码、GC分代信息等),存于对象头中(Object Header)
6、执行,初始化父类子类的变量、执行父类子类语句块、构造函数(类加载时已执行,处理静态变量和静态语句块)
三、对象内存布局
1、Header(对象头)、Instance Data(实例数据)和Padding(对齐填充)
2、Header包含Mark Word和指针类型,如果对象是数组还将有一块记录数组长度的信息
3、Mark Word 存储运行时数据,包括哈希码、GC分代年龄、锁状态、线程持有锁、偏向线程ID、偏向时间戳等
4、指针类型即指向对应类元数据的指针
5、Instance Data存储类定义的字段内容
6、Padding作为占位符存在,保证对象长度的8字节的整数倍
四、对象定位访问
1、对象定位访问有句柄和直接指针两种
2、句柄需要在堆中划一块句柄池,句柄中包括对象实例数据和类型数据的地址,引用指向的是句柄地址
3、句柄方式方便对象移动(垃圾收集时很频繁,只需移动句柄实例数据指针)
4、直接指针引用指向的是对象地址,对象内存中必须包含类型数据相关信息
5、直接指针好处是访问速度快(HotSpot 使用的是直接指针)
五、内存泄露溢出
1、程序计数器,唯一无内存溢出的区域
2、java虚拟机栈深度超长抛StackOverflowError(无限递归调用),虚拟机栈动态扩展超长抛OutOfMemoryError(很难实现)
3、本地方法栈同虚拟机栈一样,也会抛出StackOverflowError和OutOfMemoryError
4、java堆没有内存空间分配实例且无法扩展时,抛OutOfMemoryError(不断创建对象并插入List)
5、方法区动态生成大量类信息(cglib创建),无法满足内存分配时,抛OutOfMemoryError
6、jdk1.6字符常量保存于常量池,jdk1.7常量池只保存字符常量对应的引用,字符常量保存在堆中,所以字面量塞满常量池(String.intern)抛OutOfMemoryError,1.6分别是方法区溢出,1.7是堆溢出
5、直接内存不受java堆大小限制,受本机总内存限制,动态扩展失败抛OutOfMemoryError(反射Unsafe类分配内存或者NIO模拟)
内存溢出模拟代码
public static class OOMObject {}
/**
* VM args -Xmx20M -Xms20M
*/
private static void doHeapOOM() {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
static int length = 0;
/**
* VM args -Xss128k
*/
private static void doStackSOF() {
try {
stackLeak();
} catch (Exception e) {
System.out.println("stack length:" + length);
throw e;
}
}
private static void stackLeak() {
length++;
stackLeak();
}
/**
* VM args -XX:PermSize=10M -XX:MaxPermSize=10M
*/
private static void doMethodAreaOOM() {
while(true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
enhancer.create();
}
}
/**
* jdk1.6 VM args -XX:PermSize=10M -XX:MaxPermSize=10M
* jdk1.7 VM args -Xmx20M -Xms20M
*/
private static void doRuntimeConstantPoolOOM() {
int i = 0;
List<String> list = new ArrayList<String>();
while(true) {
list.add(String.valueOf(i++).intern());
}
}
/**
* VM args VM args -Xmx20M -XX:MaxDirectMemorySize=10M
*/
private static void doDirectMemoryOOM() throws IllegalArgumentException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while(true) {
unsafe.allocateMemory(1024*1024);
}
}