本文基于 JDK1.8 阐述分析

运行过程

我们都知道 Java 源文件通过编译器编译后,能产生相应的 .Class 文件,也就是字节码文件。而字节码文件通过 Java 虚拟机中的解释器,编译成特定机器上的机器码。

跨平台的特性



Java 能跨平台的原因是因为:不同的平台有不同的 JVM 版本,一个 Java 源文件被编译成字节码文件,被不同平台的 JVM 翻译成特定平台下的机器码从而运行。

Java 虚拟机组成



Java 虚拟机由三个子系统构成,分别是类加载子系统、JVM 运行时数据区和执行引擎,本文的重点是在 JVM 运行时数据区。

类加载子系统将硬盘上的字节码文件加载进内存,JVM 运行内存有一套自己的结构划分如图所示,最终程序要运行,需要操作系统分配相应的时间调度,由执行引擎去执行,才能得到最终结果。

线程共享数据:允许被所有线程共享访问的一块内存区域。

线程私有数据:本线程私有的一块内存区域

虚拟机栈(JVM Stacks)

  • Java 虚拟机栈是线程私有的,它的生命周期与线程相同,线程启动而产生,线程结束而消亡。

  • Java 虚拟机栈是描述 Java 方法执行的内存模型,用于存储栈帧。

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。

  • 虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。

  • 除了 native 方法,几乎所有的 Java 方法都是通虚拟机栈来实现方法的调用和执行(需要程序计数器、堆、方法区的配合)。

  • 栈帧(Stack Frame)
    • 每个方法执行的同时会创建一个栈帧,它是虚拟机栈的基本元素。
    • 一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    • 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
    • 栈帧随着方法调用而创建,随着方法结束而销毁。
    • 每一个栈帧包含的内容有局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。
  • 局部变量表(Local Variable Table)
    • 一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。
    • 该方法所需要分配的局部变量表的最大容量在将 Java 编译为 Class 文件时已经确定。
    • 一个局部变量表保存的是编译期可知的各种基本数据类型、对象引用和 returnAddress 类型(它指向了一条字节码指令的地址)。
    • 局部变量表的容量以变量槽为最小单位,每个变量槽可以存储32位长度的内存空间。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的变量糙空间。
    • 局部变量表所需的内存空间在编译期间就能完成分配,在运行期间不会改变其大小。
    • 虚拟机通过索引定位的方法查找相应的局部变量
  • 操作数栈(Operand Stack)
    • 虚拟机栈中的一个用于计算的临时数据存储区。
    • 随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
  • 动态链接(Dynamic Linking)
    • 在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于运行时常量池。
    • 每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用。
    • 这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
  • 方法返回
    • 一个方法开始执行后,只有两种退出方式:正常完成出口和异常完成出口
    • 正常完成出口指方法正常完成并退出,根据当前方法返回的字节码指令,这时有可能会有返回值传递给方法调用者(调用它的方法),或者无返回值。
    • 异常完成出口指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出。
    • 无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,方法返回时可能需要在栈帧中保存一些信息。
    • 一般来说,方法正常退出时,调用者的程序计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址要通过异常处理器表来确定,栈帧中一般不保存这部分信息。
  • 附加信息
    • 虚拟机规范允许具体的虚拟机实现增加一些规范中没有描述的信息到栈帧之中,例如和调试相关的信息,这部分信息完全取决于不同的虚拟机实现。
    • 在实际开发中,一般会把动态连接,方法返回地址与其他附加信息一起归为一类,称为栈帧信息。

