一、运行时数据区域
当JVM运行之后,操作系统会将自己内存 堆中的一块空间分配给JVM作为运行时存储数据的区域。因为操作系统栈中的区域可能会被操作系统回收。内存分配图如下(以windows系统为例):
1.程序计数器
线程私有的,记录此线程当前所执行的字节码的行号指示。(因为Java具有解释型语言的特征,所以用程序计数器去记录解释到某一行的字节码行号)。
2.Java虚拟机栈
线程私有的,当一个线程启动的时候,JVM会给线程分配一块栈空间(可以动态扩展),用来存储程序计数器和运行方法时候的数据。线程每进入一个方法都会在栈区域中开辟一块栈帧空间(开辟的时候栈空间大小已经确定),用来存储局部变量表、操作数栈、动态链接、方法出入口等信息。方法的栈帧空间随着方法结束,也会被收回。
(1)局部变量表
存储方法中的基本数据类型和引用类型,因为基本数据类型的大小是确定的,并且引用类型存储的都是堆中对象的地址。所以局部变量表所需的空间可以在编译期间完成分配。
(2)操作数栈
虚拟机被称为“基于栈的执行引擎”,这个栈就是操作数栈。在解释执行的时候,对于数据的运算和存储操作都是用操作数栈来执行的。比如遇到字节码指令iadd 就是将虚拟机栈顶的两个元素取出来然后相加并再次存储到栈顶中。 istore_2指令的意思 就是取出栈顶的元素,将它的值存储到局部变量表索引为2的下标处。
当一个线程调用的方法数过多,开辟的栈的次数过大,就会抛出StackOverflowError异常。当一个线程在开辟方法栈帧,动态扩展时候虚拟机的栈空间不够用了,就会抛出OutOfMemoryError异常。
3.本地方法栈
执行本地方法的时候,会在这个栈中实现操作。因为Java方法是字节码形式的,所以需要遵循特定的栈帧规则,而本地方法则可以自由实现。
4.Java堆
线程共享的,一般用来存放所有被实例化出来的对象。也是Java主要的垃圾回收区域。虚拟机所管理的最大的一块内存
5.方法区
线程共有的,用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。在JDK1.7及之前,将以上信息存储在永久代中。但是在JDK1.8之后,将常量和静态变量移入到堆中,然后类的元数据存储到元空间中。而元空间是一块和堆不相连的空间,由本地内存中划分出来,程序员可以使用参数指定元空间的大小。
(1)为什么移除永久代?
由于永久代的在堆中存储,而堆中的空间有限,如果永久代的空间太小,那么它将会频繁发生Full GC(因为它的GC是和老年代绑定的),并且频繁发生内存不够用。而将永久代空间设置扩大之后,那么老年代的空间就会减少。很难平衡二者大小。
(2)为什么引入元空间?
元空间的大小如果不指定将会动态扩展,它的空间一般够用,并且减轻了GC的压力。
6.直接内存
使用操作系统中的内存,通过native函数分配内存。然后通过java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。