这个问题是Java开发的基本功了,如果连堆和栈在 JVM 里的作用都不清楚,那真有点说不过去了。JVM内存区域分为线程私有的部分和现成共享的部分。下面这个图是我之前JVM系列里面画的,再引用下:
线程私有
1. 程序计数器
当前线程所执行的字节码的行号指示器
。
如果执行的事Java方法,计数器记录的为正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,则计数器值为空。
程序计数器是线程私有的。
2. Java虚拟机栈
Java虚拟机栈是与线程同时创建的,其生命周期与线程相同。每个方法在执行的同时都会创建一个栈帧,用于存储本地变量表、操作数栈、动态链接、方法出口等信息
。
栈帧包含以下部分内容:
-
本地变量表(局部变量表):存放了编译期可知的各种基本数据类型、对象引用和return Address(指向了一条字节码指令的地址)类型。
一个本地变量
(Slot)可以存32位以内的数据,可以保存类型为 int, short, reference, byte, char, floath和returnAddress的数据,两个本地变量
可以保存类型为long和double的数据;- 方法执行时,Java 虚拟机使用本地变量表来完成方法的参数传递,如果是实例方法,则
第0个位置
是用于传递方法所属对象实例的引用(即this
关键字)。其余参数按照参数表顺序继续排列;
-
操作数栈:每个栈帧内部都包含一个称为操作数栈的后进先出栈,提供给方法计算过程使用;
-
动态链接:每个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,以支持方法调用期间的动态链接;
- Class文件的常量池中包含了大量的符号引用,字节码中的方法调用指令以常量池中指向的符号引用作为参数,这些符号引用通过两种方式最终转换为直接引用:
静态解析
:一部分在类加载阶段或者第一次使用的时候转换为直接引用;动态链接
:在运行期间转换为直接引用;
- Class文件的常量池中包含了大量的符号引用,字节码中的方法调用指令以常量池中指向的符号引用作为参数,这些符号引用通过两种方式最终转换为直接引用:
-
返回地址:在方法退出之后,需要返回到方法被调用的位置继续执行,所以需要有一个指针指向方法调用的位置,以便继续执行调用该方法后的代码。这个指针就是方法返回地址。方法退出处理的事情包括:
- 恢复
上层方法
局部变量表和操作数栈; - 如有有返回值,把该值压入
调用者栈帧
的操作数栈中; - 调整PC计数器的值以指向方法调用指令的下一条指令地址。
- 恢复
-
特点:栈帧随着方法调用而创建,方法结束时销毁。这种内存分配方式属于线程私有。
虚拟机参数设置:-Xss
设置虚拟机栈的大小
3. 本地方法栈
本地方法栈与Java虚拟机栈功能类似,区别在于本地方法栈为虚拟机使用到的Native方法服务。
- 特点:同样是线程私有。
线程共享
1. Java堆
Java堆是JVM管理的最大一块内存区域,它被所有线程共享。在虚拟机启动时创建,几乎所有的对象实例及数组
都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此又被称作垃圾堆(“GC堆”)。
2. 方法区
方法区(MetaSpace或PermGen)同Java堆一样,是各个线程共享的内存区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
。
在Java 8及以后,方法区的实现被称为元空间
,它不在虚拟机内存中而是使用本地内存。
类信息:方法区包含class文件信息,class文件信息包含:魔数,版本号,常量池,类父类和接口数组,字段,方法等信息。
静态常量池和运行时常量池:一个class文件对应一个常量池,为静态常量池。JDK运行时,会根据静态常量池生成对应的运行时常量池。
直接内存
不属于JVM运行时数据区;jdk1.4+ NIO可以使用native函数库分配堆外内存。
虚拟机参数设置:-XX:MaxDirectMemorySize
。
当各区内存大于物理内存限制的时候,会抛出OOM异常。
堆与栈的区别
- 堆:对象的分配区域,GC管理的主要区域,线程共享。
- 栈:每个线程私有,生命周期随线程,用于方法执行,存放局部变量、操作指令等。