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

JVMmemorymodel

这篇文章主要介绍在JVM规范中描述的运行时数据区(RuntimeDataAreas)。这些区域设计用来存储被JVM自身或者在JVM上运行的程序所是用的数据。

我们先总览JVM,然后介绍下字节码,最后介绍不同的数据区域。

总览

JVM作为操作系统的抽象,保证同样的代码在不同的硬件或操作系统上的行为一致。

比如:

对于基本类型int,无论在16位/32位/64位操作系统上,都是一个32位有符号整数。范围从-2^31到2^31-1

无论操作系统或者硬件是大字节序还是小字节序,保证JVM存储和使用的内存中的数据都是大字节序(先读高位字节)

不同的JVM实现可能会有些区别,但大体上是相同的。

上图是一个JVM的总览

JVM解释编译器生成的字节码。虽然JVM是Java虚拟机的缩写,但是只要是能够编译为字节码的语言,都可以基于JVM运行,比如 scala、groovy<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs6qwcux3MPixrW3sbXEtMXFzEkvT6Os19a92sLru+Gxu2NsYXNzbG9hZGVyvNPU2LKiu7q05rW91MvQ0Mqxyv2+3cf41tC1xNK7uPbH+NPyo6zWqrXAvNPU2Mv8tcRjbGFzc2xvYWRlcrG7z/q72bvy1d9KVk3No9a51MvQ0KGjPC9wPg0KPHA+vNPU2LXE19a92sLrzai5/da00NDS/cfmKGV4ZWN1dGlvbiBlbmdpbmUpvfjQ0L3iys26zda00NA8L3A+DQo8cD7WtNDQ0v3H5tDo0qq05rSis8zQ8snPz8LOxKOsscjI57PM0PLWtNDQtb3ExNK70NCjrLvy1d/K/b7dvMbL47XE1tC85L3hufs8L3A+DQo8cD7WtNDQ0v3H5tKyuLrU8LSmwO3T67XXsuOy2df3z7XNs7XEvbu7pTwvcD4NCjxwPioquty24EpWTba8yrXP1sHLvLTKsbHg0uu5psTcKEpJVD1qdXN0IGluIHRpbWUpoaNKSVS+zcrHsNG+rbOj1rTQ0LXEtPrC6yjIyLXjtPrC6ymx4NLrs8mxvrXYtPrC6yhOYXRpdmUgQ29kZSmho7Tmt8VKSVSx4NLryfqzybT6wuu1xMf40/Kzxs6qPC9wPg0KPHA+tPrC67u6tObH+ChDb2RlIENhY2gpoaO8tMqxseDS67y8yvUoSklUKby2tPO1xMzhuN/By0pWTbXE0NTE3CoqPC9wPg0KPGgyIGlkPQ=="基于栈stack的架构">基于栈(stack)的架构

JVM使用基于栈的架构。虽然栈对于开发者是透明的,但是栈对于生成的字节码和JVM都有很重要的作用或者说影响。

我们开发的程序,会转换位低级别的操作,存于字节码中。在JVM中通过操作数(operand)映射到操作指令。按照JVM规范,操作指令需要的参数是从操作数栈获得的(the operand stack)。

举个两个数相加的例子。这这个操作称为 iadd 。下面是在字节码中 3+4 的过程

首先把3和4压入操作数栈

调用 iadd 指令

iadd 指令会从操作数栈顶弹出2个数

3+4的结果压入操作数栈,供后面使用

这种方式被称为基于栈的架构。还有其他的方式可以处理低级别操作,比如基于寄存器的架构(register based architecture)。

字节码

java字节码是java源码转换为一系列低级别操作的结果。每个操作由一个字节长度的操作码(opcode or operation code)和零或多个字节长度的参数(但是大多数操作使用的参数都是通过操作数栈获取的)组成。一个字节可以表示256个数,从0x00到0xff,目前到java8,共使用了204个。

下面列出不同种类的字节码操作码以及其范围和简单的描述

Constants: 将常量池的值或者已知的值压入操作数栈。 0x00 - 0x14

Loads: 将局部变量值压入操作数栈。 0x15 - 0x35

Stores: 从操作数栈加载值赋给局部变量 0x36 - 0x56

Stack: 处理操作数栈 0x57 - 0x5f

Math: 从操作数栈获取值进行基本的数学计算 0x60 - 0x84

Conversions: 进行类型之间的转换 0x85 - 0x 93

Comaprisons: 两个值的比较操作 0x94 - 0xa6

Controls: 执行goto、return、循环等等控制操作 0xa7 - 0xb1

References: 执行分配对象或数组,获取或检查 对象、方法、静态方法的引用。也可以调用静态方法。 0xb2 - oxc3

