前言
Java虚拟机在运行Java程序时会把它所管理的内存划分为若干个不同的数据区域。这些数据区域各司其职,互相合作来保证程序的完整运行,称之为运行时数据区。运行时数据区里面有些区域是随着虚拟机进程的启动而一直存在,有些区域则依赖线程的启动和结束而创建和销毁的。
运行时数据区域划分
根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存数据区域会包括以下几个运行时数据区域。
如上图所示,运行时数据区被划分成为五块,分别是:
- 线程隔离的数据区域:虚拟机栈、本地方法栈、程序计数器
- 线程共享的数据区域:堆、方法区
程序计数器
程序计数寄存器(Program Counter Register),也叫程序计数器(PC寄存器)。是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。程序计数器是线程私有的。
Java虚拟机栈
Java虚拟机栈,早期也称为java栈。与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同,随着线程的启动而创建,随着线程的结束而销毁。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、返回地址等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
简单来说虚拟机栈里面存放的是栈帧,栈帧里面放着对应方法的信息。每当执行一个方法,就会创建一个对应的栈帧压入到虚拟机栈内,方法执行结束后,就把该方法对应的栈帧弹出。
本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用非常相似,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。本法方法栈也是属于线程私有的。
在《Java虚拟机规范》对本地方法栈种方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(例如HotSpot虚拟机)直接就把本地方法栈和虚拟机方法栈合二为一了。
堆
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块区域。一个JVM实例只存在一个堆内存,在虚拟机启动时被创建,一旦创建其空间大小也就确定了。
此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都放在这里分配。《Java虚拟机规范》中对堆的描述:所有的对象实例以及数组都应当在堆上分配。但是事无绝对,随着逃逸分析技术的日益强大,栈上分配、标量替换优化使得所有Java对象实例都分配在堆上也渐渐变得不是那么绝对了。
关键点:
- Java堆是垃圾收集器重点照顾的内存区域。
- 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
- 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)
- 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。有意思的是,虽然在《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是他去有一个别名叫做“非堆”(Non-Heap),目的是与Java堆区分开来。
由于《Java虚拟机规范》中对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小、可扩展以外,其他就没有要求了,导致不同的JVM或相同JVM下不同版本都有着不同的实现方式。如HotSpot在1.7及之前都是使用永久代来实现方法区规范的,从1.8开始,就废弃了永久代,改用了和JRockit、J9一样在本地内存中实现元空间来代替。
结束语
好了,Java运行时数据区简单概念介绍就到这边。接下来我们将对各个运行时数据区域进行更加深入的了解。
声明
本文知识点是参考《深入理解Java虚拟机》 第三版和宋红康老师的:尚硅谷宋红康JVM全套教程(详解java虚拟机)