关于java:JVM运行时数据区域之最终章

34次阅读

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

JVM 运行时数据区域之最终章

JVM 运行时数据区域之 java 虚拟机栈

JVM 运行时数据区域之程序计数器

什么是堆

堆是 java 运行时数据区域中最大的一块内存,与栈不同,堆是被所有线程所共享的数据区域;在 java 中简直所有的对象都在堆下面分配内存,即大多数对象都寄存在堆中,留神这里说的“大多数”,上面会提及为何是大多数。

GC 堆?

堆也被称之为是 GC 堆,因为堆是 java 垃圾回收器治理的内存区域,因为有垃圾回收器所以 java 程序员才不必向 C 和 C ++ 程序员那样治理开释内存。也正是因为垃圾回收,所以堆外部又也被划分为不同的区域,例如新生代,老年代,eden,survivor 区域,依据对象的分代年龄,对象大小将其搁置在不同的区域。

上面先简略看一下堆的内存区域划分,在讲垃圾回收的时候再具体阐明

在 GMS 收集器下堆的内存区域的划分

G1 收集器下的堆内存区域

堆的内存区域间断吗?

依据《java 虚拟机标准》的规定,java 堆能够是物理下面的不间断空间,然而逻辑下面它应该是被视为是间断的。而且 java 堆既能够是固定大小,也能够是可扩大的。当堆没有内存为对象进行实例调配,而且也无奈扩大的时候,JVM 会抛出 OOM 异样

堆中只有对象吗?

看完下面的内容容易让大家产生一种错觉就是堆中只有对象,其实不然,堆中还有字符串常量池和 Class 类对象,类变量等;在不同的 JDK 下,堆的内存区域也略有有不同,例如 JDK1.6 的堆中还有永恒代。然而无论如何,堆的最重要的工作就是寄存对象实例。

对象都在堆中吗?

在 Java 倒退的晚期,对象的确都在堆中分配内存,然而随着 java 语言的倒退以及逃逸剖析的日益弱小,栈上调配,标量替换等优化伎俩使得对象不全在堆分配内存。上面就逃逸剖析和栈上调配做简略介绍

逃逸剖析

逃逸剖析是当初 JVM 比拟前沿的优化技术,它的基本原理就是剖析对象的动静作用域,当一个对象被创立后,它有可能被内部的办法所援用例如参数的传递,这种称之为是办法逃逸;甚至还有可能被内部的线程所拜访,例如全局变量,这种是线程逃逸;如果这个对象只是在这个办法内应用,则称之为从不逃逸

栈上调配

对象在堆上分配内存之后就能够被所有的线程拜访,然而试想,如果一个对象它从不产生逃逸,那么将它保留在堆上不仅没有必要,还会耗费堆内存,减轻 GC 的压力,所以这时就能够将它调配到 java 虚拟机栈上的栈帧中,这样不仅保障了栈帧对应的办法能够应用它,而且在办法执行完结后,对象会随着栈帧的出栈而销毁。


办法区

java 的一个类源程序被 javac 编译成 class 文件后再被 java 的类加载器载入内存后,是怎么样寄存的?

办法区和堆一样,也是线程共享的数据区域,它就是用来存储被 JVM 加载的类型(类和接口)信息,常量,动态变量,即时编译器编译后的代码缓存等数据;说简略点就是 class 文件被加载进内存后,class 文件的动态结构造会转换为办法区的动静构造存储在办法区中,而由字节码编译而成的机器码也会存储在办法区。

当然下面的说法在逻辑上就是那样,然而实际上像动态变量,常量在不同的 JVM 下存储的地位是不同的。咱们能够把《java 虚拟机标准》定义的办法区了解成是一个接口,对于不同的 JVM,对于这个”接口“的实现也不同;上面就以 HotSpot 虚拟机为例,就不同 JDK 下的办法区的实现做一下简略的介绍。

JDK1.7 及以前的永恒代

在 JDK8 以前,HotSpot JVM 以永恒代实现办法区,永恒代和老年代,新生代一样在堆上实现,最后设计的初衷就是让垃圾收集器能够像治理堆一样治理这部分内存,这样就能够省去专门为办法区编写代码的工作。然而起初这种实现办法区的形式弊病逐步显示进去

  1. 永恒代在堆中容易造成 OOM,如果它的内存设置过大,那么就会使得老年代和新生代的内存不足,导致频繁 GC;如果它的内存设置过小,因为在程序运行过程中,加载多少类是不可知的,所以在类很多的状况下,它自身由容易呈现 OOM
  2. 永恒代的设计减轻 GC 的压力,影响性能;因为不论是老年代满了还是永恒代满了都会触发 Full GC,造成 STW(stop the world)。
  3. 字符串常量池寄存在永恒代中,容易造成永恒代内存溢出
JDK8 的元空间

既然永恒代又这么多弊病,那么 HotSpot 团队必定得即便止损呀。其实从 JDK1.7 开始永恒代就开始逐步被移除,例如将字符串常量池从永恒代中移出,独自寄存在堆中,在 JDK8 中则齐全移除了永恒代,改用在元空间中实现办法区,而类变量则随着 Class 类对象寄存在堆中。

元空间

元空间不在 jvm 运行时数据区域中,它位于本地内存,所以它的大小受制于本地内存的大小,实践上元空间不会呈现 OOM,它能够有限应用本地内存,但为了不让它如此收缩,JVM 提供了参数来限度它对本地内存的应用。

  • XX:MetaspaceSize,class metadata 的初始空间配额,以 bytes 为单位,达到该值就会触发垃圾收集进行类型卸载,同时 GC 会对该值进行调整:如果开释了大量的空间,就适当的升高该值;如果开释了很少的空间,那么在不超过 MaxMetaspaceSize(如果设置了的话),适当的进步该值。
  • XX:MaxMetaspaceSize,能够为 class metadata 调配的最大空间。默认是没有限度的。
  • XX:MinMetaspaceFreeRatio,在 GC 之后,最小的 Metaspace 残余空间容量的百分比,缩小为 class metadata 调配空间导致的垃圾收集。
  • XX:MaxMetaspaceFreeRatio,在 GC 之后,最大的 Metaspace 残余空间容量的百分比,缩小为 class metadata 开释空间导致的垃圾收集。

    参考 isysc1 的一文读懂元空间和永恒代

    https://juejin.im/post/684490…


运行时常量池

运行时常量池也是办法区的一部分,在 class 文件中有一项信息就是常量池表,这部分内容在 class 文件被加载入内存后,存储在运行时常量池中;除此之外,常量池表中的符号援用被翻译成间接援用后也会存储于此,因为每一个类都有常量池表,所以每一个类型被加载入内存后,都有本人对应的运行时常量池

上面看一下 class 文件中的常量池表

源程序

/**
 * @author sheledon
 */
public class Main {
    private int a;
    private int b;
    private ThreadLocal threadLocal;

    public void test(){System.out.println("欢送来到小白的常识空间");
    }
    public static void main(String[] args) {}}

反编译后常量池表

Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
   #2 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #29            // 欢送来到小白的常识空间
   #4 = Methodref          #30.#31        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #32            // test/Main
   #6 = Class              #33            // java/lang/Object
   #7 = Utf8               a
   #8 = Utf8               I
   #9 = Utf8               b
  #10 = Utf8               threadLocal
  #11 = Utf8               Ljava/lang/ThreadLocal;
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Ltest/Main;
  #19 = Utf8               test
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               args
  #23 = Utf8               [Ljava/lang/String;  
  #24 = Utf8               SourceFile
  #25 = Utf8               Main.java
  #26 = NameAndType        #12:#13        // "<init>":()V
  #27 = Class              #34            // java/lang/System
  #28 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #29 = Utf8               欢送来到小白的常识空间
  #30 = Class              #37            // java/io/PrintStream
  #31 = NameAndType        #38:#39        // println:(Ljava/lang/String;)V
  #32 = Utf8               test/Main
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/System
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Utf8               java/io/PrintStream
  #38 = Utf8               println
  #39 = Utf8               (Ljava/lang/String;)V
 }
字符串常量池

对于字符串常量池在不同 JDK 存储地位的变动和其常量池中寄存内容由对象到援用而引出的 String.intern() 办法和 new String(“abc”) 到底创立了几个对象的探讨也十分有意思,前面我会写一篇文章来阐明。


间接内存

间接内存也不是 JVM 运行时数据区域的一部分,它也位于本地内存,并且还不是《java 虚拟机标准》定义的内存,然而这部分内存被频繁应用,也会导致 OOM。

在 JDK1.4 中新退出的 NIO 类,通过通道和缓存区的 IO 形式能够应用这部分内存。


最初

我是不想当码农的小白,平时会写写一些技术博客,举荐优良的程序员博主给大家还有本人遇到的优良的 java 学习资源,心愿和大家一起提高,独特成长。

以上内容如有谬误,还望指出,感激

公众号点击交换,增加我的微信,一起交换编程呗!

公众号: 小白不想当码农

正文完
 0