Java虚拟机运行时数据区 详解

2.1 概述

本文参考的是周志明的 《深入理解Java虚拟机》第二章 ,为了整理思路,简单记录一下,方便后期查阅。

2.2 运行时数据区域

Java虚拟机在Java程序运行时会将内存区域划分成若干个不同的区域,各自负责不同的职责,这些区域都有各自的用途。

  1. Java虚拟机运行时数据区分为以下几个部分。
  2. 方法区、虚拟机栈、本地方法栈、堆、程序计数器,如下图所示:

图片来源于网络如有侵权请私信删除

2.2.1 程序计数器

程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码行号指示器。需要注意以下几点内容:

  1. 程序计数器是线程私有,各线程之间互不影响。
  2. 在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。
  3. 如果正在执行java方法,计数器记录的是正在执行的虚拟机字节码指令地址。
  4. 如果是native方法,则计数器值为空(native 方法 指得就是Java程序调用了非Java代码,算是一种引入其它语言程序的接口)。
  5. 程序计数器也是在Java虚拟机规范中唯一没有规定任何OutOfMemoryError异常情况的区域。

2.2.2 java虚拟机栈

  • 可通过参数 栈帧是方法运行期的基础数据结构栈容量可由-Xss设置
  1. Java虚拟机栈是线程私有的,它的生命周期与线程相同。
  2. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  3. 虚拟机栈是执行Java方法的内存模型(也就是字节码)服务:每个方法在执行的同时都会创建一个栈帧,用于存储 局部变量表操作数栈动态链接方法出口等信息。
  • 局部变量表:32位变量槽,存放了编译期可知的各种基本数据类型、对象引用、returnAddress类型。
  • 操作数栈:基于栈的执行引擎,虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据、执行运算,然后把结果压回操作数栈。
  • 动态连接:每个栈帧都包含一个指向运行时常量池(方法区的一部分)中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次的运行期间转化为直接应用,这部分称为动态连接
  • 方法出口:返回方法被调用的位置,恢复上层方法的局部变量和操作数栈,如果无返回值,则把它压入调用者的操作数栈。
  1. 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的。
  2. 在方法运行期间不会改变局部变量表的大小。主要存放了编译期可知的各种基本数据类型、对象引用 (reference类型)、returnAddress类型)

java虚拟机栈,规定了两种异常状况:

  1. 如果线程请求的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
  2. 如果虚拟机栈动态扩展,而扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

2.2.3 本地方法栈

  • 可通过参数 栈容量可由-Xss设置
  1. 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务。
  2. 本地方法栈则是为虚拟机使用到的Native方法服务。有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