Extended: Extended: operations from the others categories that were added after. From value 0xc4 to 0xc9

(这句说不好什么意思。。。)

Reserved: JVM实现内部是用的槽子0xca,oxfe,oxff

这204个操作都很简单,举几个例子

ifeq(0x99) 判断两个值是否相等

iadd(0x60) 把两个数相加

i2l (0x85) 把一个int 转换位 long

arraylength (0xbe) 返回数组长度

pop (0x57) 从操作数栈顶弹出一个值

我们需要编译器来创建字节码文件,标准的java编译器就是jdk中的 javac。

public class Test {

 public static void main(String[] args) {
  int a =1;
  int b = 15;
  int result = add(a,b);
 }

 public static int add(int a, int b){
  int result = a + b;
  return result;
 }
}

通过“javac Test.java” 可以得到 “Test.class”的字节码文件。字节码文件是2进制的,我们可以通过javap,把二进制的字节码文件转换成文本形式

java -verbose Test.class

Classfile /C:/TMP/Test.class
 Last modified 1 avr. 2015; size 367 bytes
 MD5 checksum adb9ff75f12fc6ce1cdde22a9c4c7426
 Compiled from "Test.java"
public class com.codinggeek.jvm.Test
 SourceFile: "Test.java"
 minor version: 0
 major version: 51
 flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  #1 = Methodref     #4.#15     // java/lang/Object."<init>":()V
  #2 = Methodref     #3.#16     // com/codinggeek/jvm/Test.add:(II)I
  #3 = Class       #17      // com/codinggeek/jvm/Test
  #4 = Class       #18      // java/lang/Object
  #5 = Utf8        <init>
  #6 = Utf8        ()V
  #7 = Utf8        Code
  #8 = Utf8        LineNumberTable
  #9 = Utf8        main
 #10 = Utf8        ([Ljava/lang/String;)V
 #11 = Utf8        add
 #12 = Utf8        (II)I
 #13 = Utf8        SourceFile
 #14 = Utf8        Test.java
 #15 = NameAndType    #5:#6     // "<init>":()V
 #16 = NameAndType    #11:#12    // add:(II)I
 #17 = Utf8        com/codinggeek/jvm/Test
 #18 = Utf8        java/lang/Object
{
 public com.codinggeek.jvm.Test();
  flags: ACC_PUBLIC
  Code:
   stack=1, locals=1, args_size=1
     0: aload_0
     1: invokespecial #1         // Method java/lang/Object."<init>":()V
     4: return
   LineNumberTable:
    line 3: 0

 public static void main(java.lang.String[]);
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
   stack=2, locals=4, args_size=1
     0: iconst_1
     1: istore_1
     2: bipush    15
     4: istore_2
     5: iload_1
     6: iload_2
     7: invokestatic #2         // Method add:(II)I
    10: istore_3
    11: return
   LineNumberTable:
    line 6: 0
    line 7: 2
    line 8: 5
    line 9: 11

 public static int add(int, int);
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
   stack=2, locals=3, args_size=2
     0: iload_0
     1: iload_1
     2: iadd
     3: istore_2
     4: iload_2
     5: ireturn
   LineNumberTable:
    line 12: 0
    line 13: 4
}

可以看出字节码不只是java代码的简单翻译,它包括:

类的常量池(cosntant pool)描述。常量池是用于存储类元数据的JVM数据区域,比如类内部的方法名,参数列表,等等。当JVM加载一个类的时候,这些元数据就会加载到常量池

通过行号表和或局部变量表来提供函数和天猫的变量在字节码中的具体位置信息

java代码的翻译(包括隐藏的父类构造)

提供更具体的对于操作数栈的操作和更完整的传递和获取参数的方式

下面是一个简单的字节码文件存储信息的描述

ClassFile {
 u4 magic;
 u2 minor_version;
 u2 major_version;
 u2 constant_pool_count;
 cp_info constant_pool[constant_pool_count-1];
 u2 access_flags;
 u2 this_class;
 u2 super_class;
 u2 interfaces_count;
 u2 interfaces[interfaces_count];
 u2 fields_count;
 field_info fields[fields_count];
 u2 methods_count;
 method_info methods[methods_count];
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}

运行时数据区

运行时数据区是存储数据的内存区域设计。这些数据供开发者或JVM内部使用。

堆(Heap)

堆 在JVM启动的时候创建,由所有的JVM线程所共享。所有的类实例、数组都分配到堆(由new所创建的)。

堆必须由一个垃圾收集器来管理,垃圾收集器负责释放被开发者所创建,并且不会再被使用到的对象。

至于垃圾收集的策略由JVM实现决定(比如HotSpot提供了多种算法).

