关于java:JVM运行时数据区堆虚拟机栈本地方法栈方法区程序计数器

6次阅读

共计 11924 个字符,预计需要花费 30 分钟才能阅读完成。

Java 虚拟机标准运行时数据区相干地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

JVM 虚拟机标准翻译

运行时数据区模型

在 Java 虚拟机中把内存分为若干个不同的数据区域。这些区域有各自的用处,有些区域随着虚拟机过程启动而存在,有些区域则依赖用户线程的启动和完结而建设和销毁。在 JVM 中次要分为以下几个区域:

  • 程序计数器
  • 办法区
  • 虚拟机栈
  • 本地办法栈
  • Java 堆

程序计数器

作用

程序计数器内存占用较小,是以后线程执行的字节码的行号指示器。字节码解释器就是通过这个扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、调整、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。

为什么是线程公有的?

Java 虚拟机的多线程是通过线程轮流切换、调配处理器执行工夫的形式来实现的,在任何一个确定时刻,一个处理器(对于多核 CPU 来说是一个内核)只会执行一条线程的指令。因而为了线程切换后能复原到正确的执行地位,每条线程都须要一个公有的程序计数器,各条线程之间的计数器互不影响,独立存储。

为什么须要计数器?

多线程容许的状况下,一个线程中有多个指令,为了使线程切换能够复原到正确的地位,每个线程都具备各自独立的程序计数器,所以该区域是线程公有的。

如果执行的是 Java 办法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 办法,计数器存储为空。这块区域是虚拟机标准中惟一没有 OutOfMemoryError 的区域。

作用

  1. 实现代码的流程管制
  2. 多线程容许的状况下,记录以后线程的执行地位,从而当线程被切换回来的时候指定该线程上次容许到哪儿了。

办法区(No-Heap)

办法区是一个形象的概念,JDK7 及之前被称为“永恒代”,JDK8 及当前被称为“元空间”,它用于存储虚拟机加载的类型信息、常量、动态变量(JDK7 及之前,JDK8 及之后就把动态变量与 Class 对象放到了堆中)、即时编译器编译 (JIT) 后的代码等数据,是各个线程的共享内存区域。尽管《Java 虚拟机标准》中把办法区形容为堆的一个逻辑局部,然而它却有一个别名叫作“非堆”(Non-Heap),目标是与 Java 堆辨别开来。

JDK8 之前,很多人把办法区又称为永恒代(Permanent Generation),或将两者一概而论,实质上是不对等的,因为仅仅是过后的 HotSpot 虚拟机设计团队把收集器的分代扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得 HotSpot 的垃圾收集器可能像治理 Java 堆一样治理这部分内存,省去了专门为办法区编写内存治理代码的工作。然而这种设计更会导致 Java 利用更容易遇到内存溢出的问题(永恒代有 -XX:MaxPermSize 的下限,即便不设置也有默认大小,而像 J9 和 JRockit 只有没有触碰到过程可用内存的下限,就不会有问题),在 JDK6 的时候 HotSpot 团队就有放弃办法区,逐渐改为采纳本地内存(Native Memory)来实现办法区的打算,到了 JDK7 曾经把本来放在永恒代的字符串常量池、动态变量等移出,到了 JDK8 就齐全破除了永恒代的概念,改用 JRockit、J9 一样在本地内存中实现的元空间(Meta-space),把 JDK7 中永恒代残余的内容(次要是类型信息)全副移到元空间中。

办法区存储每个类信息, 如:

  • Classloader Reference
  • Run Time Constant Pool

    • Numeric constants
    • Field references
    • Method References
    • Attributes
  • Field data

    • Per field

      • Name
      • Type
      • Modifiers
      • Attributes
  • Method data

    • Per method

      • Name
      • Return Type
      • Parameter Types (in order)
      • Modifiers
      • Attributes
  • Method code

    • Per method

      • Bytecodes
      • Operand stack size
      • Local variable size
      • Local variable table
      • Exception table

        • Per exception handler

          • Start point
          • End point
          • PC offset for handler code
          • Constant pool index for exception class being caught
        • 类加载器参考 运行时常量池 数字常量 字段援用 办法援用 属性
        • 畛域数据 每个字段 名字 类型 修饰符 属性
        • 办法数据 每个办法 名字 返回类型 参数类型 (按程序) 修饰符 * 属性
        • 办法代码 每个办法 字节码 操作数堆栈大小 局部变量大小 局部变量表 * 例外表

在 JDK8 之前的 HotSpot JVM,寄存这些“永恒的”区域叫做“永恒代(permanent generation)”。永恒代是一片间断的堆空间,在 JVM 启动前通过在命令行设置参数 -XX:MaxPermSize 来设定永恒代最大可调配的内存空间,默认大小为 64M(64 位的 JVM 默认是 85M)。

