Java虚拟机运行时数据区
一、程序计数器
每个Java虚拟机线程都有自己的程序计数器,用于存储当前正在执行的虚拟机指令地址。如果当前执行的是native方法,那么程序计数器的值是undefined。
因为Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间来实现的,想要线程切换后能恢复到正确的执行位置,每条线程都必须私有一个程序计数器,来存储当前执行的虚拟机指令地址。
二、Java虚拟机栈
Java虚拟机栈也是每个线程私有的,它存储着栈帧数据。线程执行过程中,每个方法从调用到返回都对应着一个栈帧入栈到出栈的过程。如果程序中使用了递归,但是没有递归出口,或者方法调用超过了所允许的最大深度,那么就会抛出StackOverflowError错误,如果没有超过最大深度,但是没有足够的内存来创建栈帧,那么会抛出OutOfMemoryError错误,这也是Java虚拟机规范中说明的两种错误。这里只是举了两个例子说明,引发错误的可能还有其他方式。
栈帧,栈帧存储数据和部分结果,以及方法返回值和异常分发。每次调用方法的时候都会创建一个新的栈帧,方法退出时销毁(无论异常还是正常返回)。栈帧是线程的虚拟机栈中分配的,每个栈帧都有一个局部变量数组,操作数栈以及对当前类当前方法的常量池的引用。
三、Java堆
堆是Java虚拟机最重要的内存区域,堆是所有线程共享的区域,也是垃圾收集器管理的主要区域。主要目的是存放实例数据和数组的内存。堆中又分为新生代、老年代、永久代(Java8以后就去掉了),新生代有分为Eden空间、s1和s2。分这么多区间是为了更好的管理内存。关于各个区域分配的细节以后另起一篇文章解释。以下参数可以指定各个空间的大小和比例:
-Xms50M,设置最小堆内存为50M
-Xmx512M,设置最大堆内存为512M
-XX:NewSize=128m,设置新生代大小为128M
-XX:MaxNewSize=128M,设置新生代最大大小为128M
-XX:NewRatio=4,设置老年代和新生代的比例为4:1
-XX:SurvivorRatio=4,设置Eden和一个s1空间的比例为4:1,那么Eden:s1:s2是4:1:1
四、方法区(永久代)
方法区是Java堆的一个逻辑部分,它存储类结构、运行时常量池、字段和方法等数据。可以通过参数-XX:PermSize=256m来指定大小。在Java8以后,该区域已经被移除了,替代的是元数据空间Metaspace。在Java8以及更新版本设置PermSize将会提示:ignoring option PermSize=20M; support was removed in 8.0。
五、运行时常量池
运行时常量池是方法区的一部分,它存储从编译已知的字面量和符号引用以及类版本、字段、方法、接口描述等信息。早期版本中,String.intern()会进入常量池存放。Java8及以后,官方的虚拟机规范并没有更改或者详尽的解释。有兴趣的朋友可以留言讨论。
六、本地方法栈
本地方法栈和Java虚拟机栈的作用是相似的,他们的区别是,Java虚拟机栈是为Java方法服务,本地方法栈是为native方法服务。官方虚拟机规范中并没有强制本地方法栈中使用的语言、结构进行限制,不同的虚拟机可能有自己不同的实现。Sun的HotSpot虚拟机貌似是将本地方法栈和Java虚拟机栈合二为一了。
七、Metaspace元空间
参考资料:http://openjdk.java.net/jeps/122,元空间是Java8开始提供,目的是为了取代永久代,元空间中内存不是在堆上进行分配,而是使用本机内存。默认情况下,大小只受机器物理内存限制,可以通过参数-XX:MaxMetaspaceSize设置。永久代被移除后,String.intern()、符号引用,字面量,类静态变量都被移动到了堆空间中。元空间一般存储类的元数据信息,加载类后会存放到元空间中,当类被卸载时,会将内存释放给操作系统。
更多好问,请关注微信公众号