堆内存有一个最大值限制,如果超过这个值 JVM会抛出一个 OutOfMemroy异常

方法区(Method area)

方法区也是被JVM的所有线程所共享。同样的随JVM启动被创建。方法区存储的数据由classloader从字节码中加载,这些数据会在应用运行过程中一致存在,除非加载它们的classloader被销毁或者JVM停止。

方法区存储如下数据:

类信息(属性名、方法名、父类名、借口名、版本、等等)

方法和构造的字节码

加载每个类时创建的运行时常量池

JVM规范并不强迫在堆中实现方法区。在java7以前,HotSpot 使用一个称为永久带(PermGen)的区域实现方法区。永久带与堆相邻(和堆一样进行内存管理),默认位64MB

从java8开始,HptSpot使用分离的本地内存实现方法区,起名元数据区(Metaspace)。元数据区最大可用空间即整个系统的可用内存。

如果方法去申请不到可用内存,JVM也会抛出OutOfMemoryError.

运行时常量池(Runtime constant pool)
运行时常量池是方法区的一部分。因为运行吃常量池对于元数据的重要性,java规范中在方法区之外单独对其进行了描述。运行时常量池会随着加载的类和接口而增长。

常量池有点想传统语言中的语法表。换句话说,当调用一个类、方法或属性时,JVM通过运行时常量池来寻找这些数据在内存中的真实地址。运行时常量池也包含字符串字面值或基本类型的常量

Stirng myString="This is a string litteral"

  static final int MY_CONSTANT = 2 ;

pc(程序计数器)寄存器(每个线程) The pc Register (Per Thread)
每个线程有自己的pc(程序计数器)寄存器,与线程创建是一同创建。每个线程在一个时间点上只能执行一个方法,称为该线程的当前方法(current method)。pc寄存器包含JVM当前在执行指令(在方法区)的地址。

如果当前执行的方法是本地方法(native),pc寄存器的值是undefined

虚拟机栈每个线程-java-virtual-machine-stacks-per-thread">虚拟机栈(每个线程) java virtual machine stacks (per thread)
虚拟机栈存储多个帧,因此在描述栈前,我们先来看下帧

帧(frames)

帧是一个数据结构,帧包含表示线程正在执行的当前方法状态的多个数据:

操作数栈(Operand Stack): 之前已经提到过,字节码指令使用操作数栈来传递参数

局部变量数组(Local variable array): 这个数组包含当前执行方法的一个作用域内的所有局部变量。这个数组可以包含基本类型、引用或者返回地址。局部变量数组的大小在编译时就已经确定。jvm在方法调用时使用局部变量传递参数,被调方法的局部变量数组通过调用方法的操作数栈创建。

运行时常量池引用: 引用当前类当前被执行方法的常量池。JVM使用常量池引用传递信号给真正的内存引用。

栈(stack)

每个JVM线程都有一个私有的JVM栈,与线程同时创建。java虚拟机栈存储帧。每次调用一个方法时,都会创建一个帧,并且压入虚拟机栈。当这个方法执行完成时,这个帧也会销毁(无论方法是正常执行完成,还是抛出异常)

在一个线程执行的过程中只有一个帧是可用的。这个帧称为当前帧(current frame)。

对局部变量和操作数栈的操作通常和当前帧的引用一起。

我们再看一个加法的例子

public int add(int a, int b){
 return a + b;
}

public void functionA(){
// some code without function call
 int result = add(2,3); //call to function B
// some code without function call
}

在方法A内部,A帧是当前帧,位于虚拟机栈顶。在调用add方法开始时,创建一个新的帧B,并且压入虚拟机栈。帧B成为新的当前帧。

帧B的局部变量数组通过帧A的操作数栈中的数据填充。当add方法结束在,帧B被销毁,帧A重新成为当前帧。add方法的结果压入A帧的操作数栈,这样方法A可以通过帧A的操作数栈获取add 的结果.

总结

以上就是本文关于java虚拟机运行时数据区分析的全部内容,希望对大家有所帮助。

如有不足之处,欢迎留言指出。

(0)

