看到这里,我相信大家对于一个 Java 源文件是如何变成字节码文件,以及字节码文件的含义已经非常清楚了。那么接下来就是让 Java 虚拟机运行字节码文件,从而得出我们最终想要的结果了。在这个过程中,Java 虚拟机会加载字节码文件,将其存入 Java 虚拟机的内存空间中,之后进行一系列的初始化动作,最后运行程序得出结果。

那么字节码数据在 Java 虚拟机内存中是如何存放的 ?Java 虚拟机在为类实例或成员变量分配内存是如何分配的 ?要解答上面这些问题,我们首先需要了解一下 Java 虚拟机的内存结构。

其实 Java 虚拟机的内存结构并不是官方的说法,在《Java 虚拟机规范》中用的是「运行时数据区」这个术语。但很多时候这个名词并不是很形象,再加上日积月累的习惯,我们都习惯用虚拟机内存结构这个说法了。

根据《Java 虚拟机规范》中的说法,Java 虚拟机的内存结构可以分为公有和私有两部分。公有指的是所有线程都共享的部分,指的是 Java 堆、方法区、常量池。私有指的是每个线程的私有数据,包括:PC寄存器、Java 虚拟机栈、本地方法栈。

公有部分:Java堆、方法区、常量池

在 Java 虚拟机中,线程共享部分包括 Java 堆、方法区及常量池。

Java 堆指的是从 JVM 划分出来的一块区域,这块区域专门用于 Java 实例对象的内存分配,几乎所有实例对象都在会这里进行内存的分配。之所以说几乎是因为有特殊情况,有些时候小对象会直接在栈上进行分配,这种现象我们称之为「栈上分配」。这里并不深入介绍,后续有章节会介绍。

方法区指的是存储 Java 类字节码数据的一块区域,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造方法等。可以看到常量池其实是存放在方法区中的,但《Java 虚拟机规范》将常量池和方法区放在同一个等级上,这点我们知晓即可。

方法区在不同版本的虚拟机有不同的表现形式,例如在 1.7 版本的 HotSpot 虚拟机中,方法区被称为永久代(Permanent Space),而在 JDK 1.8 中则被称之为 MetaSpace。

说完这几个部分的大致作用之后,我们来深入说说 Java 堆。

Java 堆根据对象存活时间的不同,Java 堆还被分为年轻代、老年代两个区域,年轻代还被进一步划分为 Eden 区、From Survivor 0、To Survivor 1 区。如下图所示。

当有对象需要分配时,一个对象永远优先被分配在年轻代的 Eden 区,等到 Eden 区域内存不够时,Java 虚拟机会启动垃圾回收。此时 Eden 区中没有被引用的对象的内存就会被回收,而一些存活时间较长的对象则会进入到老年代。在 JVM 中有一个名为 -XX:MaxTenuringThreshold 的参数专门用来设置晋升到老年代所需要经历的 GC 次数,即在年轻代的对象经过了指定次数的 GC 后,将在下次 GC 时进入老年代。

这里让我们思考一个问题:为什么 Java 堆要进行这样一个区域划分呢?

根据我们的经验,虚拟机中的对象必然有存活时间长的对象,也有存活时间短的对象,这是一个普遍存在的正态分布规律。如果我们将其混在一起,那么因为存活时间短的对象有很多,那么势必导致较为频繁的垃圾回收。而垃圾回收时不得不对所有内存都进行扫描,但其实有一部分对象,它们存活时间很长,对他们进行扫描完全是浪费时间。因此为了提高垃圾回收效率,分区就理所当然了。

另外一个值得我们思考的问题是:为什么默认的虚拟机配置,Eden:from :to = 8:1:1 呢?

其实这是 IBM 公司根据大量统计得出的结果。根据 IBM 公司对对象存活时间的统计,他们发现 80% 的对象存活时间都很短。于是他们将 Eden 区设置为年轻代的 80%,这样可以减少内存空间的浪费,提高内存空间利用率。

