JVM-内存结构(精简)(JDK1.8)

JVM内存结构(JDK1.8)

Java 虚拟机的内存空间分为 5 个部分:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

一、程序计数器(Program Counter Register)

每个线程都有一个PC寄存器,它用于存储当前线程执行的指令地址。在任何时间点上,每个线程都只能执行一个方法,PC寄存器指向当前执行方法的下一条指令。

二、本地方法栈(Native Method Stack)

Java虚拟机中的一部分内存,用于存储Java程序调用本地方法(Native Method)的相关信息。本地方法是指使用本地语言(如C、C++)编写的方法,通过Java Native Interface(JNI)技术在Java程序中调用。本地方法栈主要用于支持本地方法的执行和管理。 在Java程序中调用本地方法时,JVM会将控制权转移给本地方法栈。本地方法栈使用与Java栈类似的数据结构,每个线程都有自己的本地方法栈。每个本地方法在栈中有一个栈帧,栈帧包含本地方法的参数、局部变量以及用于方法调用和返回的相关信息。

本地方法栈的大小可以在启动JVM时通过设置参数来指定。如果本地方法递归调用过程中超出了本地方法栈的容量,就会抛出"StackOverflowError"错误。同样,如果本地方法栈的容量不足以创建新的栈帧,就会抛出"OutOfMemoryError"错误。

需要注意的是,本地方法栈与虚拟机栈(Java栈)是相对独立的,它们分别用于支持Java方法和本地方法的执行。虚拟机栈用于存储Java方法的调用信息,而本地方法栈用于存储本地方法的调用信息。

总而言之,本地方法栈是Java虚拟机中用于支持本地方法的执行的内存区域,它与虚拟机栈相互独立,用于存储本地方法的参数、局部变量和方法调用信息。

三、虚拟机栈

用于存储线程的方法调用和执行信息。 每个线程在Java虚拟机中都有一个独立的虚拟机栈,用于支持线程的方法调用。每次线程执行一个方法,就会在虚拟机栈中创建一个帧(Frame)。帧包含了方法的局部变量、操作数栈、动态链接、方法返回值等信息。

虚拟机栈的大小可以在启动JVM时通过设置参数来指定。每个栈帧在虚拟机栈中占用一定的内存空间,包括局部变量表、操作数栈等。如果线程的方法调用层次太深,超过了虚拟机栈的容量,就会抛出"StackOverflowError"错误。同样,如果虚拟机栈的容量不足以创建新的栈帧,就会抛出"OutOfMemoryError"错误。

虚拟机栈的结构和操作都是线程私有的,每个线程都有自己独立的虚拟机栈。线程在方法调用时会把参数、返回地址等信息压栈,并在方法执行完毕后将其出栈。这个栈的操作是后进先出(LIFO)的。

总而言之,虚拟机栈是Java虚拟机中用于支持线程方法调用和执行的内存区域。每个线程都有自己独立的虚拟机栈,用于存储方法的局部变量、操作数栈以及方法调用和执行信息。虚拟机栈的大小可以调整,超出容量则会抛出错误。

局部变量表

局部变量表的大小在编译时确定,它是静态分配的。编译器根据方法中定义的局部变量的数量和类型来确定局部变量表的大小。使用大量的局部变量会占用更多的局部变量表空间,并且局部变量表的空间是在方法调用时创建,在方法返回时销毁,所以局部变量表的空间是相对较小的。 局部变量表的作用主要有以下几个方面:

  1. 存储方法的参数和局部变量的值:方法中定义的参数和局部变量的值会被存储在局部变量表中,供方法中的代码使用。
  2. 传递方法的参数:调用方法时,方法的参数会通过局部变量表传递给被调用的方法。
  3. 支持方法的执行:方法中的代码可以使用局部变量表中的值进行计算和操作。
  4. 引用对象的引用:对于引用类型的局部变量,局部变量表中存储的是对象的引用。

操作数栈

操作数栈是一种后进先出(LIFO)的数据结构,它用于存储方法执行过程中的中间结果和操作数。它的大小在编译时就被确定,根据方法中所需的操作数的最大值来分配。

操作数栈的作用主要有以下几个方面:

  1. 存储方法操作的操作数:当方法执行时,会将操作数从局部变量表中加载到操作数栈中,供方法中的指令使用。比如方法调用时传递的参数就会被存储在操作数栈中。
  2. 执行方法的计算操作:方法中的计算操作(如加减乘除等)需要操作数栈来存储和传递计算结果。通过操作数栈,方法可以直接进行计算,而无需再访问局部变量表。
  3. 支持方法的调用和返回:方法调用和返回时,操作数栈用于传递参数和返回值。调用方法时,参数会被压入操作数栈中;方法返回时,返回值会被从操作数栈中弹出。
  4. 临时存储计算结果:在方法执行过程中,可能会有一些中间结果需要暂存。操作数栈可以用于临时存储这些计算结果,以便后续的操作使用。

总而言之,操作数栈是Java虚拟机栈中的一部分,用于存储方法中的操作数和中间结果。它的作用是存储方法的操作数,执行方法的计算操作,支持方法的调用和返回,以及临时存储计算结果。

动态链接

如果被调用的方法无法在编译期被确定下来,只能在运行期将调用的方法的符号引用转为直接引用的过程。

当一个方法被调用时,Java虚拟机将会通过方法调用指令找到被调用方法的符号引用,包括方法名、参数列表以及所属的类。而方法的实际实现代码地址并不在编译时确定,而是在运行时通过动态链接来确定的。

