jvm基础知识总结

发布时间:2023年12月18日

1.jvm的结构

1.1 类加载器

? ? ?启动类加载器(Bootstrap Class Loader): 负责加载Java的核心类库,通常是由JVM实现提供的。

? ? 启动类加载器的一些信息:

? ? 1.加载顺序:启动加载类是虚拟机自身的一部分,他是虚拟机的第一个类加载器,在虚拟机启动时,她会首先初始化启动类加载器,然后启动类加载器会加载核心类库,包括java.lang.Object等类。

? ?2.实现细节:

? ? ?启动类加载器通常是由虚拟机实现的本地代码(Native Code)来实现的,并不是用Java语言实现的。这是因为启动类加载器需要负责加载虚拟机运行所需的核心类,而这些类在虚拟机启动之前并不存在于任何类路径之中。

3.类路径

启动类加载器加载的类通常来自于Java的核心类库,这些类库的路径通常由虚拟机的实现确定,并且用户无法改变。在Oracle JDK中,启动类加载器加载的类路径通常包含了jre/lib目录下的核心JAR文件。

4.父加载器:

启动类加载器没有父加载器,这是因为它位于类加载器层次的最顶层。在Java中,类加载器之间通过组合的方式形成了层次结构,而启动类加载器是这个层次结构的最顶层。

??扩展类加载器(Extension Class Loader): 负责加载Java的扩展库,一般位于$JAVA_HOME/lib/ext目录下。

? ? 1.加载顺序:

? ? ??扩展类加载器是在启动类加载器之后,但在应用程序类加载器之前加载的。当Java虚拟机启动时,扩展类加载器会被初始化,然后它会加载$JAVA_HOME/lib/ext目录下的JAR文件。

? 2.类路径

扩展类加载器的类路径通常包含了$JAVA_HOME/lib/ext目录下的JAR文件。这个目录是用于存放Java的扩展库的,默认情况下,Java虚拟机会自动加载这个目录下的扩展库。

3.父加载器:

扩展类加载器的父加载器是启动类加载器。这意味着扩展类加载器无法加载启动类加载器加载的类,但可以加载核心类库和扩展库。

4.特殊性质:

扩展类加载器通常是用Java语言实现的,并且在Java虚拟机启动时由虚拟机自动创建。与启动类加载器不同,用户可以通过Java代码获取对扩展类加载器的引用。

5。目的:

扩展类加载器的主要目的是加载Java扩展库,这些库通常包含了供Java平台的扩展功能使用的类。这些类并不是Java平台的核心类,但它们可以通过扩展类加载器来让应用程序访问。

应用程序类加载器(Application Class Loader): 负责加载应用程序的类,是默认的类加载器。

  1. 加载顺序: 应用程序类加载器是类加载器层次结构中的最底层,它在扩展类加载器之后被初始化。当Java虚拟机启动时,应用程序类加载器会被初始化,并且它通常会从类路径中加载应用程序的类。

  2. 类路径: 应用程序类加载器的类路径包括了用户定义的类路径,通常是通过命令行或配置文件指定的。这个类路径包括了应用程序的类和第三方类库。

  3. 父加载器: 应用程序类加载器的父加载器是扩展类加载器。这意味着应用程序类加载器可以加载扩展库中的类,同时也可以加载应用程序中的类。

  4. 特殊性质: 应用程序类加载器通常是用Java语言实现的,而且它是用户可以直接引用和使用的类加载器。在Java中,可以通过ClassLoader.getSystemClassLoader()方法获取对应用程序类加载器的引用。

  5. 自定义类加载器: 如果应用程序需要实现特殊的类加载行为,可以通过自定义类加载器来扩展应用程序类加载器。这样可以实现一些定制的类加载策略,比如从数据库中加载类定义等。

1.2 运行时数据区

方法区(Method Area)

  1. 存储内容: 方法区主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器生成的代码等数据。类信息包括类的结构信息、字段、方法信息等。

  2. 位置: 方法区通常位于堆的逻辑上方,是堆的一部分,但在物理上可以不与堆相邻。在一些虚拟机的实现中,方法区也被称为"非堆"。

  3. 运行时常量池: 方法区包含一个运行时常量池,它用于存储类文件中的常量池数据,在类加载后,将其中的符号引用转换为直接引用。

  4. 永久代与元空间: 在Java 7及以前的版本,方法区被称为永久代(Permanent Generation)。从Java 8开始,永久代被元空间(Metaspace)所取代,元空间仍然属于方法区的一部分,但不再有固定的大小,而是通过本地内存动态分配。

  5. 内存溢出: 方法区可以发生内存溢出错误,特别是在使用大量动态生成类的场景下。一旦方法区无法满足类加载的需求,就会抛出OutOfMemoryError异常。

  6. 垃圾回收: 方法区是Java虚拟机的内存区域之一,但并非所有的垃圾回收都会涉及到方法区。在一些虚拟机中,常量池的回收可能会触发垃圾回收,但具体实现因虚拟机而异。

堆(Heap)

  1. 存储对象: 堆主要用于存储Java程序中创建的对象实例。每个对象在堆上分配一块内存区域,这块内存的大小取决于对象的类型和大小。

  2. 动态分配: 堆是一个动态分配的内存区域,它不需要程序员手动管理内存。Java的垃圾回收器负责在堆中回收不再被引用的对象,释放其占用的内存空间。

  3. 堆的大小: 堆的大小可以通过启动Java虚拟机时的参数进行配置。通常,堆的大小会影响到应用程序的性能和内存利用率。堆的大小由初始堆大小(-Xms参数)和最大堆大小(-Xmx参数)来控制。

  4. 分代模型: 堆可以分为新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)或元空间(Metaspace)等不同的区域。新生代主要用于存储新创建的对象,老年代用于存储生命周期较长的对象,永久代或元空间用于存储类信息、常量池等。

  5. 垃圾回收: Java的垃圾回收器负责在堆中回收不再使用的对象。通常,垃圾回收采用不同的算法,如标记-清除、复制、标记-整理等,以提高垃圾回收的效率。

  6. 内存溢出: 当堆中没有足够的空间来分配新对象时,会发生内存溢出错误(OutOfMemoryError)。这通常是因为堆的大小配置不足,或者程序中存在内存泄漏。

