1.jvm内存区域分类
jvm在执行java程序时会把它管理的内存区域划分为若干个不同的数据区域,包括虚拟机栈、方法区、本地方法栈、堆程序计数器。
1.1 程序计数器
当前线程执行的字节码的行号指示器,线程私有的,占用空间小,无法干涉。
1.2 虚拟机栈
线程运行时,会将方法打包成一个栈针放入虚拟机栈。栈针包括局部变量表、操作数栈、动态连接、方法返回地址等。方法的执行就对应栈针的入栈和出栈,当前线程正在运行的方法就是栈顶的栈针,虚拟机栈是线程私有的。栈桢大小缺省为1M,可用参数 –Xss调整大小。
(1)局部变量表
局部变量表存放方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位。
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法(非static的方法),那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用。
为了节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。不过,这样的设计除了节省栈帧空间以外,还会伴随一些额外的副作用,例如,在某些情况下,Slot的复用会直接影响到系统的垃圾收集行为。
public static void main(String[]args)(){
{
byte[] placeholder=new byte[64*1024*1024];
}
//下面这个加上placeholder会被回收,不加则不会
int a=0;
System.gc();
}
这是因为如果不加a=0的代码, placeholder 虽然离开了作用域,但之后没有任何局部变量对其进行读写,也就是说其占用的 Slot 没有被复用,也就是说 placeholder 占用的内存仍然有引用指向它,因而它没有被回收。而加上后中的变量a由于复用了 placeholder 的 Slot ,导致 placeholder 引用被删除,因此占用的内存空间被回收。
(2)操作数栈
它是一个后入先出(Last In First Out,LIFO)栈。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。
(3)动态连接
每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
Class 文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
(4)方法返回地址
在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
1.3 本地方法栈
本地方法栈中保存的是native方法的信息。当现场调用native方法时候,不再为其创建栈帧。只是简单的动态连接,调用native方法。线程私有。
1.4 堆
几乎所有对象都分配在堆中,垃圾回收也主要发生在这里。可用以下参数调整:
-Xms:堆的最小值;-Xmx:堆的最大值;-Xmn:新生代的大小;-XX:NewSize;新生代最小值;-XX:MaxNewSize:新生代最大值;线程共享。
1.5 方法区
用于存储已经被JVM加载的类信息、常量、静态变量等数据。可用以下参数调整:jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本机总内存的限制
常说的运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。线程共享。
1.6直接内存
直接内存不是jvm运行时数据区的一部分,也不是jvm规范中定义的内存区。如果使用了NO,这块就会频繁使用,可以用directByteBuffer来操作。这块内存不受堆内存大小的限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。
元空间和方法区的作用相似,那为什么要把方法区(也就是常说的永久代)进行隔离。因为永久代存放方法信息、类信息、常量等,很容易发生内容溢出。隔离可以避免永久代引发的FullGC和OOM问题。1.8后元空间使用的是本地内存,不再受堆内存大小限制。