Java内存区域
参考周志明 《深入理解Java虚拟机》
一图胜前言 画图太难了
方法区和堆是线程共享的数据区,虚拟机、本地方法栈和程序计数器是线程隔离的数据区
1、程序计数器
当前线程所执行的字节码的行号指示器,字节码解释器工作就是通过改变这个计数器的值来选取
下一条需要执行的字节码指令
2、Java虚拟机栈
线程私有,生命周期与线程相同
每个方法执行的时候,JVM都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
每一个方法调用直至执行完毕,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
2.1、局部变量表:存放方法参数和方法内部定义的局部变量
2.2、 操作数栈(操作栈):在方法刚开始执行时,操作数栈是空的。
在方法执行过程中,各种字节码指令会向操作数栈中写入和提取信息
例如:i++与++i 的区别
i++:从局部变量表取出 i 并压入操作数栈,然后对局部变量表中的 i 自增1,将操作栈栈顶值取出使用,
最后,使用栈顶值更新局部变量表,因此线程从操作栈读到的是自增之前的值。
++i:先对局部变量表的 i 自增 1,然后取出并压入操作栈,再将操作栈栈顶值取出使用,
最后,使用栈顶值更新局部变量表,因此线程从操作栈读到的是自增之后的值。
2.3、动态连接:
每个栈帧中包含一个指向运行时常量池中该栈帧所属方法的引用,
持有这个引用是为了支持方法调用过程中的动态连接(符号引用在每一次运行期间转化为直接引用)
2.4、方法返回地址:
将方法返至方法当前被调用的位置
3、本地方法栈
主要用于之心本地(Native)方法,与虚拟机栈类似,只不过虚拟机栈执行Java方法
4、堆
虚拟机管理内存中最大的一块区域。此区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
在物理上可以是不连续的内存空间,但在逻辑上是连续的
垃圾收集器管理的内存区域,因此也称`GC堆`
5、方法区
用于存储被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓存区域
在JDK8 以前通过永久代来实现方法区,目的是可以通过垃圾收集器管理堆一样管理这部分内存。
JDK8 以后开始使用 元空间代替。
永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。
为什么使用元空间来取代永久代的实现
1. 字符串存在永久代中,容易出现性能问题和内存溢出。
2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,
太小容易出现永久代溢出,太大则容易导致老年代溢出。
3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4. 将 HotSpot 与 JRockit 合二为一。
6、运行时常量池
运行时常量池是也是方法区的一部分,主要用于存放编译器生成的各种字面量和符号引用,
这些内容将在类加载后存放到运行时常量池中,以便后续使用。
运行时常量相对于常量来说它有一个重要特征:动态性。
Java并不要求常量一定在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行期常量池中。
在Java字节码Class文件结构中的常量池包括静态常量池和运行期常量池。