程序计数器(PC)
- 内存中一块较小的空间,每个线程都有一个 PC,用于存储线程的下一步执行指令。
- 线程执行的命令,也都是从 PC 中获取的。
- 线程间的资源切换,就是获取不同线程的PC中的指令,继续执行的。
- 执行到 Native 方法,计数器值则为空(Undefined)。
栈(虚拟机栈、本地栈)
虚拟机栈
- 每一个栈都有一个虚拟机栈,虚拟机栈中放着栈帧。
- 一个方法相当于一个栈帧。方法的调用相当于创建一个栈帧,并进行入栈与出栈操作。
- 栈,遵循先进后出原则。栈中栈帧越多,相当于方法套用的层级越多。
- 栈帧相当于栈的数据结构,包括局部变量表、操作栈、动态链接、方法返回地址等信息。
- 局部变量表,存放方法参数和局部变量的区域。 局部变量没有准备阶段,必须显式初始化。(类在创建时是有准备阶段的)
- 操作栈,JVM的执行引擎是基于操作栈的执行引擎。同样是先进后出的栈,用于在方法执行过程中, 会有各种指令往栈中写入和提取信息。
- 动态链接,每个栈帧中包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。
- 方法返回地址
- 正常情况下,返回调用方法的上层地址
- 抛异常情况下,抛给异常处理的栈针
- 异常处理情况下,按照 PC执行以下个指令(内部方法抛异常,外部方法捕获并处理)
i++ 与 ++i 的区别
int i= 1;
1. i= i++;
解析:先将1放入操作栈,在将 i自增+1,再从操作栈中取出 1,并赋值给 i ,结果 i为1;
2. i= ++i;
解析:先将 i自增+1,再将1放入操作栈,再从操作栈中取出 1,并赋值给 i ,结果 i为2;
本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
Java堆
- 堆,被所有线程共享,在虚拟机启动时创建。
- 做为 GC 的主要工作区,java 堆也叫作 “GC 堆”。 堆 采用分代算法。
- 从内存回收的角度来看,青年代和老年代;青年代又分为 Eden 空间、From Survivor 空间、To Survivor 空间等。从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区。
方法区
- 被所有线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区,在JDK 1.8 以前在Java堆中。JDK 1.8 及以后在 本地内存中。
- JDK8 之前,Hotspot 中方法区的实现是永久代(Perm),JDK8开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。
Java 8 中 PermGen 为什么被移出 HotSpot JVM 了?
- 由于 PermGen 内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError,因此,移出 避免OOM 的出现。
- 移除 PermGen 可以促进 HotSpot JVM 与JRockit VM 的融合,因为 JRockit 没有永久代。
- 结果:PermGen 最终被移除,方法区移至Metaspace,字符串常量移至 Java Heap。
JDK8 之前的内存区域图如下: