《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

字节逐浪客
• 阅读 6821

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

2.1 概述

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

2.2 运行时数据区域

Java虚拟机在Java程序运行时会将内存区域划分成若干个不同的区域,各自负责不同的职责,这些区域都有各自的用途。
  1. Java虚拟机运行时数据区分为以下几个部分。
  2. 方法区、虚拟机栈、本地方法栈、堆、程序计数器,如下图所示:
图片来源于网络如有侵权请私信删除

《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

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. 使用直接指针 是直接访问,优点就是速度快。

最后上一张本章结构图

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

《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

《深入理解Java虚拟机:JVM高级特性与最佳实践_周志明.高清扫描版.pdf》

下载地址:链接:http://pan.baidu.com/s/1miBQCBY 密码:9kbn

推荐阅读

《深入理解Java虚拟机》(一)Java虚拟机发展史

《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

《深入理解Java虚拟机》(四)虚拟机性能监控与故障处理工具

《深入理解Java虚拟机》(五)JVM调优 - 工具

《深入理解Java虚拟机》(六)堆内存使用分析,GC 日志解读

Contact

  • 作者:鹏磊
  • 出处:http://www.ymq.io
  • Email:admin@souyunku.com
  • 版权归作者所有,转载请注明出处
  • Wechat:关注公众号,搜云库,专注于开发技术的研究与知识分享

《深入理解Java虚拟机》(二)Java虚拟机运行时数据区

点赞
收藏
评论区
推荐文章
红橙Darren 红橙Darren
4年前
C进阶 - 内存四驱模型
一.内存四驱模型不知我们是否有读过《深入理解java虚拟机》这本书,强烈推荐读一下。在java中我们将运行时数据,分为五个区域分别是:程序计数器,java虚拟机栈,本地方法栈,java堆,方法区。在c/c中我们将运行时数据,分为四个区域分别是:栈区,堆区,数据区,代码区。我们详细来介绍下:1.栈区:由编译器自动分配释放,存放函数的
Stella981 Stella981
4年前
JVM内存区域划分
JVM内存区域划分一、JVM运行时数据区划分根据《Java虚拟机规范》JVM会把它管理的内存划分为若干个不同的数据区域,如下图所示:方法区、堆、栈(虚拟机栈、本地方法栈)、程序计数器。线程私有的意思是指,JVM每遇到一个新的线程就会为他们分配栈和程序计数器。!(https
Stella981 Stella981
4年前
JVM运行时数据区
Java虚拟机在执行Java程序的过程中会将其管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,及创建和销毁的时间,有些区域随虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束来建立和销毁。Java虚拟机所管理的内存包括以下几个运行时数据区域,如图(图片引自网络):!(https://static.oschina.net/uplo
Stella981 Stella981
4年前
JVM 运行时内存分配
Java内存分配在解释这个问题之前,我想简单的记录一下Java虚拟机对内存的分配管理。!(https://static.oschina.net/uploads/space/2017/0207/160723_gnLQ_1054538.jpg)简单的说,Java运行时内存区域,就由上面几部分构成。青绿色标记的,是每个线程私有的内存区域,其
Stella981 Stella981
4年前
JVM 面试
1、内存模型以及分区,需要详细到每个区放什么。通俗的说,Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在Java虚拟机启动时创建,非堆内存(NonheapMemory)是在JVM堆之外的内存。简单来说,堆是Java代码可及的内
Wesley13 Wesley13
4年前
Java内存区域与内存溢出异常
Java的内存管理是一个老生常谈的问题,虽然Java号称可以自动管理自己的内存,使程序员从内存管理的围墙解放出来,但是一连串的内存泄漏和溢出方面的问题,使得我们不得不去深入了解Java的内存管理机制。本篇文章将从Java的内存区域开始剖析Jvm的内存机制,阐述内存溢出异常产生的原因。运行时数据区域众说周知,Java程序是运行在Java虚拟机
Stella981 Stella981
4年前
JVM中的Stack和Frame
JVM执行Java程序时需要装载各种数据,比如类型信息(Class)、类型实例(Instance)、常量数据(Constant)、本地变量等。不同的数据存放在不同的内存区中,这些数据内存区称作“运行时数据区(RuntimeDataArea)”。运行时数据区有这样几个重要区:JVMStack(简称Stack或者虚拟机栈、线程栈、栈等),Frame(又称S
Wesley13 Wesley13
4年前
Java虚拟机垃圾回收相关知识点全梳理(上)
一、前言笔者最近在复习JVM的知识,本着记录分享的精神,整理下学习Java虚拟机垃圾回收相关知识点,由于整个垃圾回收内容比较多,我将整理成上下两篇文章去分享,上篇我会主要分享Java虚拟机的运行时数据区域划分,垃圾回收算法。下篇文章主要分享Java虚拟机的垃圾回收器以及一些虚拟机调优建议。二、运行时数据区Java虚拟机
Stella981 Stella981
4年前
JVM笔记二:Java内存区域
Java程序在虚拟机自动内存管理的机制的帮助下,不容易出现内存泄露和内存溢出问题,这也就要求程序员需要了解虚拟机处理内存的机制,以解决OOM问题。运行时数据区域!Java虚拟机运行时数据区(https://oscimg.oschina.net/oscnet/3755e1d9e9bf4068b2b3b77b4c0b6bf99b8.jpg)
Stella981 Stella981
4年前
JVM类加载
运行时数据区java虚拟机定义了若干种程序运行时使用到的运行时数据区1.有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁2.第二种则是与线程一一对应,随线程的开始和结束而创建和销毁。java虚拟机所管理的内存将会包括以下几个运行时数据区域!(http://static.oschina.net/uplo
Wesley13 Wesley13
4年前
Java 虚拟机垃圾收集机制详解
本文摘自深入理解Java虚拟机第三版垃圾收集发生的区域之前我们介绍过Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程共存亡。栈中的每一个栈帧分配多少内存基本上在类结构确定下来时就已知,因此这几个区域的内存分配和回收都具有确定性,不需要考虑如何回收的问题,当方法结束或线程结