Loading

JVM和Java运行的理解

JVM和Java应用程序是如何执行的

我的理解jvm就是一个进程,相当于用java.exe运行了一个进程,这个进程去加载了jar包里面的.class的main方法去执行代码

深度理解

我们详细解释一下Java程序在电脑上是如何运行起来的,特别是结合Launch4j这种包装器的情况。

整体流程可以分为两个大阶段:

  1. 原生包装器(Native Wrapper)的准备阶段(用C/C++编写,编译成机器码)
  2. Java虚拟机(JVM)的初始化和Java程序的执行阶段

下面详细展开:

阶段1:原生包装器的执行(机器码直接由CPU执行)

  1. 操作系统加载可执行文件
    当用户双击.exe文件时,操作系统(Windows)会加载这个可执行文件,并创建一个进程。此时,CPU开始执行这个可执行文件中的机器指令(即原生包装器的代码)。
  2. 包装器初始化
    • 解析命令行参数(如果有的话)
    • 检查内存、环境等
    • 读取内嵌在可执行文件中的资源(比如JAR文件、配置信息等)
  3. 查找并验证JVM
    包装器会按照配置(或默认规则)在以下位置查找JVM:
    • 检查当前目录下是否有jre目录(私有JRE)
    • 查询Windows注册表(已安装的公共JRE或JDK)
    • 查找环境变量JAVA_HOME
    • 在系统的PATH中查找java.exe
      一旦找到,它会验证JVM的版本是否符合要求(例如最小版本)。
  4. 准备JVM启动参数
    根据配置,包装器会设置JVM的启动参数,例如:
    • 堆内存大小(-Xms, -Xmx)
    • 系统属性(-D)
    • 类路径(classpath)
    • 主类名(或指定JAR文件)
  5. 加载JVM动态链接库(DLL)
    包装器使用Windows API(如LoadLibrary)加载JVM的动态链接库。对于HotSpot JVM,这个动态链接库通常是jvm.dll(在JRE的bin\serverbin\client目录下)。
  6. 创建JVM实例
    通过调用jvm.dll中提供的JNI Invocation API来创建JVM实例。核心函数是JNI_CreateJavaVM,该函数会:
    • 在当前的进程地址空间中初始化一个JVM
    • 返回一个JavaVM指针(代表JVM实例)和一个JNIEnv指针(代表当前线程的JNI环境)

阶段2:JVM的运行和Java程序的执行

一旦JVM被创建,它就运行在同一个进程内。接下来:

  1. 加载Java主类
    包装器通过JNI调用,加载Java程序的主类。例如:
C






    jclass mainClass = env->FindClass("com/example/Main");
  1. 查找main方法
    找到主类后,获取其main方法的ID:
C






    jmethodID mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
  1. 调用main方法
    包装器创建Java字符串数组(用于存放命令行参数),然后调用Java的main方法:
C    jobjectArray args = ... // 将命令行参数转换为Java字符串数组
    env->CallStaticVoidMethod(mainClass, mainMethod, args);
  1. Java程序开始运行
    此时,控制权转移到Java程序。在Java的main方法中,程序可以创建其他对象、启动线程等。JVM负责执行Java字节码,包括:
    • 解释执行字节码
    • 即时编译(JIT)将热点代码编译为本地机器码
    • 内存管理(垃圾回收)
    • 线程调度
    • 异常处理等
  2. 程序结束
    当Java的main方法执行完毕并返回后,包装器可以选择销毁JVM(调用DestroyJavaVM函数),然后退出进程。或者,如果Java程序中启动了非守护线程,JVM会等待所有非守护线程结束才会退出。

注意点:运行时内存布局

在进程的内存空间中,现在包含:

  • 原生包装器的代码和数据
  • jvm.dll的代码和数据
  • JVM运行时数据结构(如堆、栈、方法区、JIT代码缓存等)
  • Java堆(存放对象实例)
  • 线程栈(每个线程有自己的栈,包括Java线程和原生线程)