程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

  • 程序计数器是线程私有的

    JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。

  • JVM 规范中唯一没有规定 OutOfMemoryError 情况的区域

    程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。

  • 执行 Native 方法时计数器值为空

    当执行 Java 方法时,程序计数器存放 Java 字节码的地址。实现上可能有两种形式,一种是相对该方法字节码开始处的偏移量,叫做 bytecode index(简称 bci)。另一种是该 Java 字节码指令在内存的地址,叫做 bytecode pointer(简称 bcp)。

    Native 方法大多通过 C 实现,它的方法体不是由 Java 字节码构成,无法应用上述 Java 字节码地址的概念,也就不需要存储字节码文件的行号。

  • Native 方法的实际执行

    Java 线程总是需要以某种形式映射到 OS 线程上,HotSpot VM 目前在大多数平台上都使用 1:1 模型(原生线程模型),也就是每个 Java 线程直接映射到一个 OS 线程上执行。此时 native 方法由原生平台直接执行。

本地方法栈(Native Method Stacks)

本地方法栈为虚拟机使用到的 Native 方法服务。Native 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用 C/C++ 方法。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

  • Java 程序调用本地方法

    不同于虚拟机栈的入/出栈,当线程调用 native 方法时,虚拟机只是简单地动态连接并直接调用指定的 native 方法。

  • 本地方法接口回调 JVM 中的 Java 方法

    如果某个虚拟机实现的本地方法接口是使用 C 连接模型的话,那个他的本地方法栈就是 C 栈,当一个 C 函数调用另一个 C 函数时,它的栈操作是确定的。如果本地方法接口需要回调JVM 中的 Java 方法,该线程会保存本地方法栈的状态并进入到另一个Java栈。

  • 不同虚拟机的不同实现

    虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。常用的 HotSpot 虚拟机选择合并了虚拟机栈和本地方法栈。

堆(Heap)

堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。

  • 分代概念
    • JVM 中堆空间由新生代和老年代两个区组成
    • 新生代可以划分为三个区,Eden 区,两个 Survivor 区
    • Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
    • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
  • 常用参数配置
    参数 说明
    -Xms 堆内存初始大小
    -Xmx 堆内存最大允许大小
    -Xss 每个线程的 Stack 大小
    -XX:NewSize(-Xns) 新生代初始大小
    -XX:MaxNewSize(-Xmn) 新生代最大允许大小
    -XX:NewRatio 设置新生代与老年代比值
    -XX:SurvivorRatio 设置 Survivor 与 Eden 比值
    -XX:PermSize 设置持久代初始内存大小(JDK8 以前)
    -XX:MaxPermSize 设置持久代最大内存(JDK8 以前)
    -XX:MetaspaceSize 设置元空间初始内存大小(JDK8 以后)
    -XX:MaxMetaspaceSize 设置元空间最大内存(JDK8 以后)
  • 堆 GC

    在堆中分配的内存,由 JVM 自动垃圾回收器来管理。关于 GC 详情,之后再补充。

方法区(Method Area)

方法区是一种规范,不同的虚拟机的实现也不一样。从 JDK 1.8 开始,元空间(Metaspace)取代了永久代(PermGen)成为 HotSpot VM 对方法区的实现。方法区存储加载进来的每一个类的结构信息,可以看做是将类(Class)的模板信息,保存在方法区里

  • 元空间属于本地内存

    JDK8 以前,永久代是堆的一部分,和新生代、老年代的地址是连续的。JDK8 以后,元空间属于本地内存,不再属于堆的一部分,它还有一个别名叫非堆(Non-Heap),所以元空间不存在 OOM 内存溢出的情况。

  • 方法区是线程共享的

    当多个线程用到同一个类,而这个类还未被加载,则应该只有一个线程去加载类,其他线程等待。