动态链接的过程可以分为两个步骤:

  1. 符号解析(Symbol Resolution):虚拟机将根据方法的符号引用信息,在当前的类或者父类中查找实际的方法实现。虚拟机通过方法的名字、类名和参数列表等信息进行匹配,找到对应的方法。
  2. 方法绑定(Method Binding):一旦找到了方法的实现,虚拟机将会将方法与其实际的代码地址进行绑定,以便在执行时直接调用对应的代码。

动态链接的机制使得Java虚拟机可以根据实际的运行时环境来动态确定方法的实现代码地址,而不是在编译时就确定,这样可以提供更大的灵活性和扩展性。同时,动态链接也使得Java程序可以在运行时适应不同的实现,例如通过接口的多态性,在运行时根据实际的对象类型来动态确定方法的实现。这种机制为Java程序的运行提供了更高的灵活性和动态性。

四、堆

堆是用来存放对象的内存空间,几乎所有的对象都存储在堆中。栈上分配、标量替换优化手段已经导致一些微妙 的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

  1. 堆的结构:堆在物理上是一个连续的内存区域,但在逻辑上分为两个部分:新生代和老年代。新生代又分为Eden区、Survivor区(From和To)。

  2. 对象的分配:在JDK 1.8中,对象的分配是在堆的Eden区进行的。当Eden区没有足够的空间来分配新对象时,将触发一次垃圾回收(Minor GC)。在Minor GC中,存活的对象会被移动到Survivor区,而非存活的对象则会被回收。当存活对象经过多次垃圾回收后仍然存活时,它们会被晋升到老年代。

  3. 垃圾回收:在JDK 1.8中,堆的垃圾回收主要分为两种类型:Minor GC和Full GC。

    • Minor GC:这是针对新生代的垃圾回收。当Eden区满时,触发一次Minor GC。在Minor GC过程中,存活的对象会被复制到Survivor区,而非存活的对象会被回收。经过多次Minor GC后仍然存活的对象会被晋升到老年代。
    • Full GC:这是对整个堆(包括新生代和老年代)的垃圾回收。Full GC会停止应用程序的执行,并且会比Minor GC消耗更多的时间。Full GC的目标是回收整个堆,包括从新生代晋升到老年代的对象。
  4. 堆大小设置:在JDK 1.8中,可以使用命令行参数来设置堆的大小。其中,-Xms参数用于设置堆的初始大小,-Xmx参数用于设置堆的最大大小。通过合理调整这两个参数的值,可以优化程序的性能。

总结来说,JDK 1.8中的堆是用于存储对象实例和数组的内存区域,分为新生代和老年代。对象的分配发生在新生代的Eden区,垃圾回收包括Minor GC和Full GC两种类型。通过合理设置堆的大小,可以优化程序的性能。

在JDK 1.8中,堆是按照分代的方式进行管理的。具体而言,JDK 1.8中的堆分为:新生代(Young Generation)、老年代(Old Generation)。

  1. 新生代(Young Generation):新生代用于存储新创建的对象。它又分为三个部分:Eden区、Survivor区(From区和To区)。当程序创建新的对象时,这些对象首先被分配到Eden区。如果Eden区没有足够的空间来存储新的对象,就会触发一次垃圾回收(Minor GC)。在Minor GC中,存活的对象会被复制到Survivor区(其中一个区叫做From区,另一个区叫做To区),而非存活的对象会被回收。经过多次Minor GC后仍然存活的对象会被晋升到老年代。
  2. 老年代(Old Generation):老年代用于存储存活时间较长的对象。当存活对象经过多次垃圾回收后仍然存活时,它们会被晋升到老年代。老年代的垃圾回收通常是通过Full GC(Full Garbage Collection)来进行的。Full GC会停止应用程序的执行,并且会比Minor GC消耗更多的时间。Full GC的目标是回收整个堆,包括从新生代晋升到老年代的对象。

五、方法区

如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。 Java虚拟机的方法区(Method Area)是一块用于存储类结构信息、常量池、静态变量、即时编译器编译后的代码等数据的区域。 在JDK 1.8之前,方法区被实现为永久代(Permanent Generation)。永久代是虚拟机的一部分,并且它的大小是固定的。在默认情况下,永久代的大小是 64MB,在不同的虚拟机实现中可能会有所不同。

然而,在JDK 1.8之后,永久代被元空间(Metaspace)所取代。元空间是使用本地内存(Native Memory)来存储类元数据的区域,不再依赖于虚拟机堆内存的大小限制。

元空间的大小可以根据应用程序的需要动态地增长或缩小,而且它的大小只受系统内存的限制。这样可以避免了永久代的一些问题,比如永久代大小设置不合理会导致OOM异常等。

需要注意的是,元空间与永久代在功能和作用上是相似的,都是用于存储类结构信息等相关数据。但是,它们的实现方式不同,JDK 1.8及之前的版本使用永久代,而JDK 1.8及之后的版本使用元空间。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。用于存储编译期生成的各种字面量和符号引用。

运行时常量池包含了编译期生成的各种字面量,如字符串常量、基本类型的常量值等。它还包含一些类、方法和字段的符号引用,用于在运行时解析对它们的引用。

与字节码文件中的常量池不同,运行时常量池可以动态地扩展,它的内容可以在类加载过程中被加载和修改。这意味着在运行时可以对运行时常量池进行某些操作,比如动态生成字符串、通过反射修改方法、字段的值等。

需要注意的是,运行时常量池是每个类或接口独有的,它们在内存中的存储位置是各自的方法区。运行时常量池的存在使得Java具有了一些动态语言的特性,如动态加载类、动态生成类和字节码等。

相关推荐
为将者,自当识天晓地。11 分钟前
c++多线程
java·开发语言
daqinzl19 分钟前
java获取机器ip、mac
java·mac·ip
激流丶35 分钟前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue38 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式1 小时前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink2 小时前
HTTP动词与状态码
java
ktkiko112 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田2 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue