前面介绍了JVM如何加载.class文件到JVM内存中,显然不同类型的数据需要不同的部分进行存储于不同部分,因此JVM在执行Java程序的过程中会把它管理的内存区域划分为若干个不同的数据区域,这些区域有各自的用途,以及创建销毁的时间。这次介绍下JVM运行时数据区。如下图所示JVM运行时数据区:
上图有两个名词线程共享与线程隔离,线程共享指的是某个进程下的所有线程都可以共享访问的数据区域;线程隔离指的是每个线程独享,只能单个线程进行操作,数据不具有共享性;还有五个部分,下面我们对每个部分进行详细讲解。
- 程序计数器
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的符号指示器。在虚拟机模型中,字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
java虚拟机的多线程是通过线程轮换并分配处理器执行时间的方式来实现的,任何时刻,一个处理器(一个核)都只会执行一条线程中的指令,因此为了线程切断后能恢复到明确的执行位置,每条线程都需要一个独立的程序计数器。
所以程序计数器是线程隔离区。
如果线程正在执行一个java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行native方法,则值为空。
- Java虚拟机栈
虚拟机栈生命周期与线程相同,为线程私有(线程隔离区)。虚拟机栈描述的是Java方法执行的内存模型,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表,操作栈,动态链接(GC时使用),方法出口信息等,每个方法被调用直至完成,就对应着一个栈帧在虚拟机栈的入栈出栈。
(1)局部变量表(本地变量表):存放编译期可知的各种基本数据类型,对象引用(reference)。其内存分配在编译期就完成。在java虚拟机中对这个区域规定了两种异常:1.线程请求栈深度大于所允许深度,抛出StackOverflowError;2.扩展内存无法申请到足够的内存会抛出OutOfMemoryError;
- 本地方法栈
本地方法栈与虚拟机栈作用相似,区别是本地方法执行的是native方法,也会抛出
StackOverflowError与
OutOfMemoryError。
- Java堆
Java堆是JVM管理的最大一块内存,被所有线程共享,在虚拟机启动时创建。java堆唯一的目的是存放对象实例,所有的对象实例以及数组都在这里分配内存。如果堆没有内存完成实例分配,并且无法再扩展,抛出
OutOfMemoryError。
- 方法区
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器
(JIT)
编译后的代码等,属于线程共享区域。GC行为在此区域很少出现,回收主要是针对常量池的回收和类型的卸载。当方法区无法满足内存分配会抛出
OutOfMemoryError。
- 运行时常量池(方法区一部分,JDK7以后Hotspot将常量池移到堆中)
Class文件除了有类的版本,字段,方法,接口信息表述等信息外,还有常量池(class文件常量池)。其存放编译期生成的各种字面量、符号引用,这部分内容加载后会放入运行时常量池。一般来说还会将class文件
翻译出来的直接引用也放入运行时常量池。
运行时常量池与class文件常量池相比还具有动态性,Java语言不要求常量一定只在编译期间产生,所以运行期间可能将新的常量放入。不如String的intern()方法。注:
对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。