面试常问的 Java 虚拟机运行时数据区
写在前面
本文描述的有关于 JVM 的运行时数据区是基于 HotSpot 虚拟机。
概述
JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机的进程启动而存在,有的区域则依赖于用户线程的启动和结束而建立和销毁。
HotSpot 运行时数据区
运行时数据区在 HotSpot 1.8 之前的版本和 1.8 版本有所不同,主要是 方法区移到元空间 了。
图 1-1:JDK1.8 之前 JVM 运行时数据区
图 1-2:JDK1.8 JVM 运行时数据区
线程私有区域
程序计数器
程序计数器是一块很小的区域,它存储的是当前线程正在执行的字节码的地址(在这里,其实有两个“当前”,一个是:当前正在被 CPU 执行的线程,另一个是:当前这个被执行的线程中正在被执行的字节码指令)。字节码解释器工作时就是改变程序计数器的值来选取下一条需要执行的字节码。对于单核心而言,多线程是通过线程轮流切换的方式实现的,在任一时刻只有一个线程能够得到 CPU 的执行权从而执行线程中的字节码指令,因此,为了使线程切换后能够恢复到正在执行的字节码的位置,每个线程都需要拥有自己的程序计数器。
注意:程序计数器是唯一的一块在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 的区域。由于它是线程私有的,所以它的生命周期随着线程的创建而创建,随着线程的结束而死亡 。
虚拟机栈
虚拟机栈也是线程私有的,所以它的生命周期与程序计数器相同。虚拟机栈描述的是 Java 方法执行的内存模型。
每个方法在执行的时候都会创建一个栈帧(一个方法对应一个栈帧,栈帧即栈的基本单位)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被线程执行从开始到结束,就对应着一个栈帧在虚拟机栈中入栈(压栈)和出栈(弹栈)的过程。局部变量表中存放了编译可知的各种基本数据类型(byte,short,int,long,float,double,char,boolean)、对象引用(reference 类型,它存储的是:对象的地址或者是指向代表对象的句柄)。
Java 虚拟机规范中规定了虚拟机栈可能出现的两种异常状况:StackOverflowError 和 OutOfMemoryError。
StackOverflowError: 若当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候会抛出 StackOverflowError。
OutOfMemoryError: 若虚拟机栈动态扩展过程中,如果线程请求申请栈空间无法申请到足够的内存,就会抛出 OutOfMemoryError。
本地方法栈
本地方法栈与虚拟机栈类似,虚拟机栈是执行 Java 方法开辟的内存空间,而本地方法栈是执行 Native 方法开辟的内存空间。
与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常,抛出条件也是类似的。
线程间共享的内存区域
堆
堆是所有线程共享的一块区域,主要用来存放对象和数组。
在 Java 虚拟机规范中有描述:所有的对象实例和数组都要在堆上分配,但是 随着 JIT(JUST-IN-TIME)编译器的发展与逃逸分析技术的逐渐成熟,并不是所有对象都只在堆上分配了,比如:随着逃逸分析技术的逐渐成熟,在即时能被回收的对象也有可能会在虚拟机栈上分配。
由于现在都采用分代回收算法,所以从内存回收的角度来看,堆还可以细分为:新生代、老年代。新生代又可以分为:Eden 空间、From Survivor 空间、To Survivor 空间。
注意:1.8 中已经彻底将方法区的实现由之前的永久代改为元空间。
堆里面可能抛出的异常就是 OutOfMemoryError, 出现这种错误的表现形式主要有两种:
OutOfMemoryError: GC Overhead Limit Exceeded:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
java.lang.OutOfMemoryError: Java heap space:假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。
方法区
方法区和堆一样也是所有线程共享的一块区域,主要用来存储已经被虚拟机加载的类信息、常量(final 修饰的)、静态变量、即时编译器(JIT)编译后产生的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
永久代就是方法区域?
早些时候,很多开发者更愿意称方法区为“永久代”。其实“永久代”这个称呼的由来是因为 HotSpot 团队并不打算为方法区重新设计垃圾回收算法,为了在方法区中能够沿用堆中的分代回收算法,所以按照堆中的命名方式,将方法去称为“永久代”。对于 JRocket、J9 而言是不存在“永久代”的概念的,所以当 HotSpot 1.8 和 JRocket 合并时,就彻底放弃了“永久代”的概念(其实从 1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
方法区的垃圾回收很困难!!!
由于 Java 虚拟机规范对方法区的限制非常松,甚至可以不实现垃圾回收,一般而言,这个区域的内存回收很不令人满意,尤其是类型的卸载,条件非常苛刻,但是由于现代框架大量的依赖于 JIT 技术,导致方法区的占用比逐渐提高,所以对于方法区的回收至关重要。根据 Java 虚拟机规范规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
运行时常量池
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
这块区域在 1.7 之前原来是方法区的一部分,Class 文件中有一项信息是常量池(或者说是一张常量表,Class 文件以表存储数据)。
图 1-3:Class 文件常量池
运行时常量池存储的东西较为复杂,主要分为字面量和符号引用。
字面量
存放的字面量主要包括 常量(final 修饰的),比如:final int x = 1、静态变量(static 修饰的),还有一些其他的字面量。
符号引用
符号引用主要包括:类的完全限定名、字段名称和描述符、方法名称和描述符,包括很多符号,比如:() 也可以看做符号引用。
字面量和符号引用将在类加载(ClassLoader 加载 Class 字节码文件)后进入方法区的运行时常量池中存放。不过,除了保存 Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于 Class 文件常量池一个重要的特征就是具备动态性,Java 语言并不要求常量一定产生于编译期的 Class 文件的常量池中,也并不是只有 Class 文件常量池中的常量才能够进入运行时常量池中,在线程执行方法的过程当中可能产生新的常量存放到运行时常量池中,例如:String 类的 intern() 方法。当运行时常量池无法申请到内存的时候就会抛出 OutOfMemoryError 异常。
直接内存
直接内存并不是 JVM 运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
在 JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
总结
Java 虚拟机包含的内容很多,本篇文章也只是对 Java 内存管理模块的 Java 虚拟机运行时数据区做了简要的分析,关于内存管理模块的其他部分后续会继续更新,敬请期待!
参考
- 《深入理解 Java 虚拟机·JVM 高级特性与最佳实践(第 2 版》
- https://mp.weixin.qq.com/s/EZ4DDTC0CSqRQlqJLGSV-g
- https://blog.csdn.net/qq_41212104/article/details/80723644
- https://www.cnblogs.com/chanshuyi/p/jvm_serial_06_jvm_memory_model.html
- https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域?id=写在前面-常见面试题
干货分享
如果大家想要实时关注我更新的文章以及我分享的干货的话,可以关注我的公众号 我们都是小白鼠。公众号内有一些整理过的 原创精品脑图,不仅包含技术点的知识脉络,更多的底层原理的梳理,目前涵盖 Redis,RabbitMQ,Mysql,Java 虚拟机等 ,这些都是博主自己的学习笔记,整理的过程花费了很多心血,除此之外还有一些整理过的 面试题 以及日常开发常用到的一些 开发工具 等,在公众号内分别回复【技术脑图】、【面试题】、【开发工具】即可获取。

面试常问的 Java 虚拟机运行时数据区的更多相关文章
- 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区
Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...
- Java 虚拟机运行时数据区
写在前面 本文描述的有关于 JVM 的运行时数据区是基于 HotSpot 虚拟机. 概述 JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以 ...
- Java虚拟机运行时数据区
运行时数据区程序计数器Java虚拟机栈本地方法栈Java堆(GC堆)方法区运行时常量池 运行时数据区 Java虚拟机在运行Java程序时,会将它所管理的内存划分为若干个内存区域.这些数据区域有各自的用 ...
- 【深入理解Java虚拟机】Java虚拟机运行时数据区
Java虚拟机运行时数据区 线程私有 程序计数器 1.当前线程所执行的字节码的行号指示器. 2.唯一不会发生OutOfMemoryError的区域 3.如果执行的是java方法,计数器值为虚拟机字节码 ...
- 【JVM从小白学成大佬】2.Java虚拟机运行时数据区
目录 1.运行时数据区介绍 2.堆(Heap) 是否可能有两个对象共用一段内存的事故? 3.方法区(Method Area) 4.程序计数器(Program Counter Register) 5.虚 ...
- 【JVM学习】2.Java虚拟机运行时数据区
来源: 公众号: 猿人谷 这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题.但是,往往都会令我比较尴尬,我还话音未落,面试者就会"背诵& ...
- 笔记:Java虚拟机运行时数据区
Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域: 1.程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器:由于Java虚拟机的 ...
- Java 虚拟机运行时数据区详解
本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...
- 深入理解Java虚拟机&运行时数据区
其中,程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程而灭.
随机推荐
- Yuchuan_Linux_C编程之五gdb调试
一.整体大纲 二.gdb调试 1. 启动gdb start -- 只执行一步 n -- next s -- step(单步) -- 可以进入到函数体内部 c - continue - ...
- angular的开始历程
开始写angular了,抑制不住的开心,比react差点开心,vue开始太虐 喜欢一个人要不要表个白?其实也没啥资格喜欢~!!考虑一段时间吧 9.29表白了,嗯,被拒绝的干脆利落 为他写了一首小诗歌, ...
- win7下firefox和chrome升级到最新版之后页面打不开的解决办法
一.升级firefox到最新版后,页面崩溃,打开是空白页,连选项设置都打不开了. 最开始是我的firefox很久没升级,最近要要开始做开发,于是最让它自动升级.等升级到最新版本后,打开浏览器是结果显示 ...
- OpenMP Programming
一.OpenMP概述 1.OpenMP应用编程接口API是在共享存储体系结构上的一个编程模型 2.包含 编译制导(compiler directive).运行库例程(runtime library). ...
- (转)协议森林09 爱的传声筒 (TCP连接)
协议森林09 爱的传声筒 (TCP连接) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 在TCP协议与"流" ...
- (转)协议森林03 IP接力赛 (IP, ARP, RIP和BGP协议)
协议森林03 IP接力赛 (IP, ARP, RIP和BGP协议) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 网络层(net ...
- 一口气说出 6种,@Transactional注解的失效场景
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...
- AndroidStudio提高编译速度的几种方法
第一种: 减少依赖库的使用,让代码更加精简.对于一些必须依赖的库要尽量使用jar包或者依赖库,这样他每次就会在本地直接加载,而不是每次翻墙检查更新 第二种: 打开Android Studio,选择菜单 ...
- 在linux系统中安装LANMP
1.安装LANMP步骤 root@kali:~# wget http://dl.wdlinux.cn/files/lanmp_v3.tar.gz #下载 root@kali:~# tar xzvf l ...
- CodeMixer工具,完美替代ChaosTool,iOS添加垃圾代码工具,代码混淆工具,代码生成器,史上最好用的垃圾代码添加工具,自己开发的小工具
新工具 ProjectTool 已上线 这是一款快速写白包工具,秒级别写H5游戏壳包,可视化操作,极易使用,支持Swift.Objecive-C双语言 扣扣交流群:811715780 进入 Proje ...