JVM和Java运行的理解
JVM和Java应用程序是如何执行的
我的理解jvm就是一个进程,相当于用java.exe运行了一个进程,这个进程去加载了jar包里面的.class的main方法去执行代码
深度理解
我们详细解释一下Java程序在电脑上是如何运行起来的,特别是结合Launch4j这种包装器的情况。
整体流程可以分为两个大阶段:
- 原生包装器(Native Wrapper)的准备阶段(用C/C++编写,编译成机器码)
- Java虚拟机(JVM)的初始化和Java程序的执行阶段
下面详细展开:
阶段1:原生包装器的执行(机器码直接由CPU执行)
- 操作系统加载可执行文件:
当用户双击.exe文件时,操作系统(Windows)会加载这个可执行文件,并创建一个进程。此时,CPU开始执行这个可执行文件中的机器指令(即原生包装器的代码)。 - 包装器初始化:
- 解析命令行参数(如果有的话)
- 检查内存、环境等
- 读取内嵌在可执行文件中的资源(比如JAR文件、配置信息等)
- 查找并验证JVM:
包装器会按照配置(或默认规则)在以下位置查找JVM:- 检查当前目录下是否有
jre目录(私有JRE) - 查询Windows注册表(已安装的公共JRE或JDK)
- 查找环境变量
JAVA_HOME - 在系统的
PATH中查找java.exe
一旦找到,它会验证JVM的版本是否符合要求(例如最小版本)。
- 检查当前目录下是否有
- 准备JVM启动参数:
根据配置,包装器会设置JVM的启动参数,例如:- 堆内存大小(-Xms, -Xmx)
- 系统属性(-D)
- 类路径(classpath)
- 主类名(或指定JAR文件)
- 加载JVM动态链接库(DLL):
包装器使用Windows API(如LoadLibrary)加载JVM的动态链接库。对于HotSpot JVM,这个动态链接库通常是jvm.dll(在JRE的bin\server或bin\client目录下)。 - 创建JVM实例:
通过调用jvm.dll中提供的JNI Invocation API来创建JVM实例。核心函数是JNI_CreateJavaVM,该函数会:- 在当前的进程地址空间中初始化一个JVM
- 返回一个
JavaVM指针(代表JVM实例)和一个JNIEnv指针(代表当前线程的JNI环境)
阶段2:JVM的运行和Java程序的执行
一旦JVM被创建,它就运行在同一个进程内。接下来:
- 加载Java主类:
包装器通过JNI调用,加载Java程序的主类。例如:
C
jclass mainClass = env->FindClass("com/example/Main");
- 查找main方法:
找到主类后,获取其main方法的ID:
C
jmethodID mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
- 调用main方法:
包装器创建Java字符串数组(用于存放命令行参数),然后调用Java的main方法:
C jobjectArray args = ... // 将命令行参数转换为Java字符串数组
env->CallStaticVoidMethod(mainClass, mainMethod, args);
- Java程序开始运行:
此时,控制权转移到Java程序。在Java的main方法中,程序可以创建其他对象、启动线程等。JVM负责执行Java字节码,包括:- 解释执行字节码
- 即时编译(JIT)将热点代码编译为本地机器码
- 内存管理(垃圾回收)
- 线程调度
- 异常处理等
- 程序结束:
当Java的main方法执行完毕并返回后,包装器可以选择销毁JVM(调用DestroyJavaVM函数),然后退出进程。或者,如果Java程序中启动了非守护线程,JVM会等待所有非守护线程结束才会退出。
注意点:运行时内存布局
在进程的内存空间中,现在包含:
- 原生包装器的代码和数据
jvm.dll的代码和数据- JVM运行时数据结构(如堆、栈、方法区、JIT代码缓存等)
- Java堆(存放对象实例)
- 线程栈(每个线程有自己的栈,包括Java线程和原生线程)
为什么说Java程序是运行在JVM中的?
- 字节码解释与JIT编译:
Java程序编译后得到的是字节码(.class文件),这些字节码并不是直接由CPU执行,而是由JVM解释执行,或者通过JIT编译器将热点代码编译成本地机器码再执行。 - 内存管理:
Java程序中的对象分配在堆上,而堆的内存分配和回收都是由JVM管理的(通过垃圾回收器)。 - 线程管理:
Java线程(java.lang.Thread)是由JVM调度的,并且映射到操作系统的原生线程。 - 安全性检查:
JVM在运行过程中会进行字节码验证、访问权限检查等安全措施。
总结启动过程
- 原生包装器(EXE)启动,由操作系统加载。
- 包装器加载
jvm.dll并创建JVM(同一进程内)。 - JVM加载指定的Java主类并调用其main方法。
- Java程序在JVM中运行,直至完成。
- JVM销毁,包装器退出。
通过这种方式,用户感觉就像在运行一个原生应用程序,而实际上内部运行的是Java程序。整个过程只有一个进程,但其中包含了原生代码和托管在JVM中的Java代码的执行。
希望这个详细解释能帮助你理解Java程序是如何在电脑上运行起来的。
理解
我们详细解释一下Java程序在电脑上是如何运行起来的,特别是结合Launch4j这种包装器的情况。
整体流程可以分为两个大阶段:
- 原生包装器(Native Wrapper)的准备阶段(用C/C++编写,编译成机器码)
- Java虚拟机(JVM)的初始化和Java程序的执行阶段
下面详细展开:
阶段1:原生包装器的执行(机器码直接由CPU执行)
- 操作系统加载可执行文件:
当用户双击.exe文件时,操作系统(Windows)会加载这个可执行文件,并创建一个进程。此时,CPU开始执行这个可执行文件中的机器指令(即原生包装器的代码)。 - 包装器初始化:
- 解析命令行参数(如果有的话)
- 检查内存、环境等
- 读取内嵌在可执行文件中的资源(比如JAR文件、配置信息等)
- 查找并验证JVM:
包装器会按照配置(或默认规则)在以下位置查找JVM:- 检查当前目录下是否有
jre目录(私有JRE) - 查询Windows注册表(已安装的公共JRE或JDK)
- 查找环境变量
JAVA_HOME - 在系统的
PATH中查找java.exe
一旦找到,它会验证JVM的版本是否符合要求(例如最小版本)。
- 检查当前目录下是否有
- 准备JVM启动参数:
根据配置,包装器会设置JVM的启动参数,例如:- 堆内存大小(-Xms, -Xmx)
- 系统属性(-D)
- 类路径(classpath)
- 主类名(或指定JAR文件)
- 加载JVM动态链接库(DLL):
包装器使用Windows API(如LoadLibrary)加载JVM的动态链接库。对于HotSpot JVM,这个动态链接库通常是jvm.dll(在JRE的bin\server或bin\client目录下)。 - 创建JVM实例:
通过调用jvm.dll中提供的JNI Invocation API来创建JVM实例。核心函数是JNI_CreateJavaVM,该函数会:- 在当前的进程地址空间中初始化一个JVM
- 返回一个
JavaVM指针(代表JVM实例)和一个JNIEnv指针(代表当前线程的JNI环境)
阶段2:JVM的运行和Java程序的执行
一旦JVM被创建,它就运行在同一个进程内。接下来:
- 加载Java主类:
包装器通过JNI调用,加载Java程序的主类。例如:
C
jclass mainClass = env->FindClass("com/example/Main");
- 查找main方法:
找到主类后,获取其main方法的ID:
C
jmethodID mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
- 调用main方法:
包装器创建Java字符串数组(用于存放命令行参数),然后调用Java的main方法:
C jobjectArray args = ... // 将命令行参数转换为Java字符串数组
env->CallStaticVoidMethod(mainClass, mainMethod, args);
- Java程序开始运行:
此时,控制权转移到Java程序。在Java的main方法中,程序可以创建其他对象、启动线程等。JVM负责执行Java字节码,包括:- 解释执行字节码
- 即时编译(JIT)将热点代码编译为本地机器码
- 内存管理(垃圾回收)
- 线程调度
- 异常处理等
- 程序结束:
当Java的main方法执行完毕并返回后,包装器可以选择销毁JVM(调用DestroyJavaVM函数),然后退出进程。或者,如果Java程序中启动了非守护线程,JVM会等待所有非守护线程结束才会退出。
注意点:运行时内存布局
在进程的内存空间中,现在包含:
- 原生包装器的代码和数据
jvm.dll的代码和数据- JVM运行时数据结构(如堆、栈、方法区、JIT代码缓存等)
- Java堆(存放对象实例)
- 线程栈(每个线程有自己的栈,包括Java线程和原生线程)
为什么说Java程序是运行在JVM中的?
- 字节码解释与JIT编译:
Java程序编译后得到的是字节码(.class文件),这些字节码并不是直接由CPU执行,而是由JVM解释执行,或者通过JIT编译器将热点代码编译成本地机器码再执行。 - 内存管理:
Java程序中的对象分配在堆上,而堆的内存分配和回收都是由JVM管理的(通过垃圾回收器)。 - 线程管理:
Java线程(java.lang.Thread)是由JVM调度的,并且映射到操作系统的原生线程。 - 安全性检查:
JVM在运行过程中会进行字节码验证、访问权限检查等安全措施。
总结启动过程
- 原生包装器(EXE)启动,由操作系统加载。
- 包装器加载
jvm.dll并创建JVM(同一进程内)。 - JVM加载指定的Java主类并调用其main方法。
- Java程序在JVM中运行,直至完成。
- 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),操作系统会:
- 创建一个新的进程。
- 在这个进程中加载JVM(通常是通过运行
java可执行文件,这个可执行文件实际上是JVM的实现)。 - 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上运行?
-
加载:类加载器将.class文件加载到方法区。
-
验证:确保字节码是合法的,不会破坏JVM的安全。
-
准备:为静态变量分配内存并设置默认值。
-
解析:将符号引用转换为直接引用。
-
初始化:执行静态初始化代码。
-
执行
:执行引擎开始执行程序的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 程序" 时,实际上是两个密切相关的部分协同工作:
详细解释过程:
1. 进程创建阶段
当启动 Java 程序时(通过 java 命令或 Launch4j 的包装器):
- 操作系统创建一个新进程
- 该进程就是 JVM 进程(在任务管理器中通常显示为
java.exe或你的程序名) - 此时内存中加载的是:
- JVM 可执行文件(如
jvm.dll) - Java 核心库(如
rt.jar)
📌 关键点:只有一个 JVM 进程,不是 "JVM 进程 + Java 程序进程"
2. JVM 进程的内部结构
这个进程内部划分成了多个逻辑区域:
3. Java 程序的运行方式
在同一个 JVM 进程内:
- 类加载器从 JAR 加载你的
.class文件到方法区 - 执行引擎解释/编译字节码:
// 你的 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
- 执行引擎逐条处理字节码:
- 解释器直接执行
- JIT 编译器将热点代码转为机器码缓存
- 内存管理:
- 在堆中创建对象
- GC 线程自动回收内存
4. 操作系统视角
在操作系统中看到的是这样的:
任务管理器 (Windows)
┌──────────────────────────┬─────────────┐
│ 进程名│ 内存占用│
├──────────────────────────┼─────────────┤
│ javaw.exe (或你的程序名) │ 350 MB│ ← 这就是JVM进程
└──────────────────────────┴─────────────┘
该进程包含:
- 20% JVM 自身代码
- 60% Java 堆内存(你的对象)
- 20% JIT 编译缓存等
5. 为什么感觉像两个东西?
这种误解源于:
实际上:
- JVM 是容器:提供字节码运行环境
- Java 程序是内容:在容器内执行的字节码
- 共享同一进程空间:就像浏览器(进程)运行JavaScript(程序)
关键总结表
| 概念 | 本质 | 是否独立进程 |
|---|---|---|
| JVM | 字节码运行环境 + 内存管理系统 | 是(进程本体) |
| Java 应用程序 | 在 JVM 内运行的字节码集合 | 否(进程内容) |
java.exe |
JVM 的进程外壳 | 是 |
| HotSpot 引擎 | JVM 内部的执行核心 | 否 |
类比理解
想象一个游戏主机(如 PlayStation):
- 游戏主机 = JVM 进程
- 提供硬件资源(CPU/GPU/内存)
- 包含系统软件(操作系统)
- 游戏光盘 = Java 程序(.jar)
- 需要主机才能运行
- 运行在主机提供的环境中
- 启动游戏 = 运行Java程序
- 放入光盘 → 加载JAR
- 主机执行游戏代码 → JVM执行字节码
当游戏运行时,你不会说“既有主机进程又有游戏进程”,因为它们融合在同一个运行实体中。JVM 和 Java 程序的关系正是如此。

浙公网安备 33010602011771号