0x01.面试题
“运行时数据区"是JVM面试题的热门考点。
实战中,内存泄露的定位、性能调优,往往也取决于我们对"运行时数据区"理解深度。
以下面试题来自百度搜索:
- From百度
三面:请阐述JVM内存模型,有哪些区?分别干什么的?
- From蚂蚁金服
请阐述Java8内存分代改进?
JVM内存分哪几个区,每个区的作用是什么?
一面: JVM内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个survivor区?
二面: Eden和survivor的比例分配?
- From小米
JVM为什么要有新生代和老年代?
- From字节跳动
二面: Java的内存分区
二面:讲讲JVM运行时数据区
什么时候对象会进入老年代?
- From京东
JVM的内存结构,Eden和Survivor比例 。
JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。
- From拼多多
JVM内存分哪几个区,每个区的作用是什么?
0x02.如何更好的理解运行时数据区
笔者认为**“理解优于记忆”**,在笔者其它文章中,也反复表达了这个观点。
那么,对于运行时数据区,怎样理解的方法更好?怎样的理解程度才算透彻?
运行时数据区,本质就是JVM能够管理的内存。
管理内存,我们要考虑如下几件事:
- 从职责的角度看
内存分几个区域?每个内存分区装什么数据?
- 从优化的角度看
怎样充分利用内存?(防御经典的内存碎片问题)
将不同类型的数据(短生命周期的数据、体积大的数据等)存入内存的流程怎样更高效?
废弃的数据何时回收、如何回收?
- 从协作的角度看
不同的线程公用同一个内存分区,如何保证安全可靠?
- 从归属的角度看
如果是虚拟机流派的语言,则要考虑这片内存属于虚拟机内存还是操作系统
用大白话理解,管理内存和管理仓库是一个意思:
- 从职责的角度看
仓库分几个货架?每个货架装什么货物?
- 从优化的角度看
怎样充分利用仓库?(货架上零零散散的放着货物,又不好找,来了大的货物也放不下)
将不同类型的货物(有的货物就是临时放一下、有的货物很大)放进货架的流程怎样更高效?
废弃的货物何时回收、如何回收?
- 从协作的角度看
两个不同的管理员都要往同一个货架上放东西,怎么协调?
- 从归属的角度看
如果菜鸟驿站的仓库放不下,能不能租一下顺丰的仓库呢?
笔者学习运行时数据区时,脑海中就出现了一位仓库管理员,那些复杂抽象的理论也变得简单了。
0x03.运行时数据区的全景
首先,我们要知道运行时数据区包括哪5个区域?
程序计数器
方法区
堆
虚拟机栈
本地方法栈
然后,我们再看运行时数据区如何与JVM中的上下游模块交互?
STEP1.程序员编写的java文件被"编译器"转换为.class文件
STEP2.类加载子系统将.class文件加载到"运行时数据区"的"方法区”
STEP3.执行引擎执行"方法区"中的方法,不断改变着"运行时数据区"的"堆”、“虚拟机栈"中的数据
为避免陷入上述步骤中术语的细节,我们借助仓库货架,通俗的类比一下[1]:
STEP1.小美下单买了件衣服,淘宝店家将小美的订单转换成出库单——淘宝店家就像"编译器”
STEP2.仓库管理员全蛋哥将出库单打印出来,还把商家的入库单也打印出来了。——打印出来的纸件就像"方法区”
STEP3.全蛋哥按照入库单、出库单,在仓库货架上搬货出库和入库,完成一笔就在纸上打个勾。——货架就像"堆”,卷毛弟在入库单、出库单上打的勾的过程就像是在更新"虚拟机栈”
0x04.从优化角度看管理重点
读到这里,如果您已经建立了内存管理与仓库管理的类比关系,我们就可以从优化的角度,思考一下管理环节的重点:
- 方法区的职责是存储"模板”,例如:程序员在代码中定义了哪些类、类和类的关系等。那么方法区是管理的重点吗?有的读者会认为"模板"太多就有可能导致方法区不够用,这种想法对吗?读者可以阅读笔者的另一篇文章《【类加载机制】从一道面试题开始》,尝试寻找答案。本文先不下结论,暂且认为”方法区可能是管理重点”。
- 堆的职责是存储"广义的对象”,例如:根据某个类创建对象,对象的实体就是放在堆中。堆够不够用,是程序员决定的,就好像仓库的货架够不够用是消费者下订单多少决定的。但JVM和仓库管理员应该尽量优化货架的摆放,保证尽量不要让货架放不下。因此,"堆肯定是管理重点”
- 虚拟机栈的职责是"记录方法执行时产生的过程数据产生数据”,这句话有点抽象,我们可以想象一张出库单要求在1号货架拿出一箱酒、在2号货架拿出一件衣服。仓库管理员抱着一箱酒再去2号货架拿衣服肯定不方便,于是仓库管理员临时把一箱酒放在仓库角落,等拿到衣服以后再把酒抱起来一起出库。我们继续想象,如果有10个仓库管理员都这么操作,仓库角落就乱七八糟的临时放了一堆东西。。。于是,"虚拟机栈也成为了管理重点”
- 程序计数器、本地方法栈,概念、职责都比较好理解,笔者在后续文章中详述,这里就直接给出结论**“程序计数器、本地方法栈不是管理重点”**
- 读者可以结合下图,再理解一下上述文字。
0x05.从协作角度看共享区域
- 前文已经阐述了方法区的职责,管理员全蛋哥能看的"模板”,管理员卷毛弟肯定也能看。因此,方法区肯定是共享的。
- 堆是共享的。有一种场景,全蛋哥看了一下卷毛弟的出库单:咦,你也是拿货送给世纪彩城的客户啊,要不我这个单子的货你一起送了呗。
- 虚拟机栈是独享的。这个更好理解,要全蛋哥和卷毛弟只打一份出库单,手牵手去出货,除了摩擦出火花,工作效率应该高不起来。
- 对于程序计数器、本地方法栈,本文只说结论程序计数器、本地方法栈是独享的。
- 读者可以结合下图,再理解一下上述文字。
0x06.从归属角度看内存
- 这里要“剧透”方法区的一个细节,即,方法区中包含了一个"常量池"区域。这就导致了方法区可能经常不够用。于是在JVM的发展历程中,逐渐直接使用了操作系统的内存。这就好像自家的仓库不够用了,就租用了隔壁家更大的仓库一样。
- 读者可以结合下图,再理解一下[1]。
[1]:不同虚拟机尝试的实现细节不同,上图中方法区以及方法区的归属特指HotSpot的JDK8+版本。
0x07.总结
从上述的分析与论述中,我们很容易发现:
- 方法区、堆、虚拟机栈是内存管理的重点
- 方法区、堆是共享的,虚拟机栈是独享的
- 方法区是直接使用了操作系统内存
另外,JVM中诸多技术点较为抽象,用通俗易懂的方式进行类比,非常有利于我们更快更好的学习、攻克JVM。笔者其它的文章也会尽量延续这种方式。