JVM simple

September 24, 2023 作者: yijianhao 分类: 浏览: 129 评论: 0

Jvm简单示意图

+------------------------------------------------------+
|                    Java Virtual Machine               |
+------------------------------------------------------+
|                                                      |
|    +----------------------+       +----------------+  |
|    |    Class Loader     |<----->|    Execution   |  |
|    |                     |       |     Engine     |  |
|    +----------------------+       +----------------+  |
|                                                      |
|    +----------------------+       +----------------+  |
|    |    Runtime Data     |       |   Native Method |  |
|    |       Areas         |       |     Interface  |  |
|    +----------------------+       +----------------+  |
|         ^                                           |
|         |                                           |
|         |                                           |
|         |                                           |
|         |                                           |
|         v                                           |
|    +----------------------+                          |
|    |  Garbage Collector  |                          |
|    |                     |                          |
|    +----------------------+                          |
|                                                      |
+------------------------------------------------------+

主要结构

Java虚拟机(Java Virtual Machine,JVM)的内部结构可以分为以下几个主要组成部分:

  • 类加载器(Class Loader):负责加载Java类的字节码文件,并将其转换为可执行的代码。类加载器将类加载到运行时数据区域中的方法区。

  • 运行时数据区域(Runtime Data Areas):是JVM用于执行Java程序的内存区域,包括以下几个部分:

    • 方法区(Method Area):用于存储类的结构信息,如类的字段、方法、构造函数等。
    • 堆(Heap):用于存储Java对象实例。
    • Java栈(Java Stack):用于存储方法的局部变量、操作数栈、方法调用和返回信息。
    • 本地方法栈(Native Method Stack):用于支持Java程序调用本地方法(使用其他编程语言编写的方法)。
    • 程序计数器(Program Counter):指向当前线程正在执行的指令的地址。
    • 直接内存(Direct Memory):用于存储直接缓冲区,由Java NIO提供支持。
  • 执行引擎(Execution Engine):负责执行字节码指令。它将字节码解释成具体的机器指令,或者使用即时编译器(Just-In-Time Compiler,JIT)将字节码编译成本地机器码。

  • 本地方法接口(Native Method Interface):允许Java代码调用本地方法库,实现与底层系统的交互。

  • 垃圾回收器(Garbage Collector):负责自动回收不再使用的Java对象,并释放内存空间。

运行时数据区域

当Java程序在JVM上运行时,JVM会为每个线程创建一组运行时数据区域来存储程序执行所需的数据。以下是JVM的运行时数据区域及其功能的简要说明:

  1. 方法区(Method Area)(1.8开始被元数据取代):

    • 用于存储类的结构信息,如类的字段、方法、常量池等。
    • 在JVM启动时被创建,被所有线程共享。
    • 存储的内容包括类的字节码、静态变量、常量等。
  2. 堆(Heap):

    • 用于动态分配的对象实例和数组的存储。
    • 在JVM启动时被创建,被所有线程共享。
    • 所有通过关键字 new 创建的对象都存储在堆中。
    • 垃圾收集器负责在堆上进行垃圾回收。
  3. Java栈(Java Stack):

    • 每个线程都有自己的Java栈
    • 用于存储方法调用和局部变量。
    • 每个方法在执行时都会创建一个栈帧,包含方法的参数、局部变量和部分运算结果。
    • 栈帧的大小在编译时确定,并且可以根据方法的需要动态扩展或缩小。
  4. 本地方法栈(Native Method Stack):

    • 与Java栈类似,但用于支持Java调用本地方法(非Java代码)。
    • 每个线程都有自己的本地方法栈。
  5. 程序计数器(Program Counter Register):

    • 每个线程都有一个程序计数器。
    • 存储当前线程正在执行的字节码指令地址。
    • 在任何时候,一个线程都只会执行一个方法的代码,程序计数器指向当前线程正在执行的方法的下一条指令。
  6. 元数据区(Metaspace) 元数据区是Java 8引入的一块内存区域,用于存储类的元数据信息。它取代了传统的方法区(包括永久代)的概念。元数据区具有动态调整大小的能力,内存分配在本地内存中,提供了更高的灵活性和效率。

