关于jvm:深入理解Java虚拟机-Java运行时数据区域

2次阅读

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

文章目录

    1. 运行时数据区域
        1.1 程序计数器
        1.2 Java 虚拟机栈
        1.3 本地办法栈
        1.4 Java 堆
        1.5 办法区
        1.6 运行时常量池
    2. 间接内存

本文参考于《深刻了解 Java 虚拟机》
  1. 运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它所治理的内存划分为若干个不同的数据区域。其包含:程序计数器、Java 虚拟机栈、本地办法栈、Java 堆和办法区。

在这里插入图片形容
1.1 程序计数器

(1)、什么是程序计数器?

程序计数器(Program Counter Register)是一块较小的内存空间,它能够看作是以后线程所执行的字节码的行号指示器。在物理层面上是由寄存器实现的。它用于存储下一条所要执行的 JVM 指令的执行地址。

(2)、为什么须要程序计数器?

在 Java 虚拟机的概念模型里,字节码解释器工作时就是通过扭转这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖这个计数器来实现。它的核心作用就是:用于存储下一条所要执行的 JVM 指令的执行地址。由执行引擎执行下一条 JVM 指令。

图示阐明

在这里插入图片形容

(3)、程序计数器的相干特点

它是一块很小的内存空间简直能够忽略不计,也是运行速度最快的存储区域。在 JVM 标准中,每个线程都有它本人的程序计数器,是线程公有的,生命周期与线程生命周期保持一致。如果线程正在执行的是一个 Java 办法,这个计数器记录的是正在执行的虚拟机字节码指令的地 址;如果正在执行的是本地(Native)办法,这个计数器值则应为空(Undefined)。此内存区域是惟一一个在《Java 虚拟机标准》中没有规定任何 OutOfMemoryError 状况的区域。

(4)、应用程序计数器存储字节码指令地址有什么用?为什么应用程序计数器记录以后线程的执行地址呢?

因为 CPU 须要不停的切换各个线程,这时候切换回来当前,就得晓得接着从哪儿开始继续执行。JVM 的字节码解释器就须要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令。

(5)、程序计数器为什么是线程公有的?

为了线程切换后能复原到正确的执行地位,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程公有”的内存。

(6)、Java 代码的执行过程

首先 Java 代码会被转换为 JVM 指令(二进制字节码,进而 Java 能够实现跨平台运行)而后解释器会对 JVM 指令进行转换,从而转换为机器码给 CPU 执行。与此同时,程序计数器的中的值指向下一条须要执行的 JVM 指令的地址。

学海无涯,一次分享的知识点是无限的的,然而学习是有限的,我这有一份 Java 中高级外围常识全面解析,内含业内大佬笔记,如有须要,戳此处收费支付。

1.2 Java 虚拟机栈

(1)、什么是 Java 虚拟机栈?

与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stack)也是线程公有的,它的生命周期与线程雷同(即随着线程的创立而创立,随着线程的沦亡而沦亡)。虚拟机栈形容的是 Java 办法执行的线程内存模型:每个办法被执行的时候,Java 虚拟机都会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

(2)、为什么须要虚拟机栈?

每个办法被执行的时候,Java 虚拟机都会同步创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静连贯、办法进口等信息。每一个办法被调用直至执行结束的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

(3)、虚拟机栈由什么组成?

虚拟机栈由一个又一个的栈帧组成,然而每个线程只有一个流动栈帧(即以后正在执行的办法),如果一个办法须要执行,则相应的栈帧须要入栈;如果一个办法执行完结,则相应的栈帧须要出栈。

在这里插入图片形容

一个栈帧蕴含以下几局部:

局部变量表:局部变量表寄存了编译期可知的各种 Java 虚拟机根本数据类型(boolean、byte、char、short、int、float、long、double)、对象援用(reference 类型,它并不等同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或者其余与此对象相干的地位)和 returnAddress 类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来示意,其中 64 位长度的 long 和 double 类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间实现调配,当进入一个办法时,这个办法须要在栈帧中调配多大的局部变量空间是齐全确定的,在办法运行期间不会扭转局部变量表的小。请读者留神,这里说的“大小”是指变量槽的数量,虚拟机真正应用多大的内存空间(譬如依照 1 个变量槽占用 32 个比特、64 个比特,或者更多)来实现一个变量槽。操作数栈:也能够称之为表达式栈(Expression Stack),在办法执行过程中,依据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)。某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,应用它们后再把后果压入栈,比方:执行复制、替换、求和等操作。操作数栈,次要用于保留计算过程的两头后果,同时作为计算过程中变量长期的存储空间。

在这里插入图片形容

动静链接
办法进口等信息

(4)、该区域常呈现的两种异样情况

StackOverflowError 异样:如果 Java 虚拟机栈的容量不能够动静拓展,线程申请的栈深度大于虚拟机所容许的深度,将抛出 StackOverflowError 异样。OutOfMemoryError 异样:如果 Java 虚拟机栈的容量能够动静拓展,当栈扩大时无奈申请到足够的内存会抛出 OutOfMemoryError 异样

(5)、Java 办法的完结形式

return 语句
抛出异样

1.3 本地办法栈

(1)、什么是本地办法栈?

