继续参考《深入理解java虚拟机》来回顾下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
      }
    

    java frame 执行顺序

    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使用运行时常量池来检索内存的实际地址。 字符串字面亮和原生常量都在里面。