金三银四面试题(三):JVM内存模型

86 阅读4分钟

很多面试官会问到这个题:请你简述一下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程序的运行机制和行为,还能帮助开发人员提高程序的性能、安全性和可靠性。

往期链接:

金三银四面试题(一):JVM类加载与垃圾回收

金三银四面试题(二):数据库缓存的数据一致性