JDK1.8 的时候,HotSpot 移除方法区(永久代),改成元空间
程序计数器
程序计数器是当前线程执行字节码的行号指示器。字节码解释器工作时就是通过这个改变这个计数器的值来决定下一条需要执行的指令,相当于 goto 语句,是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等都依赖此完成。
为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
总之,程序计数器有两个作用:
- 实现代码的流程控制
- 记录当前线程执行的位置
程序计数器是唯一一个不会出现OutOfMemoryError
的内存区域
Java 虚拟机栈
和程序计数器一样,Java 虚拟机栈也是线程私有的,生命周期和线程同步。描述的是 Java 方法执行的内存模型:每个方法被执行的时候,Java 虚拟机会同步创建一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。调用结束后被弹出。
- 如果虚拟机的栈内存大小不允许动态扩展,当线程请求栈的深度超过最大深度时,会抛出
StackOverFlowError
- 如果可以动态扩展,当栈扩展时无法申请到足够的内存时,就会抛出
OutOfMemoryError
本地方法栈
和虚拟机栈所发挥的作用类似,区别只是虚拟机栈执行 Java 方法,而本地方法栈执行 Native 方法。
也可能抛出 StackOverFlowError
和 OutOfMemoryError
堆
堆是虚拟机所管理的内存中最大的一块,也是所有线程共享的一块区域。唯一目的就存放对象实例,几乎所有的对象都在堆内存中分配
堆还可以详细的划分为新生代和老年代,新生代还可以划分为 Eden 空间、From Survivor、To Survivor。进一步的划分的目的是更好的回收内存及更快的分配内存。
方法区
方法区和 Java 堆一样也是各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然 Java 虚拟机把方法区描述为堆的已逻辑部分,但是它有一个别名叫做非堆(Non-Heap),目的是和 Java 堆区分开来。
Java 虚拟机规范只是规定有方法区这个概念和作用,并没有规定如何去实现它。方法区和永久代的关系很像 Java 中类和接口的关系,永久代就是 HotSpot 虚拟机对方法区的一个实现。JDK1.8 的时候,HotSpot彻底移除了方法区,改成元空间,元空间使用的是直接内存。
为什么要将永久代替换成元空间?
- 避免OOM异常。因为通常使用 PermSize 和 MaxPermSize 设置永久代的大小就决定了永久代的上限,而元空间使用的是直接内存,只受本机可用内存的限制。
- 可以加载更多的类。当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制。
- 要合并 HotSpot 和 JRockit的代码,JRockit 从来没有所谓的永久代。