乐趣区

关于jvm虚拟机:JVM运行时数据区

本文集体博客地址:JVM 运行时数据区 (leafage.top)

JVM 的运行时数据辨别为:

  1. 程序计数器;
  2. 虚拟机栈;
  3. 本地办法栈;
  4. 堆;
  5. 办法区;

其中 <mark> 堆、办法区是线程共享 </mark> 的,<mark> 程序计数器、虚拟机栈、本地办法栈是线程隔离 </mark> 的,构造图示如下:

1. 程序计数器:

Java 虚拟机的多线程是通过线程轮流切换、调配处理器执行工夫的形式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。

为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,能够看作是以后线程所执行的字节码的行号指示器(存储指向下一条指令的地址),行将要执行的指令代码,各条线程之间计数器互不影响,独立存储。

通过 idea 的 debug 模式能够看到具体的信息:

其特点是:

  • 程序计数器是一块较小的内存空间,线程公有的;
  • 记录非本地办法执行时的字节码指令地址,如果是本地办法,值为 undefined;
  • 惟一一个在《Java 虚拟机标准》中没有规定任何 OutOfMemoryError 状况的区域;

2. 虚拟机栈:

虚拟机栈形容的是 Java 办法执行的线程内存模型:每一个 Java 虚拟机线程创立的同时会创立一个单独的虚拟机栈,其外部保留一个个栈帧(Stack Frame)对应着一次次办法调用。

栈帧:

线程执行办法的内容保留在栈帧中,每一个办法都有本人的栈帧,而对于多层嵌套调用的办法,是依据办法的调用链,向栈中设置栈帧的(栈的操作都是先入,后出),示例如下所示:

同样,通过代码 debug 能够看到其成果:

每个栈帧(Stack Frame)中存储着:

  1. 局部变量表(Local Variables);
  2. 操作数栈(Operand Stack);
  3. 动静链接(Dynamic Linking):指向运行时常量池的办法援用;
  4. 办法返回地址(Return Address):办法失常退出或异样退出的地址 一些附加信息;
  5. 附加信息

栈帧构造示例图如下所示:

3. 本地办法栈:

什么是本地办法?

答: 由其它语言编写的,编译成和处理器相干的机器代码。

本地办法保留在动态链接库中,即.dll(Windows 零碎) 文件中,格局是各个平台专有的(Java 是平台无关的,然而本地办法不是,这也是为什么 JDK 分 Linux、Windows、MacOS 版本)

为什么要应用本地办法?

答: 应用本地办法的起因有以下几点:

  1. 与 Java 环境外交互:有时 Java 利用须要与 Java 里面的环境交互;
  2. 与操作系统交互:JVM 反对 Java 语言自身和运行时库,然而有时仍须要依赖一些底层零碎的反对。通过本地办法,咱们能够实现用 Java 与实现了 jre 的底层零碎交互,JVM 的一些局部就是 C 语言写的。
  3. Sun‘s Java:Sun 的解释器就是 C 实现的,这使得它能像一些一般的 C 一样与内部交互。比方:类 java.lang.Thread 的 setPriority() 的办法是用 Java 实现的,但它实现调用的是该类的本地办法 setPrioruty(),该办法是 C 实现的,并被植入 JVM 外部。

本地办法栈的特点:

  • 线程公有,容许线程固定或者可动静扩大的内存大小(同虚拟机栈一样):

    • 如果线程申请的栈深度大于虚拟机所容许的深度,将抛出 StackOverflowError 异样;
    • 如果 Java 虚拟机栈容量能够动静扩大(HotSpot 虚拟机的栈容量是不能够动静扩大的),当栈扩大时无奈申请到足够的内存会抛出 OutOfMemoryError 异样;
  • 通过本地办法接口来拜访虚拟机外部的运行时数据区;
  • 并不是所有 JVM 都反对本地办法。因为《Java 虚拟机标准》并没有明确要求本地办法栈的应用语言、具体实现形式、数据结构等;
  • HotSpot 虚拟机中,虚拟机栈和本地办法栈合二为一;

4. 堆:

对于大多数利用,Java 堆是 Java 虚拟机治理的内存中最大的一块,被所有线程共享。此内存区域的惟一目标就是寄存对象实例,简直所有的对象实例以及数据都在这里分配内存。

为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域,目标只是为了更好地回收内存,或者更快地分配内存。

老年代的内存大小和年老代的大小默认比例为 2:1,老年代默认的最小值为:<mark> 操作系统运行内存 /64,默认最大内存:操作系统运行内存 /4</mark>;

这个咱们能够通过代码,来看一看是否和形容的统一:

public class HeapMemory {public static void main(String[] args) {
        // 返回 JVM 堆大小
        long initalMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // 返回 JVM 堆的最大内存
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms :" + initalMemory + "M");
        System.out.println("-Xmx :" + maxMemory + "M");

        System.out.println("零碎内存大小:" + initalMemory * 64 / 1024 + "G");
        System.out.println("零碎内存大小:" + maxMemory * 4 / 1024 + "G");
    }
}

运行后果如下:

D:\env\jdk11\bin\java.exe ...
-Xms : 250M
-Xmx : 3996M
零碎内存大小:15G
零碎内存大小:15G

说到堆,那就离不开一个重要的概念,<mark> 垃圾回收 </mark>,JVM 垃圾回收针对不同的分代年龄,有不同的执行策略或算法。

什么是 Minor GC、Major GC、Mixed GC、Full GC?

答: 在进行 GC 时,并非每次都对堆内存(新生代、老年代;办法区)区域一起回收的,大部分时候回收的都是指新生代。

针对 HotSpot VM 的实现,它外面的 GC 依照回收区域又分为两大类:

  1. 局部收集:不是残缺收集整个 Java 堆的垃圾收集。其中又分为:

    • 新生代收集(Minor GC):只是新生代的垃圾收集;
    • 老年代收集(Major GC):只是老年代的垃圾收集;
    • 混合收集(Mixed GC):收集整个新生代以及局部老年代的垃圾收集(目前只有 G1 GC 会有这种行为);
  2. 整堆收集(Full GC):收集整个 Java 堆和办法区的垃圾;

5. 办法区:

办法区存储已被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据;

很多人都更违心把办法区称说为“永恒代”(PermanentGeneration),或将两者一概而论。实质上这两者并不是等价的,因为仅仅是过后的 HotSpot 虚拟机设计团队抉择把收集器的分代设计扩大至办法区,或者说应用永恒代来实现办法区而已,这样使得 HotSpot 的垃圾收集器可能像治理 Java 堆一样治理这部分内存,省去专门为办法区编写内存治理代码的工作。

办法区随着 JDK 的倒退,进行了几次大的变更,最重要的变更产生在 JDK6、JDK7、JDK8,其变动为:

  1. JDK6 –> JDK7: 将字符串常量池,动态变量移出老年代,放到堆中;
  2. JDK7 –> JDK8: 将老年代废除,应用元空间实现,同时将数据存储变更为应用物理内存,不在占用 JVM 内存空间;

变更示例如下图所示:

为什么要替换永恒代,应用元空间?

答: 起因有以下几点:

  1. 为永恒代设置空间大小是很难确定的;

    在某些场景下,如果动静加载类过多,容易产生 Perm 区的 OOM。如果某个理论 Web 工程中,因为性能点比拟多,在运行过程中,要一直动静加载很多类,经常出现 OOM。而元空间和永恒代最大的区别在于,元空间不在虚拟机中,而是应用本地内存,所以默认状况下,元空间的大小仅受本地内存限度

  2. 对永恒代进行调优较艰难;
退出移动版