跨平台的本质

关于 JVM, Java 程序员的最熟悉的一句话就是:一处编码,到处执行,指的就是 Java 语言可以通过 JVM 实现跨平台。而跨平台到底跨越了什么这个问题相信很少有人知道,接下来就跟我一起了解一下吧。

下图展示了两种不同的汇编风格,除此之外还有 ARM 汇编(主要应用于移动平台)。不同平台拥有不同的编译器,寄存器,识别不同的指令。例如图片最后一行将 8 赋值给变量 eax 就有不同的写法。正是因为汇编指令的不同,才造成了平台之间的不兼容性

而我们的 JVM 就充当了字节码文件根据不同平台翻译成不同汇编指令的翻译官,解决了跨平台的问题

声明:本文首发于博客园,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 转载请注明,谢谢!

为什么众多语言选择 JVM

目前市面上已经有包括 Java、Kotlin、Groovy 等 10 多种语言都基于 JVM 运行了,不同的语言能够使用其编译器将源代码通通编译成 .class 字节码文件然后交给 JVM。为什么它们都选择 JVM 呢?

  • JVM 拥有优秀的内存管理模型,JDK8 及以前分代内存模型非常成熟、而且可以配合各种各样垃圾回收器做垃圾回收
  • 字节码指令非常精简 + 执行引擎效率非常高
  • 类加载器系统安全、可扩展
  • 高性能 + 低延迟的垃圾回收器

Java 是短暂的,JVM 是永恒的 --- 鲁班学院子牙老师

JVM 内存模型

了解 JVM 内存模型,首先要搞清楚这四个概念

  • .class文件:.java 文件通过编译器编译后,存储在硬盘上的文件
  • class content:类加载器将硬盘中的 .class 文件加载到直接内存中的那块区域之后就成为 class content,此时 class content 内容和 class 文件是一样的
  • class 对象:类加载器基于虚拟机规范将内存中的 class content 解析成 class 对象,放入 方法区 中
  • 对象:执行引擎在执行 new 操作的时候,会将 class 对象生成对象,放入 堆 中

方法区

方法区是模型,具体的典型实现有 Hotspot 在 1.8 之前的 永久代实现 和 1.8及以后的 元空间 实现

永久代实现是 HotSpot 的设计团队选择把垃圾回收器的分代设计扩展至方法区。使用永久代来实现方法区。使得 Hotspot 的垃圾回收器能像管理 Java 堆一样管理方法区的这部分内存,省去专门为方法区编写内存管理代码的工作。 因为永久代有最大内存限制,这种设计导致 Java 更容易 oom。而 元空间 策略只要不触及物理内存上限就没多大事

方法区为什么由永久代实现改成元空间实现?

在 JDK1.8 以前,市面上的操作系统大部分还是32位,而32位操作系统最大支持 4GB 内存,这时如果程序出现死循环或其他原因而疯狂创建新对象占用内存空间,则硬件内存很容易被撑爆。因此 JVM 通过 永久代 实现来管理内存,紧紧把我内存的使用权限;随着硬件的发展,64 位操作系统占据主流市场,最大支持 256TB 内存,市面上主流机器的内存也不断增加。另外 Spring 等框架在一启动就会创建很多的 class 对象,JVM 管理起来很吃力。因此 JVM 在 JDK 1.8 之后索性放松这块的限制,变成了 元空间 实现

程序计数器

关键字:线程私有 不会OOM 字节码行号指示器

记录着虚拟机栈中每个方法执行的位置,可以看作是当前线程所执行的字节码文件的行号指示器。 Java 程序中分支、循环、跳转、异常处理、线程恢复等操作都需要程序计数器才能完成。

Java 虚拟机的多线程是由多线程轮流切换、分配处理器执行时间来实现的。在任何一个确定的时刻,一个内核都只会执行一个线程。因此 Java 中每个线程都有自己的程序计数器来确保线程切换后能回到准确的位置

虚拟机栈

关键字:线程私有 生命周期同线程 栈帧

每个线程都有自己的虚拟机栈,局部变量是存储在虚拟机栈中的,因此不存在并发问题

每个虚拟机栈又有很多个栈帧,每个方法在执行的时候,虚拟机会同步创建一个栈帧,包含了:局部变量表、操作数栈、动态链接、方法出口/返回地址(恢复现场)、附加信息 这些部分

  • 局部变量表:存储该方法编译期可知的各种 Java 基本类型、对象引用和 returnAddress 类型。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中 64 位长度的 long、double 类型占 2 个槽,其余的数据类型占 1 个
  • 操作数栈:变量赋值操作等号右边的部分
  • 动态链接:指向该方法在方法区的地址,虚拟机图示中虚拟机栈指向方法区的棕色箭头便是表示的动态链接
  • 返回地址(恢复现场) 该方法弹栈后,恢复现场进行了如下的操作
    • 局部变量表指针重置
    • 操作数栈指针重置
    • 返回值压栈
    • 该方法占用的栈帧内存回收
    • 程序计数器的数值重置
  • 附加信息:HotSpot 并没有对此进行实现

当线程请求的栈深度大于虚拟机允许的深度时,会报 StackOverflowError 异常

当无法申请到足够的内存时,会 OOM

本地方法栈

和虚拟机栈类似,区别在于运行的是 native 方法,HotSpot 中把本地方法栈和虚拟机栈合二为一

Java堆

关键字:线程共享 垃圾分代收集

所有的对象实例及数组几乎都在堆中分配

为什么老年代的空间是新生代的 2 倍?

