JVM内存区域

如无特殊声明,均指HotSpot虚拟机

虚拟机内存区域

JDK7之前内存区域

jvm1.7-mem
  • 程序计数器:当前线程所执行的字节码的行号指示器,相当于计算机系统中的PC寄存器,每个线程有一个独立的程序计数器。
    如果线程正在执行Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行本地方法,这个计数器的值为空。
  • Java虚拟机栈:线程私有,生命周期与线程相同,相当于计算机系统中函数调用的栈。
    虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都是同步创建一个栈桢(Stack Frame)用于存储局部变量表,操作数栈、动态连接、方法出口等信息。方法的调用与返回,对应着一个栈桢在虚拟机中从入栈到出栈的过程。
  • 本地方法栈:与虚拟机栈类似,为虚拟机执行本地方法时服务。
  • Java堆:对虚拟机来说,相当于传统C、C++内存布局中的堆,时虚拟机管理的内存中最大的一块,被所有线程共享,虚拟机启动时创建,存放对象实例,程序运行时产生的对象都在堆上分配。
  • 方法区:方法区线程共享,物理上属于堆的一部分。用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在《Java虚拟机规范中》被描述为堆的逻辑部分。
    Class文件中的常量池表在类加载后存放到方法区的运行时常量池

关于方法区

方法区是虚拟机的规范,JDK8之前的实现是永久代(Perm Generation),JDK8改为元空间(Meta Space)。永久代有内存上限,即使不设置也有默认大小,容易造成内存溢出,而且不同虚拟机的永久代实现不同会造成某些方法的不同表现。JDK7时,原本在永久代的字符串常量池、静态变量被移出到堆区,到JDK8完全废弃了永久代的概念,将JDK7永久代中还剩下的内容(主要是类型信息)移到本地内存实现的元空间中。

JDK8内存区域

jvm1.8-mem

总结:

  • JDK7之前,方法区的实现为永久代,内存区域如上图,字符串常量池、静态变量、类型信息等均在永久代,永久代物理上属于堆区的一部分;
  • JDK7,方法区实现为永久代,字符串常量池、静态变量等被移到堆区;
  • JDK8,永久代被废弃,方法区实现为元空间,字符串常量池、静态变量等在堆区,类型信息在本地内存实现的元空间中。

JVM内存结构与进程内存结构

首先明白一点,JVM本质上也是一个进程运行在操作系统上,对于操作系统来说,它与其他进程并没有区别,我们的Java程序是由jvm进程读取加载Class文件,然后执行字节码运行起来的

x86-64Linux进程虚拟地址空间

jvm1.8-mem
  • 程序代码和数据:对所有的进程来说,代码从同一固定地址开始。代码和数据区是直接按照可执行目标文件的内容初始化的。
  • 堆:代码和数据区在进程一开始运行时就被指定了大小,堆可以在运行时动态地扩展和收缩,如调用C语言的malloc和free函数。
  • 共享库:大约在地址空间的中间部分是一块用来存放像C标准库和数学库这样的共享的代码和数据的区域。
  • 栈:用户虚拟地址空间的顶部是栈,编译器进行函数调用时会压栈,该区域向下增长;出栈时该区域向上收缩。
  • 内核虚拟内存:地址空间顶部为内核保留,包含当前进程的特有信息与通用的内核代码数据。应用程序不能直接读写这个区域的内容或调用内核函数,必须进入内核态。

二者关系(JDK8)

jvm-process
  1. JVM进程的用户栈、代码和数据、共享库、内核内存与普通进程无区别;
  2. JVM进程向操作系统申请堆内存,一部分为堆(相对于Java堆)外内存(对于虚拟机运行时数据区来说为本地内存),一部分作为Java对象分配内存的堆区(真正的堆区);
  3. JVM一次性向操作系统申请了大块内存作为Java对象的内存分配区域,完全由虚拟机自己管理,创建Java对象时无需经系统调用

以上参考《深入理解Java虚拟机》第三版

Q.E.D.


一切很好,不缺烦恼。