JVM运行时数据区图示:
- 根据《Java虚拟机规范(JavaSE 7)》的规范,JVM所管理的内存包含如下几个运行时数据区域:
各区域介绍:
- 堆结构:
堆被划分为新生代和老年代。新生代又被细分为eden和from survivor和to survivor,参数-XX:SurvivorRatio是新生代各分区的划分比值,默认为8.代表:eden:survivor=8:1。
大多数情况下,对象都是在eden中分配,当eden中没有连续的内存空间来存放待分配的对象时,jvm就会触发Minor GC,采用复制算法回收不可达的对象,每次回收时都会将eden,和一块survivor中的不能被回收的对象复制到另一块survivor中。
新生代的对象每经过一次Minor GC后,对象的年龄都会加1,当超过-XX:MaxTenuringThreshold设置的值后,该对象会被放入老年代中,默认为15。
也可以设置-XX:PretenureSizeThreshold参数,令堆再分配大于该值的对象的内存空间时,该对象直接在老年代中分配。 - 方法区:
方法区与堆一样,是各个线程共享的内存区域。在JDK1.8之前方法区由永久代实现,用于存储已被虚拟机加载的类信息、常量、静态变量及编译器编译后的代码等数据。1.8之后方法区由MetaSpace实现,且静态变量及字符串常量池也从方法区移到堆中了。
永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。而MetaSpace使用的是本地内存,理论上取决于系统可虚拟的大小。
Metaspace参数如下:
- MetaspaceSize:初始化元空间的大小,控制元空间GC发生的阈值,动态增加MetaspaceSize的大小。
- MaxMetaspaceSize:元空间增长的最大值。
- MinMetaspaceFreeRatio,元空间GC后会计算Metaspace的空闲空间占比,要是空闲占比小于该值,就增加Metaspace。默认值为40%。该值可控制Metaspace的增长频率,太小,会导致Metaspace增长慢,Metaspace的使用趋于饱和,影响之后类的加载,太大Metaspace增长太空,浪费内存;
- MaxMetaspaceFreeRatio:当进行Metaspace后,计算的空闲占比要是大于该参数虚拟机会释放Metaspac的部分空间,默认值是70%;
- MaxMetaspaceExpansion:Metaspace的最大增幅,本机默认5452592B;
- Metaspace:Metaspace的最肖增幅,本机默认340784B;
- 虚拟机栈:
- 线程私有,是JAVA方法执行的内存模型,每个运行的线程都会在栈中创建一个栈帧,用来存储局部变 量表、操作数栈、动态链接,方法出口等信息。
- 方法的执行,即是在虚拟机栈中的入栈出栈。
- 动态链接:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。 - 局部变量表中,存放了编译期可知的基本类型和对象应用,64位操作系统中,long和double占用两个局部变量空间(slot);
- 操作数栈:
- 最大深度由编译期确定。栈帧刚建立使,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。
- 操作数栈可以存放一个jvm中定义的任意数据类型的值。
- 在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度。
- 运行方法,方法进入栈时,所需要在栈中分配多大的局部变量中间,是在编译期就决定好了的,在运行期间不会改变局部变量大小。
- 程序计数器:
- 一块较小的内存空间,是唯一一块不会出现OM的内存空间;
- 记录方法执行的时,虚拟机字节码指令地址。
- 本地方法栈:执行Navite的内存空间。
参考《深入理解JAVA虚拟机》