本章笔者将会为大家介绍JVM的运行时数据区,并介绍各个部分的功能。
一、Java虚拟机运行时数据区概览图,本问将会围绕图中的组件展开描述。
二、结构描述
1.程序计数器
程序计数器是一块较小的内存空间,它是线程私有的。它可以看作是当前线程所执行的字节码的行号指示器,由于目前的电脑都是多核的,线程的执行是靠CPU划分时间片的,一个线程并不会一直占用CPU的时间片,因此为了线程切换后能恢复到正常的执行位置,每条线程都需要一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。如果线程正在执行是一个Java方法,这个计数器记录的就是正在执行的虚拟机字节码地址;如果执行的是一个本地(Native)方法,这个计数器的值则应为空。
2.Java虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法执行的时候,都会在虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。有一道经典的++i、i++的题目就是考的这个知识点。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
3.本地方法栈
本地方法栈的作用与虚拟机栈的作用差不多,也是线程私有的,只不过它是为虚拟机使用到的本地(Native)方法服务。
4.堆(Heap)
Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建。此区域的唯一目的就是存放对象实例。几乎所有的对象实例都是在这块区域分配内存,但不是所有,由于编译器技术的进步,尤其是逃逸分析技术的日益强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象都在堆上分配内存也不是那么绝对了。
Java堆是垃圾收集器管理的内存区域,从回收的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”,“老年代”,“伊甸园区”,“From Survivor”,“To Survivor”。但随着垃圾回收技术的提升,也出现了不采用分代收集的垃圾收集器。
Java堆既可以被实现为固定的大小,也可以是扩展的。不过当前主流的Java虚拟机都是按照可以扩展来实现的,通过-Xms -Xmx设定,如果java堆没有完成实例分配并且也无法在扩展是就会跑出OutOfMemoryError异常。
5.方法区
方法区市线程共享的区域,它用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
说到方法区就必须说下永久代和元空间,其实可以将这两个东西看作方法区的实现,在JDK8之前方法区的实现是永久代,在JDK8及以后就完全移除掉了永久代这个概念,改用在本地内存中实现的元空间来代替。
6.运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。