继续参考《深入理解java虚拟机》来回顾下java的内存分配相关的知识(第二章)
线程私有区
pc register (program counter register)/程序计数器
每一个线程都有自己的程序计数器与线程在同时建立,在任何时间,每个java虚拟机线程都只会执行一个方法,叫做当前方法(current method),程序计数器纪录的就是当前方法指令在方法区内的地址。这是因为java虚拟机是多线程的,线程会轮流切换的执行,为了线程切换后能恢复到正确的执行位置,所以每个线程都有程序计数器。
Note: 当java虚拟机执行一个本地方法时,程序计数器值为undefined。该区域有足够的存储空间因此不会有OutOfMemory的情况.
stack/java虚拟机栈
与程序计数器一样,java虚拟机栈也是线程独有的,与线程生命周期相同,栈中包含许多栈桢.当一个方法被调用时就会创建一个桢并放入栈中,当方法调用完成桢回被回收。
桢
帧是一种数据结构,该数据结构包含表示当前方法中线程状态的多种数据。
- Operand Stack/操作数栈:用来为java函数的调用提供参数,获得栈顶方法执行后的返回值。
- Local variable array/本地变量数组: 这个数组中包含原始基本类型,引用和返回地址,数组的大小是编译期动态计算的
-
Run-time constant pool reference/运行时常量引用: 这个空间用来把象征方法的引用转译成真正的内存引用
看下面的例子
public int add(int a, int b){ return a + b; } public void functionA(){ // some code without function call int result = add(2,3); //call to function B // some code without function call }
frameA是代表functonA的栈桢,当开始调用add的时候回新建栈桢frameB并把frameB变成当前桢,FrameB的本地变量数组存储在被弹出的frameA的操作数栈的空间上.当add执行结束后,frameB被回收当前桢又变成frameA,add的返回值放到frameA的操作数栈上。
Note:在虚拟机运行时栈的大小时可以动态扩展的,当递归调用导致栈的深度无法扩展时java会抛出 StackOverflowError.
/**
* VM Args: -Xss256k
*
* stack length:1898
* Exception in thread "main" java.lang.StackOverflowError
* at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
*
*
* Created by xiagn on 16-4-7.
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch(Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
native method stack/本地方法栈
这个栈是java通过jni调用其他语言的栈,栈的行为依赖与低层的操作系统。
共有区
heap/堆
堆是所有java虚拟机线程共有的存储区,他是在虚拟机启动时创建的。所有类的实例和数组的存储空间都分配在堆中。这个区域使用垃圾回收机制来管理,根据一定的回收策略回收内存,不同的jvm回收的策略不同。堆所占的空间也是动态分配的,可以通过参数来配置最大和最小堆内存(java -Xms=512m -Xmx=1024m)
Note: 当内存超出时会抛出OutOfMemoryError异常
异常例:
import java.util.ArrayList;
import java.util.List;
/**
* VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
* java.lang.OutOfMemoryError: Java heap space
* Dumping heap to java_pid70499.hprof ...
* Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
* Heap dump file created [27959428 bytes in 0.244 secs]
*
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true) {
list.add(new OOMObject());
}
}
}
Method area/方法区
方法区时所有java虚拟机线程共享的,他在虚拟机启动时通过classloader创建。这些方法中的数据在内存中直到load他们的loader被释放。
方法区存储: * 类信息(字段和方法的数量,父类,接口名字,版本等) * 方法和构造函数的字节码 * 运行时常量池
Note:需要注意的是在java7以前,oracle hotsopt jvm使用PermGen永久代来存储方法区,PermGen的大小可以通过(java:-XX:PermSize=10M -XX:MaxPermSize=10M)来设置,如果超过最大限制,则会出现 java.lang.OutOfMemoryError: PermGen space 但是从JAVA8开始,hotspot jvm使用一个单独的原生内存空间来保存方法区,它被叫做Metaspace,最大的空间就是系统所有可用内存,可以使用(java: -XX:MaxMetaspaceSize=10m)来设置大小,如果超过了最大限制则会出现java.lang.OutOfMemoryError: Metaspace
异常例:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Java1.8 VM Args: -XX:MaxMetaspaceSize=10m
*
* Java1.7 VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*
*
* Created by xiagn on 16-4-7.
*/
public class JavaMethodAreaOOM {
static class OOMObject { }
public static void main(String[] args) {
while(true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
Runtime constant pool/运行时常量池
运行时常量池是方法区的一部分,当类,方法,字段被使用时,JVM使用运行时常量池来检索内存的实际地址。 字符串字面亮和原生常量都在里面。