jvm 内存结构和 java 内存模型不是同一个东西
线程私有 | 线程共享 |
---|---|
程序计数器 | 堆 |
虚拟机栈 | 方法区 |
本地方法区 | 堆外内存(Java7的永久代或JDK8的元空间、代码缓存) |
程序计数器
- 也叫 PC 寄存器,存储下一条程序行号(严格是机器码行号),比如分支、循环、线程切换之后的唤醒等
- 每个线程都有且互不影响,所需内存空间较小,唯一不会出现 OutOfMemoryError 的区域
- 生命周期跟随线程的创建和结束(线程结束,这个线程的程序计数器也被销毁)
虚拟机栈
- 存储栈帧,一个栈帧就对应一个方法的调用。类加载字节码文件时就确定好了栈帧的个数,每个类的方法在对应的字节码文件的方法表里
- 栈帧包含局部变量表、操作数栈、动态链接、方法返回地址
- 局部变量表:基本类型和引用对象
- 操作数栈:方法执行过程中的计算结果
- 动态链接:当栈帧中还需要调用别的方法时确定是哪个对象的方法
- 方法返回地址:当前方法调用结束的结果(正常 return 或出现异常)
- 可能出现的异常
- StackOverFlowError:栈帧过深,调用链路太长了,比如死循环方法调用,递归没指定出口等
- OutOfMemoryError:栈帧没有过深,但是每个栈帧要使用的内存太大导致内存不够用
本地方法栈
和虚拟机栈类似,只不过虚拟机存储调用普通方法的栈帧,本地方法栈存储的是 native 方法的栈帧
堆
- 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存
- 为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能)
- 新生带(年轻代):新对象和没达到一定年龄的对象都在新生代
- 老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
- 元空间(JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
方法区
- 与 Java 堆一样,是所有线程共享的内存区域,虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫 Non-Heap(非堆)
- 此区域存放类信息、字段信息、方法信息、常量池、静态变量、即时编译器编译后的代码缓存等数据
元空间和永久代
- 是方法区的具体落地实现,可以理解为方法区是接口,元空间是jdk1.8的实现,永久代是1.7的实现
- 永久代物理是堆的一部分,受垃圾回收影响;而元空间存在于本地内存不受垃圾回收影响
- Java7 中我们通过-XX:PermSize 和 -xx:MaxPermSize 来设置永久代参数;java8 通过-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 用来设置元空间参数
常量池和运行时常量池
- 常量池是 class 文件中的内容,包含类和方法的描述、成员变量、文本字符串、final 常量等
- 运行时常量池是方法区的一部分,是在类加载的时候把 class 文件中的常量池放到 jvm 内存中(每个 class 文件会生成一个 Class 对象)