我的编程空间,编程开发者的网络收藏夹
学习永远不晚

面试必问,JVM内存模型扫盲

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

面试必问,JVM内存模型扫盲

当我们编写Java程序时,Java源代码会被编译成为Java字节码( .java 文件被编译成 .class 文件)。这些字节码可以在任何安装了Java虚拟机的平台上运行。JVM在执行Java字节码时,将其转换成特定于底层CPU和操作系统的机器代码。

运行时数据区简介

为了执行字节码,JVM在内存中定义了一系列的数据区,用于在运行时存储各类数据,即运行时数据区(Runtime Data Areas)。理解这些数据区及其作用,是掌握Java性能调优和错误排查的关键。

JVM 运行时数据区是 Java 虚拟机在执行 Java 程序时用于数据存储的内存区域,这些区域各司其职,确保了 Java 程序的正确执行。JVM 运行时数据区主要分为五个部分:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、堆(Heap)、方法区(Method Area)。JVM运行时数据区在程序运行时动态地分配和释放内存,内存管理由JVM自动完成。不同的数据区域有不同的内存管理机制和垃圾回收算法,以保证程序运行的效率和稳定性。

其中程序计数器、虚拟机栈、本地方法栈属于线程私有区域,跟随线程的启动和结束而建立和销毁。堆和方法区是线程共享区域,跟随虚拟机进程的启动而存在。

程序计数器(Program Counter Register) 是一块较小的内存空间,作用是指示当前线程正在执行的 JVM 字节码指令地址。

虚拟机栈(VM Stack) 存放的是一些基本类型的变量(如int, long)和对象引用。Java 方法执行的内存模型是以栈帧(Stack Frame)为基础的,每个方法在执行的时候都会创建一个栈帧,栈帧中存放了局部变量表、操作数栈、动态链接、方法出口等信息。

本地方法栈(Native Method Stack) 与虚拟机栈类似,其主要服务于 JVM 使用到的 Native 方法。

堆区(Heap) 是 JVM 所管理的最大一块内存空间,主要用于存放所有线程共享的 Java 对象实例。这也是垃圾回收器主要活动区域。

方法区(Method Area) 是用来存储加载的类信息、常量、静态变量等数据的。这个区域是线程共享的。

1. 程序计数器

程序计数器(Program Counter Register)是线程私有区域,生命周期与线程一致,也是 JVM 内存中唯一一个没有任何 OutOfMemoryError 的区域。

程序计数器的作用是记录当前线程正在执行的指令地址,换句话说,它指向了下一条将要被执行的 JVM 字节码指令。在 JVM 的概念模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

当线程执行的是 Java 方法时,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为空(Undefined)。

程序计数器对于现代多线程而言至关重要,因为在 CPU 切换各个线程时,需要将各个线程的程序计数器记录下来,以便在下一次切换回这个线程时,能知道该从哪里继续执行。

总结:

  • 程序计数器是一块很小的内存空间,也是运行速度最快的存储区域。
  • 在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期一致。
  • 如果当前线程正在执行的是 Java 方法,程序计数器记录的是 JVM 字节码指令地址,如果是执行 native 方法,则是未指定值(undefined)
  • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
  • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
  • 它是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域

2. 虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,生命周期与线程相同。描述的是Java方法执行的内存模型。

在 JVM 中,每当一个新的线程被创建,都会创建一个与之关联的私有 JVM 栈。这个栈会随着线程的运行而进行入栈(push)和出栈(pop)操作。它主要用于存储局部变量、操作数堆栈以及方法调用的情况。

JVM 栈是由一系列栈帧(Stack Frame)组成的。每当一个方法被调用,一个新的栈帧就会被压入栈中,每当一个方法调用结束,一个栈帧就会被弹出栈。每个栈帧中都包含了局部变量表、操作数栈、动态链接和方法返回地址等信息。

局部变量表主要存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于指针,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。

操作数栈则是在执行字节码指令时用到的临时存储区,比如在进行算数运算时,操作数栈就会用来存放操作数和接收结果。

Java虚拟机栈可能会抛出以下异常:

  1. 如果线程请求的栈深度大于 JVM 所允许的深度,将抛出 StackOverflowError。
  2. 如果 JVM 栈可以动态扩展,当扩展时无法申请到足够的内存,会抛出 OutOfMemoryError。

3. 本地方法栈

本地方法栈(Native Method Stack)也是线程私有,生命周期与线程相同。作用是与虚拟机栈类似,虚拟机栈是为Java 方法服务的,而本地方法栈是为 Native 方法服务的。

和虚拟机栈一样,本地方法栈的大小可以是固定的也可以是动态的。如果是固定的,当线程请求的栈深度超过最大深度时,会抛出 StackOverflowError。如果是动态的,并且在尝试扩展时无法申请到足够的内存,会抛出 OutOfMemoryError。

4. 堆

堆(Heap)是 JVM 所管理的最大一块内存空间,也是所有线程共享的一块内存区域,在虚拟机启动时创建。堆主要用于存储对象实例和数组,这也是 Java 垃圾回收器主要活动的区域。

在物理上,堆区可以处于分散的内存空间中,但在逻辑上它被视为连续的。堆区在 JVM 启动时创建,如果堆区的空间不足,将会抛出 OutOfMemoryError。