为什么说Java程序是运行在JVM中的?

  1. 字节码解释与JIT编译
    Java程序编译后得到的是字节码(.class文件),这些字节码并不是直接由CPU执行,而是由JVM解释执行,或者通过JIT编译器将热点代码编译成本地机器码再执行。
  2. 内存管理
    Java程序中的对象分配在堆上,而堆的内存分配和回收都是由JVM管理的(通过垃圾回收器)。
  3. 线程管理
    Java线程(java.lang.Thread)是由JVM调度的,并且映射到操作系统的原生线程。
  4. 安全性检查
    JVM在运行过程中会进行字节码验证、访问权限检查等安全措施。

总结启动过程

  1. 原生包装器(EXE)启动,由操作系统加载。
  2. 包装器加载jvm.dll并创建JVM(同一进程内)。
  3. JVM加载指定的Java主类并调用其main方法。
  4. Java程序在JVM中运行,直至完成。
  5. JVM销毁,包装器退出。

通过这种方式,用户感觉就像在运行一个原生应用程序,而实际上内部运行的是Java程序。整个过程只有一个进程,但其中包含了原生代码和托管在JVM中的Java代码的执行。

希望这个详细解释能帮助你理解Java程序是如何在电脑上运行起来的。

理解

我们详细解释一下Java程序在电脑上是如何运行起来的,特别是结合Launch4j这种包装器的情况。

整体流程可以分为两个大阶段:

  1. 原生包装器(Native Wrapper)的准备阶段(用C/C++编写,编译成机器码)
  2. Java虚拟机(JVM)的初始化和Java程序的执行阶段

下面详细展开:

阶段1:原生包装器的执行(机器码直接由CPU执行)

  1. 操作系统加载可执行文件
    当用户双击.exe文件时,操作系统(Windows)会加载这个可执行文件,并创建一个进程。此时,CPU开始执行这个可执行文件中的机器指令(即原生包装器的代码)。
  2. 包装器初始化
    • 解析命令行参数(如果有的话)
    • 检查内存、环境等
    • 读取内嵌在可执行文件中的资源(比如JAR文件、配置信息等)
  3. 查找并验证JVM
    包装器会按照配置(或默认规则)在以下位置查找JVM:
    • 检查当前目录下是否有jre目录(私有JRE)
    • 查询Windows注册表(已安装的公共JRE或JDK)
    • 查找环境变量JAVA_HOME
    • 在系统的PATH中查找java.exe
      一旦找到,它会验证JVM的版本是否符合要求(例如最小版本)。
  4. 准备JVM启动参数
    根据配置,包装器会设置JVM的启动参数,例如:
    • 堆内存大小(-Xms, -Xmx)
    • 系统属性(-D)
    • 类路径(classpath)
    • 主类名(或指定JAR文件)
  5. 加载JVM动态链接库(DLL)
    包装器使用Windows API(如LoadLibrary)加载JVM的动态链接库。对于HotSpot JVM,这个动态链接库通常是jvm.dll(在JRE的bin\serverbin\client目录下)。
  6. 创建JVM实例
    通过调用jvm.dll中提供的JNI Invocation API来创建JVM实例。核心函数是JNI_CreateJavaVM,该函数会:
    • 在当前的进程地址空间中初始化一个JVM
    • 返回一个JavaVM指针(代表JVM实例)和一个JNIEnv指针(代表当前线程的JNI环境)

阶段2:JVM的运行和Java程序的执行

一旦JVM被创建,它就运行在同一个进程内。接下来:

  1. 加载Java主类
    包装器通过JNI调用,加载Java程序的主类。例如:
C






    jclass mainClass = env->FindClass("com/example/Main");
  1. 查找main方法
    找到主类后,获取其main方法的ID:
C






    jmethodID mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
  1. 调用main方法
    包装器创建Java字符串数组(用于存放命令行参数),然后调用Java的main方法:
