JVM内存结构
JVM在执行程序的过程中会将内存划分为五个不同的数据区域:虚拟机栈、本地方法栈、方法区、堆、程序计数器。
JVM五个区中虚拟机栈、本地方法栈、程序计数器为线程私有,方法区和堆为线程共享区。JVM不同区域的占用内存大小不同,一般情况下堆最大,用来存放”对象“,程序计数器较小
- 堆(Heap):线程共享。
- 方法区(Method Area):线程共享。
- 虚拟机栈(VM Stack):线程私有。
- 程序计数器(Program Counter Register):线程私有。
- 本地方法栈(Native Method Stack):线程私有。
1.堆
堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)。
Java堆分为年轻代(Young Generation)和老年代(Old Generation);年轻代又分为伊甸园(Eden)和幸存区(Survivor区);幸存区又分为From Survivor空间和 To Survivor空间。
年轻代存储“新生对象”,我们新创建的对象存储在年轻代中。当年轻内存占满后,会触发Minor GC,清理年轻代内存空间。
老年代存储长期存活的对象和大对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发Full GC。
GC类型:
Minor GC(又称Young GC):主要用于收集年轻代中的非存活对象。Minor GC在Eden区空间不足时触发。
Major GC:主要用于收集老年代中的非存活对象。Major GC在老年代空间不足时触发。注意:Major GC一般都会伴随一次Minor GC,Major GC的速度一般会比Minor GC慢10倍,因此,STW的时间会更长(应尽量避免Minor GC)。
Mixed GC(混合收集):主要用于收集年轻代以及部分老年代中的非存活对象。
Full GC:主要用于收集整个堆(含:年轻代和老年代)中的非存活对象,即:Major GC+Minor GC组合。Full GC触发条件:
- 调用System.gc()方法时,可通过参数-XX:+ DisableExplicitGC来禁止调用System.gc()。
- 方法区空间不足时。
2.方法区
方法区同 Java 堆一样是被所有线程共享的区间,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。
更具体的说,静态变量+常量+类信息(版本、方法、字段等)+运行时常量池存在方法区中。常量池是方法区的一部分。
在JDK8及之前,方法区属于永久代,而在JDK8之后,永久代被移除,方法区被移到了本地内存中,即:元空间(Meta Space)。
元空间逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫非堆。元空间是各个线程共享的内存区域,它主要存储两部分内容:
- 类信息。
- 运行时常量池 。
元空间相关参数:
- MetaspaceSize :初始化元空间大小,控制发生GC阈值,默认值为20MB。
- MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存,默认值为-1(表示无限制)。
3.虚拟机栈
JVM 中的栈包括 Java 虚拟机栈和本地方法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 方法服务,本地方法栈则为 JVM 使用到的 Native 方法服务。
虚拟机栈为线程私有,它描述的是Java方法执行的内存模型。每个栈由多个栈帧(Stack Frame)组成,每个方法被执行时,Java虚拟机都会同步创建一个栈帧用于存储该方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。
4.程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器,指向下一个将要执行的指令代码,由执行引擎来读取下一条指令。更确切的说,一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。
程序计数器特点:
- 每个线程都有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。
- 执行Java方法时,程序计数器的值不为空;执行Native本地方法时,程序计数器的值为空(Undefined)。
- 程序计数器是唯一一个在Java虚拟机规范中没有规定任何内存溢出情况的区域。
5.本地方法栈
本地方法栈与虚拟机栈类似,不同的是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。在Java程序调用Native方法时,Native方法所需要的内存空间在本地方法栈中开辟。
本地方法栈特点:
-
本地方法栈为线程私有。
-
本地方法栈允许被实现成固定或者是可动态扩展的内存大小:
-
- 固定内存大小,当线程请求分配的栈容量超过本地方法栈允许的最大内存大小时,Java虚拟机会抛出StackOverflowError异常。
- 可动态扩展内存大小,当本地方法栈尝试扩展时无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,Java虚拟机会抛出OutOfMemoryError异常。
-
Native方法一般由C/C++实现,它的具体做法是先在本地方法栈中登记Native方法,然后在执行引擎中加载本地方法库并执行。