简介:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁
如下介绍各个内存区域的用途以及创建和销毁时间
1、程序计数器
作用:保存当前线程所执行的字节码的行号,即该字节码执行到了哪一行;
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码;
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方法来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存
2、堆
堆是JVM所管理的最大一块内存,用于存放对象和数组,为了更好的实现内存分配和回收,将堆细分为如下结构:
从图可以看出,堆被划分为:
- 新生代和老年代,比例是1:2(具体比例以实际JVM为准),可以通过参数:-XX:NewRatio=3,将比例修改为1:3;
- 新生代又被划分为:Eden、from、to,比例是8:1:1,可以通过参数:-XX:SurvivorRatio=10,将比例修改为10:1:1(Eden在西方指伊甸园,人类最开始生活的地方,此处表示对象最开始存在的地方);
另外再介绍两个设置JVM堆内存大小的命令:
- -Xms:minimum memory size for pile and heap,为堆分配的最小内存(其中m表示memory,s表示size),例如:-Xms100K和-Xms100M;
- -Xmx:maximum memory size for pile and heap,为堆分配的最大内存(其中m表示memory,为了对齐三字符,压缩了其表示形式,采用计算机中约定表示方式:用x表示“大”),例如:-Xmx10M;
下面通过代码和jdk自带的jvisualvm详细进行讲解
public class HeapTest {
byte[] b = new byte[1024 * 100];
public static void main(String[] args) {
List<HeapTest> list = new ArrayList<HeapTest>();
while (true) {
list.add(new HeapTest());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在HeapTest 类中定义了一个大小为100KB的字节数组成员变量,然后在main方法中初始化一个集合,并不停的插入HeapTest 对象,这样可以保证创建的对象不会被回收。然后设置虚拟机参数:-Xms100M -Xmx100M -XX:NewRatio=3 -XX:SurvivorRatio=10,含义在上文已解释,接着打开jvisualvm.exe工具,在jdk的bin目录下。打开后,点击菜单栏中的工具->插件,安装VisualGC,安装成功后,运行上述代码,在jvisualvm中点击对应的进程,查看VisualGC,下面以截取其中一个图进行分析
红色框对应上述讲的老年代,总大小是25M,且比例是我们手动设置的10:1:1;蓝色框是老年代,总大小是75M,和老年代的比例也刚好是设置的3:1,且两者的总大小是100M,完全符合设置的参数。
创建的对象首先会存入Eden,当存满时会转移到S0或S1,S0或S1存放时会转移到Old,循环该过程直到Old也存满时就会报错:java.lang.OutOfMemoryError: Java heap space
3、虚拟机栈
Java虚拟机中的每个线程都有一个私有虚拟机栈,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,如下图(操作数栈也是栈结构,后进先出):
下面通过一个简单的实例进行演示:
public class VMStacksTest {
public static void main(String[] args) {
add();
}
public static int add() {
int num1 = 1;
int num2 = 2;
int sum = num1 + num2;
return sum;
}
}
定义了一个add方法,在main方法中进行调用。接着将其编译成.class文件,然后执行jad -a VMStacksTest.class命令获取到VMStacksTest.jad文件,内容如下:
其中【】里的值来自于该虚拟机指令集
4、本地方法栈
逻辑和虚拟机栈类似,此处不做介绍
5、方法区(Method Area)
从字面来看,方法区存放的是方法,但由于方法只能依赖所在的类,不能单独存在,因此存放的是该类的信息。在类加载阶段的“验证”阶段后,字节码文件被存储到方法区。类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在这里定义。简单来说,所有定义的方法的信息都保存在该区域,静态变量(与类相关)+常量(与类相关)+类信息(构造方法/接口定义)+运行时常量池都存在方法区中,虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是为了和Java的堆区分开(jdk1.8以前hotspot虚拟机叫永久代、持久代,jdk1.8时叫元空间)。
在这里需要注意,方法区是一种规范的叫法,Java虚拟机规范里规定,内存里要有一块内存区域用来存储静态变量、类信息等,但是具体怎么实现由不同的虚拟机厂商决定,他们的叫法也不尽相同,永久代、持久代、元空间等。