如上图所示,Java虚拟机在运行时主要分为两类:一类是由线程共享的内存区域,包含方法区和堆;另一类是线程私有的内存区域,包含虚拟机栈、本地方法栈和程序计数器。
下面来细说一下每一个内存区域的定义以及它们在java虚拟机运行时所发挥的作用。
1.程序计数器
顾名思义,程序计数器的作用在于计数。程序计数器是一块较小的内存空间,用来保存线程即将执行的下一条字节码的行号。我们可以这么理解:由于多线程是通过轮流切换并分配处理器执行时间来实现的。假设当前有两个线程A和B轮流切换使用处理器执行时间,当由线程A切换至线程B,再由线程B切换至A的时候,怎么知道线程A从何处继续执行呢,通过读取线程A的程序计数器,获得下一条字节码的行号,继续执行。所以,每个线程都有属于自己的程序计数器。记住,此内存区域是唯一一个在Java虚拟机规范中没有任何OutOfMemoryError的区域。
2.虚拟机栈
虚拟机栈也是线程私有的,生命周期和线程相同。该内存区域用于存储局部变量表、操作数栈、动态链接、方法出口。
局部变量表:存放编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可以是一个代表对象的句柄或其他与此对象有关的地址)和returnAddress类型(指向一条字节码指令的地址),在编译期可知,所以内存大小是固定的,并在编译期完成分配。
虚拟机栈的两种异常状况:
(1)StackOverflowError:当线程请求的栈深度大于虚拟机所允许的最大深度时产生;
(2)OutOfMemoryErro:如果虚拟机可以动态扩展,当扩展时无法申请到足够的内存时产生。
3.本地方法栈
本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别在于,虚拟机栈为Java方法服务,而本地方法栈为Native方法服务。
4.Java堆
Java堆在运行时数据区内是一块比较大的内存,它是线程共享的一块区域,在虚拟机启动时创建,此内存区域的唯一目的是存放对象实例和数组,所以该区域也是垃圾回收器主要扫描的区域,因此很多时候也被称为“GC堆”。
由于现在主流的虚拟机大多采用分代垃圾回收机制(后面的文章会谈到),所以该区域也被细分为:新生代和老年代。再细致一点的话,新生代又被分为Eden空间、From Survivor空间、To Survivor空间。
Java堆会出现的异常情况:
OutOfMemoryError:当堆中无法再分配对象实例并且也不能继续扩展的时候产生。
5.方法区
方法区也是线程间共享的一个内存区域 ,用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法去会出现的异常情况:
OutOfMemoryError:当方法去无法继续满足内存分配需求的时候产生。
Java虚拟机的内存大致就是这样,不同的虚拟机之间存在着差异,读者如果有兴趣可以继续深入探讨。