很多面试官会问到这个题:请你简述一下JVM的内存模型?
内存模型简介
JVM 定义了不同运行时数据区,他们是用来执行应用程序的。某些区域随着JVM 启动及销毁,另外一些区域的数据是线程性独立的,随着线程创建和销毁。实际上官方也在技术文档中提供了模型架构图
JVM 在执行Java 程序时,会把它管理的内存划分为若干个区域,每个区域都有自己的用途和创建、销毁时间。周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》中把JVM 内存分为线程私有区和线程共享区
那么接下来对上图中各个部分进行介绍
线程共享区
堆
其实在之前的垃圾回收中已经涉及过堆。
它是存放对象实例和数组,是垃圾回收的主要区域,分为新生代和老年代。刚创建的对象在新生代的Eden 区中,经过GC 后进入新生代的S0 区中,再经过GC 进入新生代的S1 区中,15 次GC 后仍存在就进入老年代。这是按照一种回收机制进行划分的,不是固定的。若堆的空间不够实理分配,则OutOfMemoryError。
上图仅仅是一个示意图, SO,S1的角色每轮会互换。
而且各个分区比例可以通过JVM参数进行调整。默认情况下, 新生代和老年代的比例为1:2。S0:S1:Eden = 1:1:8。 不过通过JVM参数设置
- -Xms设置堆的最小空间大小。
- -Xmx设置堆的最大空间大小。
- -XX:NewSize设置新生代最小空间大小。
- -XX:MaxNewSize设置新生代最小空间大小。 可以控制堆大小
方法区
线程共享的,用于存放被虚拟机加载的类的元数据信息,如常量、静态变量和即时编译器编译后的代码。其中运行时常量池存放编译生成的各种常量。方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾收集或压缩它。本规范不强制要求方法区的位置或用于管理编译代码的策略。方法区可以是固定大小的,或者可以根据计算的需要来扩展,并且如果不需要更大的方法区,则可以收缩。方法区的内存不需要是连续的。详情可以阅读JVM规范
同样可以通过JVM参数改变大小
- -XX:PermSize 设置最小空间
- -XX:MaxPermSize 设置最大空间。
线程私有区
程序计数器
当同时进行的线程数超过CPU 数或其内核数时,就要通过时间片轮询分派CPU 的时间资源,避免发生线程切换。这时,每个线程就需要一个属于自己的计数器来记录下一条要运行的指令。如果执行的是JAVA 方法,计数器记录正在执行的java 字节码地址,如果执行的是native 方法,则计数器为空。
虚拟机栈
线程私有的,与线程在同一时间创建。管理JAVA 方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法, -Xss 参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError ;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。使用jclasslib 工具可以查看class 类文件的结构。下图为栈帧结构图:
本地方法栈
与虚拟机栈作用相似。区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
总结
总的来说,学习JVM内存模型对于Java开发人员来说是非常重要的。它不仅有助于理解Java程序的运行机制和行为,还能帮助开发人员提高程序的性能、安全性和可靠性。
往期链接: