java 虚拟机 分析_Java 虚拟机中的运行时数据区分析

本文基于 JDK1.8 阐述分析数据结构

运行过程

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

跨平台的特性

d7075e6f3dc0b8726827790597f4b3ce.png

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

函数

Java 虚拟机组成

2087142664307996c9dda72d26baf7a8.png

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

spa

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

线程共享数据:容许被全部线程共享访问的一块内存区域。线程

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

虚拟机栈(JVM Stacks)

eae648e34b895f52bf65cb67c63b1beb.png

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

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

若是线程请求的栈深度大于虚拟机所容许的深度,将抛出 StackOverflowError 异常。code

虚拟机栈能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出 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 所管理的最大的一块内存空间,主要用于存放各类类的实例对象。堆能够处于物理上不连续的内存空间中,只要逻辑上是连续的便可,就像咱们的磁盘空间同样。

b5fd959c3f5fbfec75023a33c029cda8.png

分代概念

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 内存溢出的状况。

方法区是线程共享的

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值