C    jobjectArray args = ... // 将命令行参数转换为Java字符串数组
    env->CallStaticVoidMethod(mainClass, mainMethod, args);
  1. Java程序开始运行
    此时,控制权转移到Java程序。在Java的main方法中,程序可以创建其他对象、启动线程等。JVM负责执行Java字节码,包括:
    • 解释执行字节码
    • 即时编译(JIT)将热点代码编译为本地机器码
    • 内存管理(垃圾回收)
    • 线程调度
    • 异常处理等
  2. 程序结束
    当Java的main方法执行完毕并返回后,包装器可以选择销毁JVM(调用DestroyJavaVM函数),然后退出进程。或者,如果Java程序中启动了非守护线程,JVM会等待所有非守护线程结束才会退出。

注意点:运行时内存布局

在进程的内存空间中,现在包含:

  • 原生包装器的代码和数据
  • jvm.dll的代码和数据
  • JVM运行时数据结构(如堆、栈、方法区、JIT代码缓存等)
  • Java堆(存放对象实例)
  • 线程栈(每个线程有自己的栈,包括Java线程和原生线程)

为什么说Java程序是运行在JVM中的?

  1. 字节码解释与JIT编译
    Java程序编译后得到的是字节码(.class文件),这些字节码并不是直接由CPU执行,而是由JVM解释执行,或者通过JIT编译器将热点代码编译成本地机器码再执行。
  2. 内存管理
    Java程序中的对象分配在堆上,而堆的内存分配和回收都是由JVM管理的(通过垃圾回收器)。
  3. 线程管理
    Java线程(java.lang.Thread)是由JVM调度的,并且映射到操作系统的原生线程。
  4. 安全性检查
    JVM在运行过程中会进行字节码验证、访问权限检查等安全措施。

总结启动过程

  1. 原生包装器(EXE)启动,由操作系统加载。
  2. 包装器加载jvm.dll并创建JVM(同一进程内)。
  3. JVM加载指定的Java主类并调用其main方法。
  4. Java程序在JVM中运行,直至完成。
  5. JVM销毁,包装器退出。

通过这种方式,用户感觉就像在运行一个原生应用程序,而实际上内部运行的是Java程序。整个过程只有一个进程,但其中包含了原生代码和托管在JVM中的Java代码的执行。

希望这个详细解释能帮助你理解Java程序是如何在电脑上运行起来的。

JVM 的本质

深入思考

我们一步一步来理解JVM(Java虚拟机)到底是什么,以及它是如何运行的。

1. JVM 是什么?

JVM(Java Virtual Machine)是一个抽象的计算机器,它提供了运行Java字节码的环境。但它在物理上并不是一个独立的实体,而是一个规范(specification)。不同的公司可以按照这个规范实现自己的JVM(例如,Oracle的HotSpot JVM、IBM J9、Azul Zing等)。

关键点:

  • JVM 是进程吗? 当我们启动一个Java程序时,操作系统会启动一个进程,这个进程就是JVM进程。在这个进程内部,JVM负责执行Java字节码。
  • JVM 和操作系统的关系:JVM作为一个进程运行在操作系统之上,它向操作系统申请内存、CPU等资源,然后在这些资源上运行Java程序。

2. JVM 进程的启动

当你运行Java程序时(例如通过命令行java -jar app.jar),操作系统会:

  1. 创建一个新的进程。
  2. 在这个进程中加载JVM(通常是通过运行java可执行文件,这个可执行文件实际上是JVM的实现)。
  3. JVM进程开始运行,并加载你的Java程序。

在Launch4j的情况下:

  • Launch4j包装器进程(即你生成的.exe)启动后,它会在同一个进程内通过JNI(Java Native Interface)调用JNI_CreateJavaVM函数来创建JVM。所以,整个运行过程中只有一个进程,即Launch4j包装器进程,而JVM在这个进程内部运行。

3. JVM 进程内部结构