一个简单的Java代码示例:

public class RuntimeDataAreaExample {
    public static void main(String[] args) {
        // 声明一个局部变量
        int a = 10;
        
        // 创建一个对象
        Object obj = new Object();
        
        // 调用一个方法
        printMessage("Hello, JVM!");
    }
    
    public static void printMessage(String message) {
        // 打印消息
        System.out.println(message);
    }
}

在上述示例中,当main方法被调用时,JVM会为该线程创建一个Java栈,并在栈上为main方法创建一个栈帧。栈帧包含局部变量a的值、引用变量obj的值以及方法返回地址等信息。

在堆上,JVM会为new Object()创建一个对象实例,并将该对象的引用存储在obj变量中。

printMessage方法被调用时,JVM会为该线程创建一个新的栈帧,并将方法的参数message的值传递给新的栈帧。在该方法执行期间,局部变量表中会包含参数message的值。

Heap中的各种代

在Java虚拟机(JVM)的内存结构中,堆(Heap)被划分为不同的区域,其中之一就是老年代。

在Java中,对象的生命周期可以分为新生代(Young Generation)和老年代(Old Generation)两个阶段。当对象被创建时,它会被分配到新生代的Eden区域。如果对象经过一次垃圾回收后仍然存活,它将会被移动到Survivor区域,而不会直接进入老年代。当对象在Survivor区域中经过多次垃圾回收后仍然存活,它就会被晋升到老年代。

  • 新生代(Young Generation):新生代是JVM堆内存的一部分,用于存储新创建的对象。它被划分为Eden区域和Survivor区域(通常有两个,From和To)。大部分对象在新生代中被创建,并且它们的生命周期较短。新生代使用垃圾回收算法,如复制算法,频繁地进行垃圾回收,清理无用的对象。

  • 老年代(Old Generation):老年代是JVM堆内存的另一部分,用于存储长时间存活的对象。当对象在新生代经过多次垃圾回收后仍然存活,它们会被晋升到老年代。老年代通常具有较大的内存空间,并且使用垃圾回收算法,如标记-清除算法或标记-整理算法,来清理无用的对象。

  • 永久代(Permanent Generation)永久代是Java 8之前的JVM内存区域,是方法区的实现,并不是Heap区中的,本质上,方法区和永久代并不等价。仅是对hotspot而言的。《Java虚拟机规范》对如何实现方法区,不做统一要求,用于存储类的元数据信息、常量池、静态变量等。它是堆内存的一部分,但与新生代和老年代不同,它具有固定的大小,并且不会被垃圾回收器自动清理。永久代经常会成为内存溢出的原因之一。从Java 8开始,永久代被取消并由元空间(Metaspace)取代。元空间也是JVM堆内存的一部分,用于存储类的元数据信息,但与永久代相比,它具有动态调整大小的特性,并且内存分配在本地内存中。

新生代中的区域

  • Eden区域(Eden Space):这是对象分配的初始区域。当对象被创建时,它们会被分配到Eden区域。通常,大部分对象在很短的时间内就会变成垃圾。

  • Survivor区域(Survivor Space):Survivor区域是用于存储从Eden区域中幸存下来的对象的区域。当进行垃圾回收时,存活的对象会被移动到Survivor区域。Survivor区域通常有两个,分别称为From区和To区,它们交替使用。

  • 分配区域(Allocation Area / TLAB):分配区域是每个线程私有的对象分配缓冲区。为了提高对象分配的效率,JVM会为每个线程预先分配一块较小的空间用于对象的分配。这个区域也被称为Thread-Local Allocation Buffer(TLAB)。

老年代

老年代一般没有像新生代那样的区域,老年代中存储的对象通常是具有较长生命周期的对象,可能是全局变量、长时间存活的实例等。由于老年代中的对象存活时间较长,垃圾回收的频率相对较低,但回收过程可能涉及更复杂的算法和策略。

老年代的大小通常比新生代要大,因为长时间存活的对象更多。合理配置老年代的大小对于应用程序的性能和垃圾回收效率都有重要影响。如果老年代空间不足,会触发更频繁的垃圾回收,可能导致较长的停顿时间和性能下降。