Java 虚拟机中的运行时数据区分析的更多相关文章

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

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

  2. 介绍下Java内存区域(运行时数据区)

    介绍下Java内存区域(运行时数据区) Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域.JDK 1.8 和之前的版本略有不同. 下图是 JDK 1.8 对JV ...

  3. Java虚拟机一:运行时数据区域

    java虚拟机在执行java程序的过程中,会把内存划分为若干个不同的数据区域.每个区域都有各自的用途,创建和销毁时间,按照<java虚拟机规范(Java SE 7 版)>的规定,虚拟机运行 ...

  4. 深入理解Java虚拟机一:运行时数据区域

    根据<Java虚拟机规范(第2版)>的规定,Java虚拟机管理的内存包括下图几个运行时数据区域: 1.程序计数器        程序计数器(Program Counter Register ...

  5. 虚拟机系列 | JVM运行时数据区

    本文源码:GitHub·点这里 || GitEE·点这里 一.内存与线程 1.内存结构 内存是计算机的重要部件之一,它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱 ...

  6. 你必须了解的java内存管理机制(一)-运行时数据区

    前言 本打算花一篇文章来聊聊JVM内存管理机制,结果发现越扯越多,于是分了四遍文章(文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8),本文为其中第一篇.from 你必须了解的java内存 ...

  7. jvm入门及理解(三)——运行时数据区(程序计数器+本地方法栈)

    一.内存与线程 内存: 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了JAVA在运行过程中内存申请.分配.管理的策略,保证了JVM的 ...

  8. JVM 专题九:运行时数据区(四)本地方法栈

    1. 本地方法栈 2. 什么是本地方法栈? Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用   本地方法栈,也是线程私有的. 允许被实现成固定或者是可动态拓展的内存大小 ...

  9. JVM运行时数据区--本地方法栈

    本地方法栈 1.Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法(一般非Java实现的方法)的调用 2.本地方法栈,也是线程私有的. 3.允许被实现成固定或者是可动态拓展的内存 ...

随机推荐

  1. jQuery操作DOM的相关方法

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  2. Jmeter 注册多个用户 之 CSV Data set Config

    1. 打开Jmeter,新建一个测试计划 > 新建线程组> 创建一个Http 请求 2. 创建一个信息头管理器 > content-Type   application/json; ...

  3. 阿里 IOS 面试官教你在面试中脱颖而出

    前言: 知己知彼.百战不殆,面试也是如此. 只有充分了解面试官的思路,才能更好地在面试中充分展现自己. 今天,阿里高级技术专家将分享自己作为面试官的心得与体会.如果你是面试者,可以借此为镜,对照发现自 ...

  4. shell重定向输出的应用

                         shell重定向输出的应用 案例2:重定向输出的应用 2.1问题 本例要求编写一个脚本/root/out.sh,功能特性如下: 执行此脚本显示I love s ...

  5. mysql 聚集函数 count 使用详解

    mysql 聚集函数 count 使用详解 本文将探讨以下问题 1.count(*) . count(n).count(null)与count(fieldName) 2.distinct 与 coun ...

  6. C#如何正确的做深拷贝

    估计很多人在网上看到各种各样的DeepClone实现, 例如: 1. 通过BinaryFormatter进行二进制序列化 这玩意儿序列化出来的东西还带namespace类型, 尺寸非常大, 调试一下就 ...

  7. 11. SpringCloud实战项目-初始化数据库和表

    SpringCloud实战项目全套学习教程连载中 PassJava 学习教程 简介 PassJava-Learning项目是PassJava(佳必过)项目的学习教程.对架构.业务.技术要点进行讲解. ...

  8. docker win10 推送镜像问题

    镜像制作 docker build  -t gameniuniu:v1.1 . 一.推送 1.docker images 中查找IMAGE ID镜像 2.docker commit <IMAGE ...

  9. 复杂Excel转换与导入

    需求 把不同客户提供Excel 直接导入到系统中生成对应的收货单或是出货单.后端创建收货端和出货单的接口已经有现成的webservice或是标准的xml:这类需要做的就是把客户提供不同种类的Excel ...

  10. 关于gpu版本的tensorflow+anaconda+jupyter的一些安装问题(持续更新)

    关于anaconda安装,虽然清华镜像站资源很丰富,但是不知道是网络还是运气的问题,用这个路径安装的时候总是出现文件丢失.具体表现可能是anaconda prompt 找不到,conda命令无效等问题 ...