一旦JVM启动(无论是在独立的java进程内,还是在Launch4j的进程中),它会在内存中划分多个区域来管理程序的运行:

  • 类加载器(ClassLoader):负责加载.class文件(字节码)到内存中。

  • 运行时数据区(Runtime Data Areas)

    • 方法区(Method Area):存储类结构、常量、静态变量等。
    • 堆(Heap):所有对象实例和数组都在这里分配内存(被所有线程共享)。
    • Java栈(Java Stacks):每个线程创建时都会分配一个栈,用于存储局部变量、方法调用等。
    • 程序计数器(Program Counter Registers):每个线程独立的指针,指向当前执行的指令。
    • 本地方法栈(Native Method Stacks):用于支持本地方法(用其他语言编写的方法)的执行。
  • 执行引擎(Execution Engine):负责执行字节码。它包括解释器(逐条解释执行字节码)和JIT编译器(将热点代码编译成本地机器代码以提高性能)。

  • 本地方法接口(JNI):用于调用本地库(如C/C++编写)。

4. Java程序如何在JVM上运行?

  1. 加载:类加载器将.class文件加载到方法区。

  2. 验证:确保字节码是合法的,不会破坏JVM的安全。

  3. 准备:为静态变量分配内存并设置默认值。

  4. 解析:将符号引用转换为直接引用。

  5. 初始化:执行静态初始化代码。

  6. 执行

    :执行引擎开始执行程序的main方法:

    • 解释器逐行解释执行字节码。
    • JIT编译器监控执行频率,将热点代码编译为本地机器码,后续执行直接使用机器码(提高速度)。

5. JVM 进程在操作系统中的表现

在操作系统的任务管理器(Windows)或活动监视器(Mac)中,你会看到:

  • 如果使用java -jar命令,你会看到一个java进程(或者javaw进程,如果没有控制台)。
  • 如果使用Launch4j生成的exe,你会看到这个exe的进程名(例如myapp.exe),这个进程内部运行了JVM。

无论哪种方式,这个进程就是JVM进程(或者包含JVM的进程)。这个进程占用的内存包括:

  • JVM自身需要的内存(如元空间Metaspace、线程栈等)。
  • Java堆内存(由你设置的-Xmx参数决定)。
  • 其他本地内存(如JIT代码缓存、直接内存等)。

6. 总结:JVM 是进程吗?

  • JVM 本身是一个规范,但当我们说“启动JVM”时,实际上是指启动了一个操作系统进程,这个进程运行了JVM的实现(例如HotSpot)。
  • 在这个进程中,JVM负责解释/编译Java字节码,并管理内存、线程等资源。

因此,严格来说:

  • JVM不是一个独立的进程,而是一个运行在某个进程内部的运行时环境
  • 但我们通常把运行JVM的进程称为“JVM进程”。

图示

 操作系统
│
├── 进程1: 浏览器 (chrome.exe)
├── 进程2: 文本编辑器 (notepad.exe)
└── 进程3: Java程序 (java.exe 或 myapp.exe)  ← 这就是“JVM进程”
    │
    ├── JVM 运行时环境
    │   ├── 类加载器
    │   ├── 执行引擎(解释器 + JIT编译器)
    │   ├── 堆(Heap)
    │   ├── 栈(Stacks)
    │    └── ...
    │
     └── 你的Java应用程序
        ├── 主类(Main Class)
        ├── 其他类
         └── ...

回到Launch4j

Launch4j生成的exe进程内部:

  • 包含一个原生的启动器(用C/C++编写),它负责启动JVM(通过JNI)。
  • 然后JVM在这个进程中加载并运行你的Java程序。

所以整个过程只有一个进程(即exe进程),但它内部包含了JVM运行时和你的Java应用程序。

深入理解

我在这里还是没有理解到jvm 是什么,他是一个进程运行在电脑里面吗?

理解 JVM 的本质需要从多个层面来看。让我用更直观的方式解释:

JVM 的本质:一个特殊的进程

是的,JVM 本质上就是一个运行在操作系统上的进程。但当你说 "运行 Java 程序" 时,实际上是两个密切相关的部分协同工作:

graph LR A[操作系统] --> B[JVM 进程] B --> C1[JVM 运行时系统] B --> C2[你的 Java 程序] C1 --> D[字节码执行引擎] C1 --> E[内存管理系统] C1 --> F[类加载器]

