JVM内存模型一般可以分为以下几个部分:
- 程序计数器:保存当前线程执行的字节码指令的地址,每个线程都有一个独立的程序计数器。
- Java虚拟机栈:每个线程在创建时都会分配一个Java虚拟机栈,用于存储方法调用的信息、局部变量等。Java虚拟机栈被划分为多个栈帧,每个栈帧对应一个方法调用。栈帧包含局部变量表、操作数栈、动态链接、方法返回地址等。
- 本地方法栈:与Java虚拟机栈类似,但是是为本地方法服务的。
- 堆:用于存储对象实例和数组。堆是Java虚拟机管理的最大的一块内存区域,所有的对象实例和数组都在堆上分配,垃圾回收器也是在堆上进行垃圾回收。
- 方法区:用于存储类的元数据信息、静态变量、常量、编译后的代码等内容。方法区也是堆的一部分,但是它有自己的特殊用途,因此单独区分出来。
在JVM运行时,这些内存区域的分配和使用都有一定的规则和限制。
- 程序计数器、Java虚拟机栈和本地方法栈的大小都是固定的;
- 堆和方法区的大小可以通过JVM参数调整;堆和方法区也有一些特殊的分配策略,如年轻代、老年代、永久代等。
堆和方法区在年轻代、老年代、永久代的特殊分配策略
在Java虚拟机中,堆内存被划分为年轻代、老年代和永久代(在JDK8之后,永久代被移除,取而代之的是元空间)。它们的分配策略如下:
年轻代:年轻代分为一个较大的Eden区和两个较小的Survivor区(一般是一个S0区和一个S1区)。新创建的对象首先会被分配到Eden区中,当Eden区满了之后,会触发Minor GC,将Eden区和Survivor区中的存活对象复制到另一个Survivor区中,并且清空Eden区和之前的Survivor区,这样被清空的区域就可以被重新分配。当Survivor区也满了之后,会将其中的存活对象晋升到老年代中。
老年代:老年代是指存活时间比较长的对象,一般是在年轻代中经历了多次GC之后,仍然存活的对象会被晋升到老年代中。老年代的分配策略比较简单,就是当老年代空间不足时,会触发Full GC,将整个堆内存进行清理和整理。
永久代(或元空间):永久代主要存放JVM使用的类信息和常量池等数据,它的分配策略是比较特殊的,一般来说JVM会预留一定的永久代空间,当永久代空间不足时,会触发Full GC,将无用的类信息和常量池等数据进行清理和整理。
需要注意的是,在JDK8之后,永久代被移除,取而代之的是元空间,元空间的分配策略与永久代类似,只是实现方式不同。