老年代存在两种对象:

  • 在新生代经过 15 次 GC 还没有被回收的对象
  • 大小超过伊甸园(Eden区)的大对象,直接放到老年代。避免了对象在新生代三个区之间的复制、避免了新生代三个区被撑爆

可以看出老年代存储的都是大对象 / 老对象。另外老年代也是一种空间担保机制,避免由于新生代空间的限制导致的内存问题。因此需要更大的内存空间

新生代 Eden 和 From、To 区的内存比例为什么是 8:1:1

新生成的对象放入 Eden 区,经过一次 GC 后存活就会放到下一个区。根据大部分的数据统计,90% - 95% 的对象逃不过第一次 GC (朝生夕死),取最小值 90%,得到的两个区的比例是 9:1,但由于这样回收会产生很多内存碎片,导致内存有空间但却不可用。造成了内存浪费。因此将新生代再分成两个区并使用复制算法,一次释放一片内存。就形成了现在的三个区 8:1:1 的比例

内存碎片是在 Eden 产生的还是 From / To?

只要有垃圾回收,就会有碎片产生。

复制算法的细节是什么?怎样避免内存碎片的?

复制算法是将内存分为等大的区域,比如 from 和 to,每次回收前只使用其中一个。当进行一次垃圾回收后,这个区域的内存被完整释放。而存活的对象就被复制到另一个区域。这样就避免了内存碎片的产生

虚拟机栈和Java堆的联系

当我们在方法中执行这样一行代码:

Person p = new Person();

此时变量 p 会被存储在虚拟机栈栈帧的局部变量表中,而 Person 对象则存放在堆中。虚拟机图示中的虚拟机栈指向堆的黄色箭头则表示 p 到 Person 对象的引用关系

堆和方法区之间的联系

堆中存储的对象的对象头的类型指针存在方法区中

对于静态变量,例如 private static Person staticPerson = new Person(); class 对象存储在方法区中,而他的静态变量 staticPerson 指向的对象则存储在堆中

参考文献

B站鲁班学院视频,子牙老师讲解 JVM: https://www.bilibili.com/video/BV1BC4y187Ti?p=19

《深入理解Java虚拟机》--- 周志明

码字不易,如果你觉得读完以后有收获,不妨点个推荐让更多的人看到吧!

图文并茂,带你认识 JVM 运行时数据区的更多相关文章

  1. Jvm运行时数据区

    一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...

  2. Java内存管理:Java内存区域 JVM运行时数据区

    转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...

  3. JVM 运行时数据区 (三)

    JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...

  4. JVM总结(一):概述--JVM运行时数据区

    大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...

  5. JVM运行时数据区与JVM堆内存模型小结

    前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...

  6. JVM运行时数据区和垃圾回收机制

    最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...

  7. Jvm运行时数据区 —— Java虚拟机结构小记

    关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...

  8. Java中的字符串常量池和JVM运行时数据区的相关概念

    什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...

  9. JVM运行时数据区及对象在内存中初始化的过程

    JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...

随机推荐

  1. 微软全球资深副总裁对 VS Code 黑宝书的推荐序!VS Code 月活用户已达 1200 万!

    前不久,首本 VS Code 中文书终于问世了! 在本书出版之前,我很高兴能邀请到微软全球资深副总裁 Julia Liuson 为本书写推荐序!下面,我们就来看一下 Julia 所写的推荐序的完整内容 ...

  2. AT2272 [ARC066B] Xor Sum 题解

    题目连接:传送门 分析 这道题只看题目中给的样例是找不出规律的 所以我们可以打一下表 1, 2, 4, 5, 8, 10, 13, 14, 18 如果你还是没有看出什么规律的话,我们可以从OEIS上搜 ...

  3. Python 最强 IDE 详细使用指南!

    PyCharm 是一种 Python IDE,可以帮助程序员节约时间,提高生产效率.那么具体如何使用呢?本文从 PyCharm 安装到插件.外部工具.专业版功能等进行了一一介绍,希望能够帮助到大家.作 ...

  4. mysql无法启动服务,错误1067

    安装mysql,提示安装成功后,启动服务,提示错误1067 前情提示:mysql安装文件和配置文件没有放在Program File文件夹下. 解决办法:将my.ini文件剪切放在Program Fil ...

  5. Pop!_OS下安装C++编程工具

    Pop!_OS下C++编程 #0x0 Visual Studio Code #0x1 C++ 0x11 code::blocks #0x0 Visual Studio Code 下载安装vscode ...

  6. Mysql基础(四):库、表、记录的详细操作、单表查询

    目录 数据库03 /库.表.记录的详细操作.单表查询 1. 库的详细操作 3. 表的详细操作 4. 行(记录)的详细操作 5. 单表查询 数据库03 /库.表.记录的详细操作.单表查询 1. 库的详细 ...

  7. python 面向对象专题(九):特殊方法 (二)__get__、__set__、__delete__ 描述符(二)覆盖型与非覆盖型描述符对比

    前言 根据是否定义__set__ 方法,描述符可分为两大类. 实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作 ...

  8. C与lua交互--lua调用栈分析(2)

    0 预备知识: 至少对Lua手册C API有简单的了解.lua5.3手册中文 1  Lua虚拟机的栈,如图: 假设虚拟机的栈有n个数据: 解释: 两种解释方式: A,栈顶开始, -1 ...-n B, ...

  9. 全栈的自我修养: 003Axios 的简单使用

    全栈的自我修养: Axios 的简单使用 You should never judge something you don't understand. 你不应该去评判你不了解的事物. 全栈的自我修养: ...

  10. Python Ethical Hacking - WEB PENETRATION TESTING(5)

    Guessing Login Information on Login Pages Our target website: http://10.0.0.45/dvwa/login.php #!/usr ...