相关推荐

  • 深入理解Java运行时数据区_动力节点Java学院整理

    JVM体系结构和运行时数据区概述 要理解JVM的运行时数据区, 必须先要理解JVM的体系结构, 因为虚拟机的体系结构基本上解释了"为什么会有这些运行时数据区" . JVM的体系结构如下: 由此可见, 运行时数据区的划分, 是和JVM的体系结构相关的. 本文主要介绍运行时数据区的划分, 对体系结构不做深入的讲解. 简单概括一下, 类加载器子系统用于将class文件加载到虚拟机的运行时数据区中(准确的说应该是方法区) . 可以认为执行引擎是字节码的执行机制, 一个线程可以看做是一个执行引擎

  • Java运行时数据区概述详解

    Java 虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途,如图所示: 程序计数器 程序计数器是一块比较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器. 在虚拟机的概念模型中(仅是概念模型,各种虚拟机可能会通过一些更加高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成. 如果线程正在执行一个Java方法,则这个计数

  • java虚拟机运行时数据区分析

    JVMmemorymodel 这篇文章主要介绍在JVM规范中描述的运行时数据区(RuntimeDataAreas).这些区域设计用来存储被JVM自身或者在JVM上运行的程序所是用的数据. 我们先总览JVM,然后介绍下字节码,最后介绍不同的数据区域. 总览 JVM作为操作系统的抽象,保证同样的代码在不同的硬件或操作系统上的行为一致. 比如: 对于基本类型int,无论在16位/32位/64位操作系统上,都是一个32位有符号整数.范围从-2^31到2^31-1 无论操作系统或者硬件是大字节序还是小字节

  • Java JVM运行时数据区(Run-Time Data Areas)

    1.官网概括 引用官网说法: The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits.

  • JAVA JVM运行时数据区详解

    目录 一.前言 二.运行时数据区整体概架构 三.程序计数器 四.虚拟机栈 1.栈的特点 2.栈帧的内部结构 3.局部变量表 4.操作数栈 5.动态链接 6.方法返回地址 五.本地方法栈 六.堆 1.设置堆大小的参数 2.对象分配过程 3.堆中的GC 4.内存分配策略 5.什么是TLAB 6.堆是分配对象存储的唯一选择吗? 七.方法区 1.方法区概述 2.设置方法区内存大小 3.如何解决OOM问题? 4.方法区存储什么 5.方法区的演进细节 6.方法区的GC 总结 一.前言 这是JVM系列文章的第

  • Java虚拟机运行时数据区域汇总

    程序计数器(Program Counter) 程序计数器作为一个概念模型,这个是用来指示下一条需要执行的字节码指令在哪. Java的多线程实际上是通过线程轮转做到的,如果是一个单核的机器(或单cpu),严格意义上在一个时间块中只会有一个线程在执行.为了线程切换以后能恢复到正确的执行位置,每个线程都需要有一个单独的计数器,每个计数器之间要是独立的互不干扰. 如果线程执行的是Java方法,那么PC指向的是正在执行的虚拟机字节码指令的区域,如果执行的是native方法,那么它是undefined. J

  • JVM内存模型/内存空间:运行时数据区

    目录 JVM内存模型/内存空间 ① 程序计数器 (Program Counter Register) ② Java虚拟机栈 (VM Stack) ③ 本地方法栈 (Native Method Stack) ④ Java堆 (Java Heap) ⑤ 方法区(Method Area) ⑥ 运行时常量池 (Running Constant Pool) [特] 直接内存 总结 JVM内存模型/内存空间 Java虚拟机JVM运行起来,就会给内存划分空间,这块空间成为运行时数据区. 运行时数据区主要划分为

  • Java内存模型与JVM运行时数据区的区别详解

    首先,这两者是完全不同的概念,绝对不能混为一谈. 1.什么是Java内存模型? Java内存模型是Java语言在多线程并发情况下对于共享变量读写(实际是共享变量对应的内存操作)的规范,主要是为了解决多线程可见性.原子性的问题,解决共享变量的多线程操作冲突问题. 多线程编程的普遍问题是: 所见非所得 无法肉眼检测程序的准确性 不同的运行平台表现不同 错误很难复现 故JVM规范规定了Java虚拟机对多线程内存操作的一些规则,主要集中体现在volatile和synchronized这两个关键字. vo

  • Java虚拟机运行时栈的栈帧

    目录 Java虚拟机栈概述 局部变量表 操作数栈 动态连接 方法的返回地址 结合javap命令理解栈帧 Java虚拟机栈概述 Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同.虚拟机栈描述的是Java方法执行的内存模型:栈帧(Stack Frame)是用于支持Java虚拟机进行方法调用和执行的数据结构,它是虚拟机栈中的栈元素.每个方法在执行的同到都会创建一个栈帧用于存储局部变量表.操作数栈.动态链接.方法出口等信息. 在编译程序代码的

  • 面试时必问的JVM运行时数据区详解

    目录 前言 正文 1.运行时数据区(Run-Time Data Areas) 1)程序计数器(Program Counter Register) 2)Java虚拟机栈(Java Virtual Machine Stacks) 3)本地方法栈(Native Method Stacks) 4)堆(Heap) 5)方法区(Method Area) 6)运行时常量池(Run-Time Constant Pool) 2.Java 中有哪几种常量池? 3.class 文件常量池 4.运行时常量池 5.字符串

随机推荐