2.2.4 java堆

  • 可通过参数 -Xms 初始堆大小-Xmx 最大堆大小-Xmn 新生代` 设置
  1. Java堆是被所有线程共享,是Java虚拟机所管理的内存中最大的一块 Java堆在虚拟机启动时创建。
  2. Java堆唯一的目的是存放对象实例,几乎所有的对象实例和数组都在这里。
  3. Java堆为了便于更好的回收和分配内存,可以细分为,新生代和老年代

     **再细致一点的有Eden空间、From Survivor空间、To Survivor区**。
    • 新生代:包括Eden区、From Survivor区、To Survivor区,系统默认大小Eden:Survivor=8:1:1。
    • 老年代:在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。
  4. Survivor空间等Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可(就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的)。
  • 据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

2.2.5 方法区

  • 可通过参数-XX:MaxPermSize设置
  1. 线程共享内存区域,用于储存已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码,方法区也称持久代(Permanent Generation)。
  2. 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
  3. 如何实现方法区,属于虚拟机的实现细节,不受虚拟机规范约束。
  4. 方法区主要存放java类定义信息,与垃圾回收关系不大,方法区可以选择不实现垃圾回收,但不是没有垃圾回收。
  5. 方法区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
  6. 运行时常量池,也是方法区的一部分,虚拟机加载Class后把常量池中的数据放入运行时常量池。

2.2.6 运行时常量池

  • 可通过参数-XX:PermSize-XX:MaxPermSize设置
  • 常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量。
  • 字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。
  • 运行时常量池(Runtime Constant Pool):方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目资源关联最多的数据类型。
  1. 常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。
  2. 字面量:文本字符串、声明为final的常量值等;。
  3. 符号引用:类和接口的完全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。
  • JDK1.6之前字符串常量池位于方法区之中
  • JDK1.7字符串常量池已经被挪到堆之中

2.2.7 直接内存

  • 可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样
  • 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

2.3 hotspot虚拟机对象探秘

2.3.1 对象的创建

  • 主要探讨HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程
  • 虚拟机遇到new指令时
  1. 首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查引用代表的类是否已被加载、解析和初始化过。如果没有,则执行类加载过程(第7章 虚拟机类加载机制)。
  2. 加载检查通过后,分配内存(内存在类加载完成后便可完全确定)。
  3. 内存分配完成后,虚拟机对对象进行必要的设置,如对象是哪个类的实例、如何找到类的元数据信息等(都放在对象的对象头中)。
  4. 从虚拟机角度看,一个新的对象产生了,但从java程序视角看,对象创建才刚刚开始,因为<init>方法还没有执行,,所有字段为零。执行new指令之后会接着执行<init>方法(构造方法),进行初始化,这样一个真正可用的对象才算完成产生。

2.3.2 对象的内存布局

对象在内存中存储的布局可以分为3块区域:对象头、实例数据、对齐填充

对象头包含两部分(Header)

  • 存储对象自身的 运行时数据,如哈希码、GC分代年龄等。长度在32位和64位的虚拟机中,分别为32bit、 64bit,官方称它为“Mark Word”。
  • 类型指针,对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

注:如果对象是一个java数组,对象头中还必须有一块记录数据长度的数据

实例数据(InstanceData)

  • 对象真正存储的有用信息,也是程序中定义的各种类型的字段内容。

对齐填充(Padding)

  • 由于HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,通俗的说,就是对象大小必须是8字节的整数倍。对象头正好是8字节的倍数。当实例数据部分没有对齐时,需要通过对齐填充来补全。

2.3.3 对象的访问定位

  1. Java程序通过栈上的reference数据来操作堆上的具体对象。
  2. 不同虚拟机实现的对象访问方式会有所不同,目前主流的访问方式有两种:使用句柄和直接指针。
  3. 使用句柄 是间接访问,优点是reference中存储的是稳定的句柄地址,对象移动时只会改变句柄中的实例数据指针。
  4. 使用直接指针 是直接访问,优点就是速度快。

最后上一张本章结构图

图片来源于网络如有侵权请私信删除

转至:https://segmentfault.com/a/1190000010412582

《深入理解Java虚拟机》(二)Java虚拟机运行时数据区的更多相关文章

  1. 《深入理解Java虚拟机》笔记01 -- 运行时数据区

    运行时数据区示意图 1. 程序计数器 占用一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.主要用来记录线程执行到哪条语句了,分支.循环.跳转.异常处理.线程恢复等功能都需要依赖这个 ...

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

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

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

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

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

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

  5. 深入理解Java虚拟机(一) 运行时数据区划分

    前言:从我学Java的第一天开始,我的大学老师就告诉我 Java语言相比C.C++的语言有一个非常强大的功能,那就是自动内存管理:我们用Java编码时不需要申请或释放内存等,这些工作全部交由我们的Ja ...

  6. 【深入理解Java虚拟机】Java虚拟机运行时数据区

    Java虚拟机运行时数据区 线程私有 程序计数器 1.当前线程所执行的字节码的行号指示器. 2.唯一不会发生OutOfMemoryError的区域 3.如果执行的是java方法,计数器值为虚拟机字节码 ...

  7. Java虚拟机及运行时数据区

    1.Java虚拟机的定义 Java虚拟机(Java Virtual Machine),简称JVM.当我们说起Java虚拟机时,可能指的是如下三种不同的东西: 抽象的虚拟机规范 规范的具体实现 一个运行 ...

  8. 笔记:Java虚拟机运行时数据区

    Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域: 1.程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器:由于Java虚拟机的 ...

  9. Java虚拟机一 运行时数据区(栈、堆、方法区等)

    Java虚拟机的内存管理主要分两点:内存分配以及内存回收.· 一.内存分配图: 注: 所占区域的大小与实际的内存大小比例并无直接关系. 解读: 1.如图,分成两种颜色的内存区域,其中蓝色的是线程隔离的 ...

随机推荐

  1. TensorFlow 运行模型--会话(Session)

    会话模式一: 需要明确调用会话生成函数和关闭函数 # 创建一个会话 sess = tf.Session() # 使用创建好的会话进行运算 sess.run("要运算的对象") # ...

  2. JavaScript基础视频教程总结(031-040章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  3. Codeforces Round #486 (Div. 3) F. Rain and Umbrellas

    Codeforces Round #486 (Div. 3) F. Rain and Umbrellas 题目连接: http://codeforces.com/group/T0ITBvoeEx/co ...

  4. bash编程-grep

    grep, egrep, fgrep :输出匹配模式的行 grep:支持基本正则表达式元字符(grep -E相当于egrep) egrep:支持扩展正则表达式元字符(egrep -G相当于grep) ...

  5. Spark中的Phoenix Dynamic Columns

    代码及使用示例:https://github.com/wlu-mstr/spark-phoenix-dynamic phoenix dynamic columns HBase的数据模型是动态的,很多系 ...

  6. linux(centos7)安装docker

    1.检查内核版本,必须是3.10及以上 uname ‐r 2.安装docker yum install docker 3.输入y确认安装 4.启动docker [root@localhost ~]# ...

  7. MongoDB学习小结

    启动对应server:cd:到mangodb安装根目录下 mongod --dbpath db路径 创建MangoDB服务: mongod.exe --logpath d:/mongodb/logs/ ...

  8. HTML5之FileReader文件读取接口

    FileReader用来把文件读入内存,并且读取文件中的数据.FileReader接口提供了一个异步API,使用该API可以在浏览器主线程中异步访问文件系统,读取文件中的数据. 1.FileReade ...

  9. JavaScript对象编程-第3章

    目录 Date对象 Math对象 数组对象 字符串对象 正则表达式对象 什么是对象 对象拥有属性和方法,属性各种数据类型,方法对属性中的数据进行操作. JavaScript的对象 内置对象 Date. ...

  10. 前后端分离开发之前端自己的API(DB)---- (1)

    Creating demo APIs for Front-End Developer 心理准备 Tool-1 开发工具/编辑器:Visual Studio Code , 即 VSCode官网: htt ...