Java 虚拟机栈为虚拟机执行 Java 办法(也就是字节码)服务,而本地办法栈则是为虚拟机应用到的本地(Native)办法服务的。

补充概念

本地办法:当 Java 虚拟机须要和操作系统底层进行交互的时候须要调用 C 或者 C ++ 办法,而所调用的 C 或者 C ++ 办法就是本地办法。

在这里插入图片形容

(2)、本地办法栈的作用

和 Java 虚拟机栈作用相似,本地办法栈用于寄存相干的本地办法所被调用产生的栈帧。当一个本地办法被调用时,相应的栈帧入栈;当一个本地办法调用完结时,相应的栈帧出栈。而本地办法栈的栈帧寄存的是局部变量表、操作数栈、动静链接和办法进口等信息。

(3)、本地办法栈可能呈现的异样情况

本地办法栈也会在栈深度溢出或者栈扩大失败时别离抛出 StackOverflowError 和 OutOfMemoryError 异样
1.4 Java 堆

(1)、什么是 Java 堆?

Java 堆(Java Heap)是虚拟机所治理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,所以此处的对象会波及线程平安问题。在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,Java 世界里“简直”所有的对象实例都在这里分配内存。

简直一词的阐明

而这里笔者写的“简直”是指从实现角度来看,随着 Java 语言的倒退,当初曾经能看到些许迹象表明日后可能呈现值类型的反对,即便只思考当初,因为即时编译技术的提高,尤其是逃逸剖析技术的日渐弱小,栈上调配、标量替换优化伎俩曾经导致一些奥妙的变动悄悄产生,所以说 Java 对象实例都调配在堆上也慢慢变得不是那么相对了。

(2)、GC 堆阐明

Java 堆是垃圾收集器治理的内存区域,因而一些材料中它也被称作“GC 堆”。从回收内存的角度看,因为古代垃圾收集器大部分都是基于分代收集实践设计的,所以 Java 堆中常常会呈现“新生代”“老年代”“永恒代”“Eden 空间”“From Survivor 空间”“To Survivor 空间”等名词。这些区域划分仅仅是一部分垃圾收集器的独特个性或者说设计格调而已,而非某个 Java 虚拟机具体实现的固有内存布局,更不是《Java 虚拟机标准》里对 Java 堆的进一步粗疏划分。

在这里插入图片形容

(3)、常见的异样:OutOfMemoryError

Java 堆既能够被实现成固定大小的,也能够是可扩大的,不过以后支流的 Java 虚拟机都是依照可扩
展来实现的(通过参数 -Xmx 和 -Xms 设定)。如果在 Java 堆中没有内存实现实例调配,并且堆也无奈再扩大时,Java 虚拟机将会抛出 OutOfMemoryError 异样。
1.5 办法区

(1)、什么是办法区?

办法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载
的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。

(2)、办法区的实现形式

办法只是一个抽象概念,只是定义了相干的标准。就好比办法区是抽象类,定义了相应的标准,而它的实现形式(永恒代或者元空间)就好比是实现了该抽象类的子类。

永恒代(JDK1.7 之前)

在这里插入图片形容

元空间(JDK1.8 之后)

在这里插入图片形容

(3)、永恒代到元空间的变动

到了 JDK 7 的 HotSpot,曾经把本来放在永恒代的字符串常量池、动态变量等移出到堆中,而到了 JDK 8,终于齐全废除了永恒代的概念,改用与 JRockit、J9 一样在本地内存中实现的元空间(Metaspace)(次要是类型信息)来代替。

(4)、为什么要将永恒代更换为元空间?

当年应用永恒代来实现办法区的决定并不是一个好主见,这种设计导致了 Java 利用更容易遇到 内存溢出的问题(永恒代有 -XX:MaxPermSize 的下限,即便不设置也有默认大小),无奈进行调整;而元空间应用的是间接内存(操作系统的内存),尽管还是可能会导致元空间内存溢出,然而概率变低了。

元空间溢出时的谬误:java.lang.OutOfMemoryError: MetaSpace

1.6 运行时常量池

(1)、什么是运行时常量池?

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

(2)、常见异样情况

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

(3)、运行时常量池的地位

JDK1.7 之前,办法区是由永恒代实现的,所以运行时常量池也位于永恒代外部。JDK1.7 时,办法区中的运行时常量池中字符串常量池从办法区移出到堆中,而运行时常量池的残余局部还留在办法区中,办法区的实现形式为永恒代。JDK1.8 时,永恒代被元空间代替,而此时字符串常量池还留在堆中,运行时常量池还留在办法区中。

所以运行时常量池始终处于办法区中,只不过办法区的实现形式产生了扭转,同时随着实现形式产生了扭转,它的理论物理地址由堆中转移到了本地内存中。

  1. 间接内存

(1)、什么是间接内存?

间接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机标准》中
定义的内存区域。然而这部分内存也被频繁地应用,而且也可能导致 OutOfMemoryError 异样呈现。本机间接内存的调配不会受到 Java 堆的限度,然而,既然是内存就会受到本机总内存大小以及处理器寻址空间的限度。

(2)、新引入的 NIO 类

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

学海无涯,一次分享的知识点是无限的的,然而学习是有限的,我这有一份 Java 中高级外围常识全面解析,内含业内大佬笔记,如有须要,戳此处收费支付。

正文完
 0