栈(Stack)

栈(Stack)是Java虚拟机内存管理中的另一个重要区域,主要用于存储线程的局部变量、操作数栈、方法调用和返回信息。每个线程在运行时都有自己的栈,用于追踪方法的调用和执行。以下是有关栈的一些关键信息:

  1. 线程私有: 每个线程都有自己的栈,栈是线程私有的内存区域。这意味着一个线程无法直接访问其他线程的栈。

  2. 栈帧: 栈由一个个的栈帧(Stack Frame)组成,每个栈帧对应一个方法的调用。栈帧包含了局部变量表、操作数栈、方法返回地址等信息。

  3. 局部变量表: 用于存储方法的参数和局部变量。这些变量的生命周期与方法的调用周期相对应。

  4. 操作数栈: 用于执行方法时的计算。操作数栈是一个后进先出(LIFO)的栈,用于存储中间计算结果和方法调用过程中的临时数据。

  5. 方法调用和返回: 每个方法的调用都会在栈上创建一个新的栈帧,其中包含方法的参数、局部变量等信息。方法执行完毕后,对应的栈帧会被弹出,返回到调用方法的栈帧中。

  6. 栈的大小: 栈的大小可以通过启动Java虚拟机时的参数进行配置。栈的大小影响着程序的调用深度,如果调用的方法层次很深,可能需要适当增大栈的大小,以防止栈溢出。

  7. 内存分配速度: 栈上的内存分配和回收速度比堆要快,因为它只需简单地移动栈顶指针,而不需要进行复杂的垃圾回收算法。

  8. 栈上分配: 一些现代的Java虚拟机对于一些局部变量的对象可以选择在栈上分配,而不是在堆上分配,以提高对象的访问速度。

总体而言,栈在Java虚拟机中扮演着重要的角色,用于支持方法的调用和执行。了解栈的原理对于理解Java程序的运行机制以及进行性能调优都是至关重要的。

程序计数器(Program Counter

程序计数器(Program Counter)是Java虚拟机内存结构中的一部分,用于存储当前线程执行的字节码指令地址或行号。每个线程都有自己的程序计数器,它是线程私有的。以下是有关程序计数器的一些关键信息:

  1. 存储内容: 程序计数器存储了当前线程正在执行的字节码指令的地址或行号。对于Java虚拟机来说,它通常存储的是字节码的地址。

  2. 线程私有: 每个线程都有自己的程序计数器,它是线程私有的内存区域。这意味着不同的线程之间的程序计数器互不干扰。

  3. 指示下一条指令: 程序计数器是一个指针,它指示了当前线程将要执行的字节码指令。在线程切换时,程序计数器的值会被恢复,以便继续执行。

  4. 方法调用: 在Java中,程序计数器在方法调用时起着重要作用。它记录了方法调用的位置,以便在方法执行完毕后能够回到调用点继续执行。

  5. 异常处理: 程序计数器也在异常处理时发挥作用。当抛出异常时,程序计数器记录了异常抛出的位置,以便确定异常处理的入口点。

  6. 线程恢复: 程序计数器的值在线程切换时被保存和恢复。这确保了线程能够从上一次执行的地方继续执行,而不会出现混乱或错误。

  7. Native方法: 对于Native方法,程序计数器的值可以是undefined。这是因为Native方法不是用Java字节码实现的,因此程序计数器不需要记录具体的地址。

本地方法栈(Native Method Stack)

  1. Native方法: 本地方法是使用非Java语言编写的方法,通常是由C、C++等语言编写,并通过JNI在Java程序中调用。这些方法在本地方法栈中执行。

  2. 线程私有: 每个线程都有自己的本地方法栈,与Java栈类似,它是线程私有的内存区域。不同线程之间的本地方法栈互不干扰。

  3. 栈帧结构: 本地方法栈的栈帧结构与Java栈类似,包含局部变量表、操作数栈等信息,但它存储的是Native方法的信息。

  4. 内存管理: 与Java栈一样,本地方法栈也需要进行内存管理,包括栈帧的分配和释放。由于Native方法通常由非Java语言编写,内存管理可能依赖于底层语言的特性。

  5. 异常处理: 本地方法栈同样负责处理Native方法中的异常。在Native方法执行时,如果发生异常,本地方法栈会负责处理并传递给Java代码。

  6. 栈的大小: 本地方法栈的大小可以通过启动Java虚拟机时的参数进行配置。与Java栈相似,栈的大小影响着Native方法的调用深度,需要根据实际需求进行配置。

  7. 栈帧的创建和销毁: 本地方法栈中的栈帧在Native方法调用时创建,在方法返回时销毁。栈帧的创建和销毁过程由JNI实现。

?总结:

线程私有的:本地方法栈、程序计数器、栈

1.3执行引擎

  • 解释器(Interpreter): 将字节码逐行解释为机器代码并执行。
  • 即时编译器(Just-In-Time Compiler,JIT): 将整个方法体编译为本地机器代码,提高执行效率。

文章来源:https://blog.csdn.net/qq_31273845/article/details/134915004
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。