私有部分:PC寄存器、Java 虚拟机栈、本地方法栈

Java 堆以及方法区的数据是共享的,但是有一些部分则是线程私有的。线程私有部分可以分为:PC 寄存器、Java 虚拟机栈、本地方法栈三大部分。

PC 寄存器,顾名思义 Program Counter 寄存器,指的是保存线程当前正在执行的方法。如果这个方法不是 native 方法,那么 PC 寄存器就保存 Java 虚拟机正在执行的字节码指令地址。如果是 native 方法,那么 PC 寄存器保存的值是 undefined。任意时刻,一条 Java 虚拟机线程只会执行一个方法的代码,而这个被线程执行的方法称为该线程的当前方法,其地址被存在 PC 寄存器中。

Java 虚拟机栈,这个栈与线程同时创建,用来存储栈帧,即存储局部变量与一些过程结果的地方。栈帧存储的数据包括:局部变量表、操作数栈。

当 Java 虚拟机使用其他语言(例如 C 语言)来实现指令集解释器时,也会使用到本地方法栈。如果 Java 虚拟机不支持 natvie 方法,并且自己也不依赖传统栈的话,可以无需支持本地方法栈。

总结

Java 虚拟机的内存结构是学习虚拟机所必须掌握的地方,其中以 Java 堆的内存模型最为重要,因为线上问题很多时候都是 Java 堆出现问题。因此掌握 Java 堆的划分以及常用参数的调整最为关键。

除了上述所说的六大部分之外,其实在 Java 中还有直接内存、栈帧等数据结构。但因为直接内存、栈帧的使用场景还比较少,所以这里并不做介绍,以免让初学者一时间混淆。

学到这里,一个 Java 文件就加载到内存中了,并且 Java 类信息就会存储在我们的方法区中。如果创建对象,那么对象数据就会存放在 Java 堆中。如果调用方法,就会用到 PC 寄存器、Java 虚拟机栈、本地方法栈等结构。那么面对如此之多的 Java 类,JVM 是如何决定这些类的加载顺序,又是如此控制它们的加载的呢?下一节,我们讲讲 JVM 的类加载机制。


如果只是看,其实无法真正学会知识的。为了帮助大家更好地学习,我建了一个虚拟机群,专门讨论学习 Java 虚拟机方面的内容,每周针对我所发文章进行讨论答疑。如果你有兴趣,关注「Java技术精选」公众号,通过右下角菜单「入群交流」加我好友,小助手会拉你入群。


JVM系列目录

JVM基础系列第6讲:Java 虚拟机内存结构的更多相关文章

  1. JVM基础系列第2讲:Java 虚拟机的历史

    说起 Java 虚拟机,许多人就会将其与 HotSpot 虚拟机等同看待.但实际上 Java 虚拟机除了 HotSpot 之外,还有 Sun Classic VM.Exact VM.BEA JRock ...

  2. JVM基础系列第3讲:到底什么是虚拟机?

    我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的,而这个软件包在苹果的 Mac OSX 系统上是无法安装的.类似地,Mac OSX 系统上软件安装包则是 dmg 后缀,同样无法在 ...

  3. JVM基础系列第1讲:Java 语言的前世今生

    Java 语言是一门存在了 20 多年的语言,其年纪比我自己还大.虽然存在了这么长时间,但 Java 至今都是最大的工业级语言,许多大型互联网公司均采用 Java 来实现其业务系统.大到国际电商巨头阿 ...

  4. JVM基础系列第15讲:JDK性能监控命令

    查看虚拟机进程:jps 命令 jps 命令可以列出所有的 Java 进程.如果 jps 不加任何参数,可以列出 Java 程序的进程 ID 以及 Main 函数短名称,如下所示. $ jps 6540 ...

  5. JVM基础系列第14讲:JVM参数之GC日志配置

    说到 Java 虚拟机,不得不提的就是 Java 虚拟机的 GC(Garbage Collection)日志.而对于 GC 日志,我们不仅要学会看懂,而且要学会如何设置对应的 GC 日志参数.今天就让 ...

  6. JVM基础系列第13讲:JVM参数之追踪类信息

    我们都知道 JVM 在启动的时候会去加载类信息,那么我们怎么得知他加载了哪些类,又卸载了哪些类呢?我们这一节就来介绍四个 JVM 参数,使用它们我们就可以清晰地知道 JVM 的类加载信息. 为了方便演 ...

  7. JVM基础系列第11讲:JVM参数之堆栈空间配置

    JVM 中最重要的一部分就是堆空间了,基本上大多数的线上 JVM 问题都是因为堆空间造成的 OutOfMemoryError.因此掌握 JVM 关于堆空间的参数配置对于排查线上问题非常重要. tips ...

  8. JVM基础系列第10讲:垃圾回收的几种类型

    我们经常会听到许多垃圾回收的术语,例如:Minor GC.Major GC.Young GC.Old GC.Full GC.Stop-The-World 等.但这些 GC 术语到底指的是什么,它们之间 ...

  9. JVM基础系列第9讲:JVM垃圾回收器

    前面文章中,我们介绍了 Java 虚拟机的内存结构,Java 虚拟机的垃圾回收机制,那么这篇文章我们说说具体执行垃圾回收的垃圾回收器. 总的来说,Java 虚拟机的垃圾回收器可以分为四大类别:串行回收 ...

随机推荐

  1. LVDS、CVBS

    LVDS(Low Voltage Differential Signaling) 是一种低压差分信号技术接口.它是为克服以TTL电平方式传输宽带高码率数据时功耗大.EMI电磁干扰大等缺点而研制的一种数 ...

  2. SFTP服务的使用!!

    在Client端安装Xshell或者SecureCRT等具有SFTP文件传输功能的软件,在服务器端安装好SFTP服务并启动,就可以用SFTP传输文件了! from:   http://www.cnbl ...

  3. Python封装:实现输出一个文件夹下所有各个文件的地址存为列表集合内——Jason niu

    def getAllImages(folder): assert os.path.exists(folder) assert os.path.isdir(folder) imageList = os. ...

  4. 分布式存储ceph理论

    一.ceph简介 Ceph是一种具有优秀性能,可靠性和可扩展性,统一的分布式文件系统.ceph 的统一体现在可以提供文件系统.块存储和对象存储,分布式体现在可以动态扩展.在国内一些公司的云环境中,通常 ...

  5. 查找更改的PeopleCode

    当我们做工程包迁移时,经过会遗漏部分更改过的定义.我们可以用下面的SQL来查找变更项 变量 &OPRID =代码变更者 变量 &PROJECT 项目工程名 SELECT * FROM ...

  6. 类型后面加问号 int?

    类型后面加问号 int? 单问号---用于给变量设初值的时候,给变量(int类型)赋值为null,而不是0! 双问号---用于判断并赋值,先判断当前变量是否为null,如果是就可以赋一个新值,否则跳过 ...

  7. python学习:for循环

    for循环 for i in range(1,4): print("loop:",i) 输出100以内的奇数: for i in range(1,101): if i % 2 == ...

  8. 查看Oracle中存储过程长时间被卡住的原因

    1:查V$DB_OBJECT_CACHE SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CUX_OE_ORDER_RPT_PKG' AND LOCKS!='0 ...

  9. ES6语法(一)

    对于ES6中的一些基础语法,包括对数组/对象/函数/字符串的操作,chroem已经支持了这些语法 // var a = '你' // console.log(a.length) let a = 'ni ...

  10. 如何查找MySQL中查询慢的SQL语句(转载)

    转载自https://www.cnblogs.com/qmfsun/p/4844472.html 如何在mysql查找效率慢的SQL语句呢?这可能是困然很多人的一个问题,MySQL通过慢查询日志定位那 ...