办法区或永生代相干参数配置

  • -XX:PermSize=64MB最小尺寸,初始调配
  • -XX:MaxPermSize=256最大容许调配尺寸
  • 按需分配-XX:+CMSClassUnloadingEnabled
  • -XX:+CMSPermGenSweepingEnabled设置垃圾不回收
  • -server选项下默认 MaxPermSize 为 64MB-client 选项下默认 MaxPermSize 为 32MB

Java 虚拟机标准堆办法区限度十分的宽松,可抉择不垃圾回收,以及不须要间断的内存和可扩大的大小。这个区域次要是 针对于常量池的回收以及对类型的卸载,当办法区无奈调配到足够的内存的时候也会报 OOM。

常量池

Class 文件常量池

以下应用理论代码及反编译 Class 文件解说

反编译命令:javap -verbose StringTest.class

public class StringTest {
    private  static String s1 = "static";
    public static void main(String[] args) {String hello1 = new String("hell") + new String("o");
        String hello2 = new String("he") + new String("llo");
        String hello3 = hello1.intern();
        String hello4 = hello2.intern();
        System.out.println(hello1 == hello3);
        System.out.println(hello1 == hello4);
    }
}
Classfile /E:/workspace/VariousCases/target/classes/cn/onenine/jvm/constantpool/StringTest.class
  Last modified 2021-8-3; size 1299 bytes
  MD5 checksum 338bd0034155ec3bf8d608540a31761c
  Compiled from "StringTest.java"
public class cn.onenine.jvm.constantpool.StringTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // cn/onenine/jvm/constantpool/StringTest
   #2 = Utf8               cn/onenine/jvm/constantpool/StringTest
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               s1
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               <clinit>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = String             #11            // static
  #11 = Utf8               static
  #12 = Fieldref           #1.#13         // cn/onenine/jvm/constantpool/StringTest.s1:Ljava/lang/String;
  #13 = NameAndType        #5:#6          // s1:Ljava/lang/String;
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               <init>
  #17 = Methodref          #3.#18         // java/lang/Object."<init>":()V
  #18 = NameAndType        #16:#8         // "<init>":()V
  #19 = Utf8               this
  #20 = Utf8               Lcn/onenine/jvm/constantpool/StringTest;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Class              #24            // java/lang/StringBuilder
  #24 = Utf8               java/lang/StringBuilder
  #25 = Class              #26            // java/lang/String
  #26 = Utf8               java/lang/String
  #27 = String             #28            // hell
  #28 = Utf8               hell
  #29 = Methodref          #25.#30        // java/lang/String."<init>":(Ljava/lang/String;)V
  #30 = NameAndType        #16:#31        // "<init>":(Ljava/lang/String;)V
  #31 = Utf8               (Ljava/lang/String;)V
  #32 = Methodref          #25.#33        // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #33 = NameAndType        #34:#35        // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #34 = Utf8               valueOf
  #35 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
  #36 = Methodref          #23.#30        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #37 = String             #38            // o
  #38 = Utf8               o
  #39 = Methodref          #23.#40        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = NameAndType        #41:#42        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #41 = Utf8               append
  #42 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #43 = Methodref          #23.#44        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #44 = NameAndType        #45:#46        // toString:()Ljava/lang/String;
  #45 = Utf8               toString
  #46 = Utf8               ()Ljava/lang/String;
  #47 = String             #48            // he
  #48 = Utf8               he
  #49 = String             #50            // llo
  #50 = Utf8               llo
  #51 = Methodref          #25.#52        // java/lang/String.intern:()Ljava/lang/String;
  #52 = NameAndType        #53:#46        // intern:()Ljava/lang/String;
  #53 = Utf8               intern
  #54 = Fieldref           #55.#57        // java/lang/System.out:Ljava/io/PrintStream;
  #55 = Class              #56            // java/lang/System
  #56 = Utf8               java/lang/System
  #57 = NameAndType        #58:#59        // out:Ljava/io/PrintStream;
  #58 = Utf8               out
  #59 = Utf8               Ljava/io/PrintStream;
  #60 = Methodref          #61.#63        // java/io/PrintStream.println:(Z)V
  #61 = Class              #62            // java/io/PrintStream
  #62 = Utf8               java/io/PrintStream
  #63 = NameAndType        #64:#65        // println:(Z)V
  #64 = Utf8               println
  #65 = Utf8               (Z)V
  #66 = Utf8               args
  #67 = Utf8               [Ljava/lang/String;
  #68 = Utf8               hello1
  #69 = Utf8               hello2
  #70 = Utf8               hello3
  #71 = Utf8               hello4
  #72 = Utf8               StackMapTable
  #73 = Class              #67            // "[Ljava/lang/String;"
  #74 = Utf8               SourceFile
  #75 = Utf8               StringTest.java
{static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #10                 // String static
         2: putstatic     #12                 // Field s1:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public cn.onenine.jvm.constantpool.StringTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #17                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/onenine/jvm/constantpool/StringTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=5, args_size=1
         0: new           #23                 // class java/lang/StringBuilder
         3: dup
         4: new           #25                 // class java/lang/String
         7: dup
         8: ldc           #27                 // String hell
        10: invokespecial #29                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        13: invokestatic  #32                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        16: invokespecial #36                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        19: new           #25                 // class java/lang/String
        22: dup
        23: ldc           #37                 // String o
        25: invokespecial #29                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        28: invokevirtual #39                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: new           #23                 // class java/lang/StringBuilder
        38: dup
        39: new           #25                 // class java/lang/String
        42: dup
        43: ldc           #47                 // String he
        45: invokespecial #29                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        48: invokestatic  #32                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        51: invokespecial #36                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        54: new           #25                 // class java/lang/String
        57: dup
        58: ldc           #49                 // String llo
        60: invokespecial #29                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        63: invokevirtual #39                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        66: invokevirtual #43                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        69: astore_2
        70: aload_1
        71: invokevirtual #51                 // Method java/lang/String.intern:()Ljava/lang/String;
        74: astore_3
        75: aload_2
        76: invokevirtual #51                 // Method java/lang/String.intern:()Ljava/lang/String;
        79: astore        4
        81: getstatic     #54                 // Field java/lang/System.out:Ljava/io/PrintStream;
        84: aload_1
        85: aload_3
        86: if_acmpne     93
        89: iconst_1
        90: goto          94
        93: iconst_0
        94: invokevirtual #60                 // Method java/io/PrintStream.println:(Z)V
        97: getstatic     #54                 // Field java/lang/System.out:Ljava/io/PrintStream;
       100: aload_1
       101: aload         4
       103: if_acmpne     110
       106: iconst_1
       107: goto          111
       110: iconst_0
       111: invokevirtual #60                 // Method java/io/PrintStream.println:(Z)V
       114: return
      LineNumberTable:
        line 13: 0
        line 14: 35
        line 15: 70
        line 16: 75
        line 17: 81
        line 18: 97
        line 20: 114
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0     115     0  args   [Ljava/lang/String;
           35      80     1 hello1   Ljava/lang/String;
           70      45     2 hello2   Ljava/lang/String;
           75      40     3 hello3   Ljava/lang/String;
           81      34     4 hello4   Ljava/lang/String;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 93
          locals = [class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String]
          stack = [class java/io/PrintStream]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String]
          stack = [class java/io/PrintStream, int]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [class java/io/PrintStream]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String]
          stack = [class java/io/PrintStream, int]
}
SourceFile: "StringTest.java"

运行时常量池

运行时常量池是办法区的一部分,Class 文件中除了有类的版本、字段、办法、接口等信息外,还有一项信息是常量表(Constant Pool Table),用于寄存编译器生成的各种字面量和援用符号,这部分内容将在类加载后放到运行时常量池中。

运行时常量池绝对于 Class 文件常量池的另外一个特色是具备动态性,Java 语言并不要求常量肯定只有编译期能力产生,也就是说并非预置入 Class 文件中常量池的内容能力进入办法区运行时常量池,运行期间也能够将新的常量放入运行时常量池,如 String#intern() 办法。

既然运行时常量池是办法区的一部分,天然受到办法区的内存的限度,当常量池无奈再申请到内存时,就会抛出 OutOfMemoryErro 异样。

全局字符串常量池

HotSpot VM 里,记录 intered 字符串的一个全局表叫做 String Table,它实质上就是一个 HashSet<String>,是一个纯运行时的构造,而且是惰性保护的。
只存储对 java.lang.String 实例的援用,而不存储理论的 String 对象,依据这个援用能够找到理论的 String 对象。

更多对于 String 与常量池相干的常识,独自开一篇文章记录,String 与字符串常量池的恩怨情仇

虚拟机栈

虚拟机栈是每个 Java 办法的内存模型:每个办法被执行的时候都会创立一个 ” 栈帧 ”,用于存储 局部变量表 (包含参数)、操作栈、办法进口 等信息。每个办法被调用到执行实现的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。

平时说的栈个别指的是局部变量表局部。局部变量表寄存了编译期可知的各种 Java 虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference 类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和 returnAddress 类型(指向了一条字节码指令的地址),这些数据类型在局部变量表中以槽(slot)来示意,其中 64 位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。
局部变量表所须要的空间在编译期实现调配,当执行一个办法的时候,该办法须要在栈帧中调配多大的局部变量表的空间齐全是能够确定的,因而在办法运行的期间不会扭转局部变量表的大小,这里说的“大小”是指变量槽的数量,虚拟机真正应用多大的内存空间来实现一个变量槽是由具体的虚拟机实现自行决定的事件。