详细解释过程:

1. 进程创建阶段

当启动 Java 程序时(通过 java 命令或 Launch4j 的包装器):

  • 操作系统创建一个新进程
  • 该进程就是 JVM 进程(在任务管理器中通常显示为 java.exe 或你的程序名)
  • 此时内存中加载的是
  • JVM 可执行文件(如 jvm.dll
  • Java 核心库(如 rt.jar

📌 关键点:只有一个 JVM 进程,不是 "JVM 进程 + Java 程序进程"

2. JVM 进程的内部结构

这个进程内部划分成了多个逻辑区域:

classDiagram class JVM进程 { + 原生代码区(Native Code) + 运行时数据区(Runtime Data Areas) + 执行引擎(Execution Engine) + 本地方法接口(JNI) } class 运行时数据区 { <<JVM内存>> 方法区(Method Area) 堆(Heap) Java栈(Java Stacks) 程序计数器(PC Registers) 本地方法栈(Native Stack) } class 执行引擎 { <<字节码处理器>> 解释器(Interpreter) JIT编译器(JIT Compiler) 垃圾回收器(GC) } JVM进程 *-- 运行时数据区 JVM进程 *-- 执行引擎

3. Java 程序的运行方式

在同一个 JVM 进程内:

  1. 类加载器从 JAR 加载你的 .class 文件到方法区
  2. 执行引擎解释/编译字节码:
// 你的 Java 代码
public class Hello {
public static void main(String[] args) {
System.out.println("Hello JVM!");
}
}

↓ 转为字节码 ↓

0: getstatic#2// 获取 System.out
3: ldc#3// 加载 "Hello JVM!"
5: invokevirtual #4// 调用 println 方法
8: return
  1. 执行引擎逐条处理字节码:
  • 解释器直接执行
  • JIT 编译器将热点代码转为机器码缓存
  1. 内存管理
  • 中创建对象
  • GC 线程自动回收内存

4. 操作系统视角

在操作系统中看到的是这样的:

任务管理器 (Windows)
┌──────────────────────────┬─────────────┐
│ 进程名│ 内存占用│
├──────────────────────────┼─────────────┤
│ javaw.exe (或你的程序名) │ 350 MB│ ← 这就是JVM进程
└──────────────────────────┴─────────────┘

该进程包含:

  • 20% JVM 自身代码
  • 60% Java 堆内存(你的对象)
  • 20% JIT 编译缓存等

5. 为什么感觉像两个东西?

这种误解源于:

flowchart TD A[程序员视角] --> B[Java 程序] A --> C[JVM] D[系统视角] --> E[单一进程] E --> F[包含JVM运行时] E --> G[包含Java代码]

实际上:

  • JVM 是容器:提供字节码运行环境
  • Java 程序是内容:在容器内执行的字节码
  • 共享同一进程空间:就像浏览器(进程)运行JavaScript(程序)

关键总结表

概念 本质 是否独立进程
JVM 字节码运行环境 + 内存管理系统 是(进程本体)
Java 应用程序 在 JVM 内运行的字节码集合 否(进程内容)
java.exe JVM 的进程外壳
HotSpot 引擎 JVM 内部的执行核心

类比理解

想象一个游戏主机(如 PlayStation)

  • 游戏主机 = JVM 进程
  • 提供硬件资源(CPU/GPU/内存)
  • 包含系统软件(操作系统)
  • 游戏光盘 = Java 程序(.jar)
  • 需要主机才能运行
  • 运行在主机提供的环境中
  • 启动游戏 = 运行Java程序
  • 放入光盘 → 加载JAR
  • 主机执行游戏代码 → JVM执行字节码

当游戏运行时,你不会说“既有主机进程又有游戏进程”,因为它们融合在同一个运行实体中。JVM 和 Java 程序的关系正是如此。

posted @ 2025-07-23 11:31  我不想学编丿程  阅读(35)  评论(0)    收藏  举报