堆分为新生代(Young Generation)和老年代(Old Generation)。新生代又分为 Eden 区、From Survivor 区(简称 S0)、 To Survivor 区(简称 S1)。划分这么多区域的目的是为了更好地回收内存,或者更快地分配内存。

新生代中各个区域的内存占比分别是,Eden : S0 : S1 = 8 : 1 : 1

新创建的对象优先在  Eden 区进行分配。当 Eden 区满时,会触发一次 Minor GC(新生代垃圾回收,也叫 Young GC),将仍然存活的对象从 Eden 区和 S0 区移动到 S1 区,下次 Minor GC 处理情况类似,把存活的对象从 Eden 区和 S1 区移动到 S0 区。当 Survivor 区也满了,还存活的对象会被移动到老年代。如果老年代也满了,将会触发 Major GC(老年代垃圾回收,也叫 Old GC)。当老年代满了,也可能触发 Full GC,Full GC 会对整个堆内存进行垃圾回收,包含新生代、老年代和方法区。Full GC 会导致较长的停顿时间,并且会消耗大量的系统资源。

5. 方法区

方法区(Method Area)与堆一样,是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区只是 JVM 规范中定义的一个概念,针对 Hotspot 虚拟机,JDK8 之前使用永久代(Permanent Generation,简称 PermGen)实现,JDK8 使用元空间(Metaspace)实现。

JDK8 之前可以通过 -XX:PermSize 和 -XX:MaxPermSize 来设置永久代大小,JDK8 之后,使用元空间替换了永久代,改为通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 来设置元空间大小。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区中的一部分,用于存储编译期间生成的各种字面量和符号引用。在Java程序运行时,JVM将编译期生成的class文件中的常量池内容读取到运行时常量池中。

运行时常量池存储了类和接口中的常量,包括字符串字面量、被声明为final的常量值等。它还存储了类和接口中的符号引用,如类和接口、字段和方法的引用等。

在JVM中,运行时常量池是线程安全的。每个线程都有一个自己的线程栈,其中包含了局部变量表,而这些局部变量表中所引用的对象都位于堆中。当一个线程需要引用运行时常量池中的常量时,JVM会先将常量值从运行时常量池中复制到线程栈的局部变量表中,然后再进行引用。

需要注意的是,在JDK8中,运行时常量池已经被移动到元空间(Metaspace)中。元空间是在本地内存中分配的,与JVM的堆内存是分离的,因此不会受到Java堆大小的限制。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

面试必问,JVM内存模型扫盲

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

面试必问,JVM内存模型扫盲

运行时常量池(Runtime Constant Pool)是方法区中的一部分,用于存储编译期间生成的各种字面量和符号引用。在Java程序运行时,JVM将编译期生成的class文件中的常量池内容读取到运行时常量池中。
JVM内存模型2024-11-30

面试必问 | 聊聊Kafka的消费模型?

最近,有些读者去头条二面,被面试官问了一个关于Kafka的问题:多个Kafka消费者如何同时消费相同Topic下的相同Partition的数据? 看似一个简单的问题,竟然把这位读者问懵了!

面试官:什么是Java内存模型?

Java 内存模型存在的原因在于解决多线程环境下并发执行时的内存可见性和一致性问题。在现代计算机系统中,尤其是多处理器架构下,每个处理器都有自己的高速缓存,而主内存(RAM)是所有处理器共享的数据存储区域。

面试必问:什么是双亲委派模型?

双亲委派模型是和 Java 中多个类加载器(启动类加载器、扩展加载器、应用程序类加载器)的运行规则,通过这个(双亲委派模型)规则可以避免类的非安全问题和类被重复加载的问题,但它也遇到了一些问题,比如 JNDI 和 JDBC 不能通过这个规则

程序员进阶架构师必看的面试重灾区:JVM整体架构、内存模型与调优实战

在互联网这个行业中,谁掌握了底层的核心知识,谁就能在激烈的竞争环境中脱颖而出。JVM看起来很难,只要你掌握了学习JVM的规律和方法,吃透它,其实很简单的。

面试官:说说什么是Java内存模型?

由于CPU 和主内存间存在数量级的速率差,想到了引入了多级高速缓存的传统硬件内存架构来解决,多级高速缓存作为 CPU 和主内间的缓冲提升了整体性能。解决了速率差的问题,却又带来了缓存一致性问题。

面试必问:说一下 Java 虚拟机的内存布局?

当然它通常是 JVM 模块的第一个面试问题,所以,接下来我们一起来看它里面包含了哪些内容。

面试必问:Redis过期Key删除和内存淘汰策略

众所周知,Redis是一种内存级kv数据库,所有的操作都是在内存里面进行,定期通过异步操作把数据库数据flush到硬盘上进行保存。因此它是纯内存操作,Redis的性能非常出色,每秒可以处理超过10万次读写操作。

一文详解Spark内存模型原理,面试轻松搞定

Spark内存管理的核心目标是在有限的内存资源下,实现数据缓存的最大化利用和执行计算的高效进行,同时尽量减少由于内存不足导致的数据重算或内存溢出等问题,是整个spark允许可以稳定运行的基础保障。

去京东面试问我JVM堆外内存是什么,我直接麻了,赶紧复习

这个 JVM 里的堆内存是有划分的,一块区域是年轻代,一块区域是老年代,像这种缓存数据,因为是长期存在堆内存里的,所以通常会在年轻代里待一段时间,然后因为没法垃圾回收,给放到老年代里去。
JVM内存区域2024-12-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录