该区域就是咱们常说的 Java 内存中的栈区域,该区域的局部变量表存储的是 根本类型、对象的援用类型,在对象的援用类型中存储的是 指向对象的堆空间的地址

该区域会呈现两种异样

  1. 当线程申请的栈深度超过虚拟机容许的深度,抛出 StackOverflowError 异样 ( 递归!!!)
  2. 个别虚拟机的栈容量都是能够 动静扩大 的,当栈扩张时申请不多足够的内存,就会抛出 OOM 异样(HotSpot 虚拟机的栈容量是不容许动静扩大的,所以 HotSpot 虚拟机上是不会因为虚拟机栈无奈扩大而导致 OOM 异样的 —- 只有线程申请栈空间胜利了就不会呈现 OOM,然而如果申请失败了,依然是会呈现 OOM 异样的)。

本地办法栈

本地办法栈(Native Method Stacks)与虚拟机栈施展的作用是十分类似的,其区别不过是虚拟机栈为虚拟机执行 Java 办法(字节码)服务,本地办法栈为虚拟机应用到的 native 办法分为,底层调用的是 C 或者 C ++ 的办法。

《Java 虚拟机标准》中对本地办法栈中办法应用的语言、应用形式与数据结构没有任何强制规定,因而具体的虚拟机能够依据须要自在实现它,HotSpot 虚拟机间接就把本地办法栈和虚拟机栈合二为一来应用,与虚拟机栈一样本地办法栈也会在栈深度溢出或者栈扩大失败时别离抛出 StackOverflowError 和 OutOfMemoryError 异样。

Java 堆

Java 堆(Java Heap)是 Java 虚拟机所治理的 内存中最大的一块。Java 堆是 被所有线程共享的内存区域,在虚拟机启动的时候创立。

此内存区域的目标就是存储对象实例,简直所有的对象实例都在这里分配内存。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代实践设计的,所以 Java 堆中经常出现“新生代”、“老年代”、“永恒代”、“Eden 空间”、“From Survivor 空间”、“To Survivor 空间”等名词,这些区域划分仅仅是局部垃圾收集器的独特个性或者设计格调,而非某个 Java 虚拟机的具体实现的固有内存布局,更不是《Java 虚拟机标准》里对 Java 堆的进一步划分。后边讲到的 G1 垃圾收集器就不是基于分代实践设计的。

Java 堆是线程共享的,它的目标是寄存对象实例。同时它也是 GC 所治理的次要区域,因而常被称为 GC 堆。依据虚拟机标准,Java 堆能够存在物理上不间断的内存空间,就像磁盘空间逻辑上是间断的即可。它的内存大小能够设置为固定大小,也能够扩大。以后支流的虚拟机如 HotSpot 都能按扩大实现 (通过设置 **-Xmx**-Xms,默认堆内存大小为服务器内存的 1 /4),如果堆中没有内存实现实例调配,而且堆无奈扩大,则会报 OOM 谬误(OutOfMemoryError)。

新生代又分为:Eden 空间、From SurvivorTo Survivor空间,。进一步划分的目标是为了更好的回收内存或者更快的分配内存。

非堆(No-Heap)

  • 办法区
  • 字符串
  • 代码缓存,用于缓存曾经被 JIT 编译器编译和缓存的本地代码

间接内存

间接内存并不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机标准》中定义的内存区域,然而这部分内存也被频繁应用,而且也可能导致 OOM。

在 JDK 1.4 中新退出了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区
(Buffer)的 I / O 形式,它能够应用 Native 函数库间接调配堆外内存,而后通过一个存储在 Java 堆外面的
DirectByteBuffer 对象作为这块内存的援用进行操作。这样能在一些场景中显著进步性能,因为防止了
在 Java 堆和 Native 堆中来回复制数据。

本机内存间接内存调配不会受到 Java 堆大小的限度,然而既然是内存,必定会受到物理机内存的限度,当咱们通过 -Xmx 设置堆的最大内存时,不要忘了还有间接内存,如果堆内存设置过大,将会导致间接内存不够用,导致动静扩大时产生 OOM。

间接内存的容量大小能够通过 -XX:MaxDirectMemorySize 参数来指定,如果不指定,则默认与 Java 堆的最大值 (-Xmx) 统一。

间接内存导致的 OOM 不会在 Heap Dump 文件中看到什么显著的异样,如果发现内存溢出后的 Dump 文件很小而程序中又间接或间接应用了 DirectMemory(典型的间接应用就是 NIO),那就能够思考重点检查一下间接内存方面的起因了。

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0