需要注意的是,老年代并不是所有的垃圾回收器都具备的概念,某些垃圾回收器可能没有明确的老年代区域,或者使用其他方式管理长时间存活的对象。老年代的存在主要是为了优化长生命周期对象的回收效率和减少垃圾回收的频率。

垃圾回收机制

当一个对象在Java程序中不再被引用时,它就成为了垃圾,垃圾回收机制(Garbage Collection)是Java虚拟机(JVM)自动管理内存的一种机制,用于自动释放不再使用的对象所占用的内存空间。以下是关于垃圾回收机制的一些要点:

  • 标记-清除算法:垃圾回收机制通常使用标记-清除算法来识别和回收不再使用的对象。该算法分为两个阶段:标记阶段和清除阶段。

    • 标记阶段:垃圾回收器从根对象(如栈中的引用、静态变量等)开始,通过可达性分析算法遍历对象图,标记所有可达的对象。
    • 清除阶段:垃圾回收器清除未标记的对象,释放它们占用的内存空间。
  • 引用判断:在标记阶段,垃圾回收机制通过判断对象是否可达来确定是否标记为垃圾。常见的引用类型包括强引用、软引用、弱引用和虚引用,它们有不同的生命周期和影响垃圾回收的行为。

  • 垃圾回收器:JVM中的垃圾回收器负责执行垃圾回收操作。不同的垃圾回收器采用不同的算法和策略,如串行回收器、并行回收器、并发回收器等。垃圾回收器的选择和配置可以通过JVM参数进行调整。

  • 垃圾回收的时机:垃圾回收的时机由JVM决定,通常在以下情况下触发垃圾回收:

    • 当堆内存不足时,即将发生OOM(Out of Memory)时,垃圾回收器会被触发,尝试回收未使用的对象以释放内存。
    • 在程序空闲或暂停时,垃圾回收器可能会运行,对堆中的对象进行回收。
  • 对象终结(Finalization):垃圾回收机制通常与对象终结机制(Finalization)结合使用。通过重写finalize()方法,对象可以在垃圾回收之前执行一些清理操作。然而,finalize()方法的使用已不推荐,因为它的执行时机不确定且可能导致性能问题。

垃圾回收机制使得开发者无需手动管理内存,减轻了内存泄漏和野指针等问题的风险。然而,垃圾回收也会引入一定的性能开销,因此在特定场景下,合理地配置和调优垃圾回收策略是重要的。

某些垃圾回收调优

垃圾回收器的选择和配置可以通过以下一些常用的JVM参数进行调整:

  • -XX:+UseSerialGC:启用串行垃圾回收器。适用于单线程环境或小型应用程序,回收过程会暂停所有应用线程。

  • -XX:+UseParallelGC:启用并行垃圾回收器。适用于多核处理器的服务器环境,回收过程会使用多个线程并行进行。

  • -XX:+UseParallelOldGC:启用并行老年代垃圾回收器。与并行垃圾回收器类似,但在老年代的回收过程中使用多个线程并行进行。

  • -XX:+UseConcMarkSweepGC:启用并发标记-清除垃圾回收器。回收过程与应用程序并发执行,不会长时间停顿应用线程,适用于对停顿时间要求较高的场景。

  • -XX:+UseG1GC:启用G1(Garbage-First)垃圾回收器。G1是一种面向服务器的垃圾回收器,可以在可接受的停顿时间内高效回收大堆内存。

除了选择垃圾回收器,还可以通过以下参数对垃圾回收器的行为进行配置:

  • -Xms和-Xmx:设置堆内存的初始大小和最大大小。适当调整堆大小可以影响垃圾回收的频率和性能。

  • -XX:NewSize和-XX:MaxNewSize:设置新生代的初始大小和最大大小。

  • -XX:SurvivorRatio:设置新生代中Eden区和Survivor区的比例。

  • -XX:MaxTenuringThreshold:设置对象晋升到老年代的年龄阈值。

  • -XX:ParallelGCThreads和-XX:ConcGCThreads:设置并行垃圾回收过程中的线程数量。


评论