从OpenJDK源码看JAVA虚拟机的创建过程

这里写目录标题

  • 关于Java跨平台能力的理解
  • Java Virtual Machine是怎么创建的。
    • 1. Java Launcher
    • 2. JLI_Launch 入口
    • 3. JVM-Init
    • 4. 开启新线程并继续
    • 5. 调用JavaMain
    • 6. 初始化Java虚拟机,并执行Main方法
      • java.c中的InitializeJVM 方法
    • 7. JNI_CreateJavaVM
    • 8. 虚拟机创建
      • 初始化过程概览
      • System.java - InitPhase1,2,3
      • Pharse1 - 第一阶中几个关键点
      • Pharse2 - 初始化模块系统
      • Pharse3 - 系统初始化最后一阶段
  • 总结
  • 关注我的公众号

关于Java跨平台能力的理解

Java语言是一种跨平台的语言,大家通常都这样描述Java语言:Write Once, Run Anywhere,即一次编写,到处运行。

理论上来讲,一个Java程序只需要编写并编译一次,就可以在任何合适版本的JVM设备上运行。

之所以能跨平台,这主要是因为它采用了一种独特的运行时环境和字节码概念。

  • 字节码
    Java源代码首先被编译成一种中间形式,称为字节码(.class 文件)。这种字节码不是针对任何特定的硬件或操作系统编写的,它是一种特殊类型的代码,可以在任何实现了Java虚拟机(JVM)的设备上运行。
  • Java虚拟机(JVM)
    运行Java程序时,不是直接在操作系统上运行,而是在JVM上运行。JVM是一个软件层,工作在特定操作系统上面。JVM读取字节码并且转换(或解释)成特定平台上的本地代码。每个操作系统平台都有自己的JVM实现,这使得任何平台上的JVM都能理解相同的Java字节码。

今天我们主要是走读一下OpenJDK的源码,来看一下Java虚拟机的创建过程,了解了虚拟机的创建过程,为后面更好的理解类的加载和创建打下基础。

Java Virtual Machine是怎么创建的。

通常我们运行一个Java应用程序是通过以下的方式:

java -Xmx1024m -Xms512m -jar my_application.jar

当我们执行这个条java命令后,就会启动一个Java虚拟机来运行我们的程序。

接下来我们就从源码的角度,一步一步来分析这条命令背后都有哪些细节。

1. Java Launcher

没错,当我们执行java命令时,这个java命令就叫做Java Launcher,它负责执行与启动 JVM 实例、加载类、初始化 Java 环境等操作。

它的源代码位于:/jdk-master/src/java.base/share/native/launcher目录,main.c就是java命令的源代码了。我们看到代码的最后,调用了一个JLI_Launch的方法。
Java Launcher入口

2. JLI_Launch 入口

源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
JLI_Launch入口
它主要做的工作是解析命令行参数,设置JVM环境,加载Java虚拟机,并启动Java应用程序。

在这个方法的最后面,会调用JVMInit方法,来初始化Java虚拟机。

    /* Override class path if -jar flag was specified */if (mode == LM_JAR) {SetClassPath(what);     /* Override class path */}/* set the -Dsun.java.command pseudo property */SetJavaCommandLineProp(what, argc, argv);/* Set the -Dsun.java.launcher pseudo property */SetJavaLauncherProp();return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);

3. JVM-Init

源码位置:/jdk-master/src/java.base/unix/native/libjli/java_md.c
JVM-Init
我们看到这时,会开启一个新的线程来继续JVM的初始化工作。

4. 开启新线程并继续

又回到了libjli中的java.c文件
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c
CallJavaMain

5. 调用JavaMain

回到/jdk-master/src/java.base/unix/native/libjli/java_md.c文件,

  • CallJavaMainInNewThread 方法实现
    • 最底下调用JavaMain()方法

Call JavaMain

6. 初始化Java虚拟机,并执行Main方法

终于进入了主题,JavaMain方法会开启一个新的线程,并今行JVM的初始化工作。
源码位置:/jdk-master/src/java.base/share/native/libjli/java.c

初始化虚拟机

java.c中的InitializeJVM 方法

/** Initializes the Java Virtual Machine. Also frees options array when* finished.*/
static jboolean
InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{JavaVMInitArgs args;jint r;memset(&args, 0, sizeof(args));args.version  = JNI_VERSION_1_2;args.nOptions = numOptions;args.options  = options;args.ignoreUnrecognized = JNI_FALSE;if (JLI_IsTraceLauncher()) {int i = 0;printf("JavaVM args:\n    ");printf("version 0x%08lx, ", (long)args.version);printf("ignoreUnrecognized is %s, ",args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");printf("nOptions is %ld\n", (long)args.nOptions);for (i = 0; i < numOptions; i++)printf("    option[%2d] = '%s'\n",i, args.options[i].optionString);}r = ifn->CreateJavaVM(pvm, (void **)penv, &args);JLI_MemFree(options);return r == JNI_OK;
}

看们看倒数第三行代码,调用CreateJavaVM方法,来创建虚拟机。

//这里 ifn->,是C语言中结构体指针访问结构体的一种语法。
r = ifn->CreateJavaVM(pvm, (void **)penv, &args);//ifn的声明在另外一个文件中
//源码位置:/jdk-master/src/java.base/share/native/libjli/java.h
typedef struct {int    argc;char **argv;int    mode;char  *what;InvocationFunctions ifn;
} JavaMainArgs;//在动态链接库 (Dynamic Linking Library, DLL) 中查找名为 "JNI_CreateJavaVM" 的函数,并将其地址赋值给 ifn->CreateJavaVM
//该代码片段位于LoadJavaVM()方法中,而LoadJavaVM()目的是动态加载Java虚拟机(JVM)的库(libjvm.so或jvm.dll等,取决于操作系统)并查找JNI(Java Native Interface)相关的几个关键函数指针,使得C或C++代码能够调用这些函数创建和操作Java虚拟机。
ifn->CreateJavaVM = (CreateJavaVM_t)dlsym(libjvm, "JNI_CreateJavaVM");if (ifn->CreateJavaVM == NULL) {JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());return JNI_FALSE;
}

7. JNI_CreateJavaVM

即将调用创建虚拟机的真正方法:Threads::create_vm()
源码位置:/jdk-master/src/hotspot/share/prims/jni.cpp

_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {jint result = JNI_ERR;// On Windows, let CreateJavaVM run with SEH protection
#if defined(_WIN32) && !defined(USE_VECTORED_EXCEPTION_HANDLING)__try {
#endifresult = JNI_CreateJavaVM_inner(vm, penv, args);
#if defined(_WIN32) && !defined(USE_VECTORED_EXCEPTION_HANDLING)} __except(topLevelExceptionFilter((_EXCEPTION_POINTERS*)_exception_info())) {// Nothing to do.}
#endifreturn result;
}//JNI_CreateJavaVM_inner 同样位于 jni.cpp中,代码片段如下:
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {HOTSPOT_JNI_CREATEJAVAVM_ENTRY((void **) vm, penv, args);jint result = JNI_ERR;//此处省略部分代码//创建虚拟机的关键方法调用:Threads::create_vm()result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);

8. 虚拟机创建

源码位置: /jdk-master/src/hotspot/share/runtime/threads.cpp
整个Threads::create_vm方法,从413行开始,832行结束,另外还调用了一些其它重要的初始化方法
虚拟机创建过程

初始化过程概览

由于内容太多,我们只看几个关键的地方。


initialize_java_lang_classes(main_thread, CHECK_JNI_ERR);//此方法由initialize_java_lang_classes方法调用
// Phase 1 of the system initialization in the library, java.lang.System class initializationcall_initPhase1(CHECK);
// The system initialization in the library has three phases.
//
// Phase 1: java.lang.System class initialization
//     java.lang.System is a primordial class loaded and initialized
//     by the VM early during startup.  java.lang.System.<clinit>
//     only does registerNatives and keeps the rest of the class
//     initialization work later until thread initialization completes.
//
//     System.initPhase1 initializes the system properties, the static
//     fields in, out, and err. Set up java signal handlers, OS-specific
//     system settings, and thread group of the main thread.
static void call_initPhase1(TRAPS) {Klass* klass = vmClasses::System_klass();JavaValue result(T_VOID);JavaCalls::call_static(&result, klass, vmSymbols::initPhase1_name(),vmSymbols::void_method_signature(), CHECK);
}// This will initialize the module system.  Only java.base classes can be// loaded until phase 2 completescall_initPhase2(CHECK_JNI_ERR);
// Phase 2. Module system initialization
//     This will initialize the module system.  Only java.base classes
//     can be loaded until phase 2 completes.
//
//     Call System.initPhase2 after the compiler initialization and jsr292
//     classes get initialized because module initialization runs a lot of java
//     code, that for performance reasons, should be compiled.  Also, this will
//     enable the startup code to use lambda and other language features in this
//     phase and onward.
//
//     After phase 2, The VM will begin search classes from -Xbootclasspath/a.
static void call_initPhase2(TRAPS) {TraceTime timer("Initialize module system", TRACETIME_LOG(Info, startuptime));Klass* klass = vmClasses::System_klass();JavaValue result(T_INT);JavaCallArguments args;args.push_int(DisplayVMOutputToStderr);args.push_int(log_is_enabled(Debug, init)); // print stack trace if exception thrownJavaCalls::call_static(&result, klass, vmSymbols::initPhase2_name(),vmSymbols::boolean_boolean_int_signature(), &args, CHECK);if (result.get_jint() != JNI_OK) {vm_exit_during_initialization(); // no message or exception}universe_post_module_init();
}// Final system initialization including security manager and system class loadercall_initPhase3(CHECK_JNI_ERR);// Phase 3. final setup - set security manager, system class loader and TCCL
//
//     This will instantiate and set the security manager, set the system class
//     loader as well as the thread context class loader.  The security manager
//     and system class loader may be a custom class loaded from -Xbootclasspath/a,
//     other modules or the application's classpath.
static void call_initPhase3(TRAPS) {Klass* klass = vmClasses::System_klass();JavaValue result(T_VOID);//这行代码调用了一个静态方法。//call_static方法的参数包括://1. 结果存储对象的地址//2. 要调用的静态方法所在的类:System.class//3. 要调用的方法的名字initPhase3, //   在jdk-master/src/hotspot/share/classfile/vmSymbols.hpp定义//4. 要调用的方法的签名//5. 异常处理对象//这里调用的是System类的initPhase3方法,该方法没有参数并且返回类型为voidJavaCalls::call_static(&result, klass, vmSymbols::initPhase3_name(),vmSymbols::void_method_signature(), CHECK);
}//这几个阶段,都和System.java有关,具体的初始化过程参见System.java.

System.java - InitPhase1,2,3

源码位置:/jdk-master/src/java.base/share/classes/java/lang/System.java
System_Init1_2_3

Pharse1 - 第一阶中几个关键点

  • 设置系统编码(jnu.encoding):检查并确保sun.jnu.encoding系统属性是支持的编码。如果不支持或没有设置,则设置为默认值"UTF-8"。
  • 静态属性加载:加载某些静态属性,比如java.home。
  • 设置行分隔符属性: (line.separator)。
  • 初始化标准输入输出流:创建FileInputStream和FileOutputStream流,并且为标准输入输出创建缓冲流。

Pharse2 - 初始化模块系统

这里提一个小知识点,模块系统是Java 9中引入 的一个重要我发,它的目的是提供更好的封装性和模块化能力,从而改进大型应用和库的构建、维护和部署。

Pharse3 - 系统初始化最后一阶段

  • 初始化引导方法工厂
  • 设置安全管理器
  • 设置系统类加载器
  • 设置线程上下文类加载器

这里我们需要重点关注:设置系统类加载器

初始化ClassLoader
在这个过程中Java的几种类加载器都将会被始化,我们无法主动控制这些类加载器的创建,但我们可以定义自己的类加载器。以下是Java中的几种类加载器:

  • 引导类加载器(Bootstrap ClassLoader):

    • 这是由JVM实现的,用C++编写的。
    • 它负责加载JVM的核心类库,例如rt.jar或者java.*包中的类。
    • 由于是由JVM的原生代码实现,它并不是继承自java.lang.ClassLoader,通常在Java程序中不能直接引用引导类加载器。
  • 系统类加载器(System/Application ClassLoader):

    • 这是ClassLoader层次中的第三层,它负责加载环境变量CLASSPATH或者系统属性java.class.path指定路径中的类库,通常用来加载我们编写的类和第三方库。
    • 它是sun.misc.Launcher内部类AppClassLoader(在JDK9之前)或jdk.internal.loader.ClassLoaders内部类AppClassLoader(JDK9及之后)的实例。
  • 用户自定义类加载器:

    • 开发者可以通过继承ClassLoader类来实现自定义的类加载器。
    • 通过定义自己的加载策略,可以完成例如从网络加载类,解密类文件等特殊的加载需求。

JDK 9中引入模块系统后,引入新的类加载器:

  • 平台类加载器(Platform ClassLoader):
    • 在模块系统中取代了传统的扩展类加载器。
    • 它负责加载提供平台特定功能(如特定于JVM实现的部分)的模块。

总结

文章到这里,基本上已经将JVM启动的过程的核心步骤描述清楚了。本文是基于OpenJDK 22版本的源码进行走读,大家可从Github上下载最新代码。
下载地址:

或者

#可以通过git clone的方式下载代码
#由于网络原因,我下载总是失败,最后直接选择下载了zip文件。
git clone https://github.com/openjdk/jdk.git

JVM的初始化过程是一个稳式的过程,当我们在进行Java应用调试的时候,这一过程我们无法看到,学习这一个过程,有助于我们理解类似于类的加载,对象的创建,初始化等过程。

关注我的公众号

欢迎大家关注我的公众号,一起交流软件开发、架构设计、云原生技术
TXZQ聊IT技术与架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/4641.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPS的JS宏如何设置Word文档的表格的单元格文字重新编号

希望对Word文档中的表格进行统一处理&#xff0c;表格内的编号&#xff0c;有时候会出现紊乱&#xff0c;下一个表格的编号承接了上一个表格的编号&#xff0c;实际需要重新编号。 当表格比较多时&#xff0c;手动更改非常麻烦&#xff0c;而且更改一遍并不能完成&#xff0c;…

使用 XHbuilder 编辑器 uniapp开发 app 中使用手机本相机可直接拍摄照片进行上传,也可以选择相册进行上传

学习目标&#xff1a; 使用 XHbuilder 编辑器 uniapp开发 app 中使用手机本相机可直接拍摄照片进行上传&#xff0c;也可以选择相册进行上传 学习内容&#xff1a; 相关内容 上传图片上传时调用的相关方法配置的相关模块需要配置的相关权限 知识小结&#xff1a; 总结&#…

最大连续1的个数 ||| ---- 滑动窗口

题目链接 题目: 分析: 题目中说可以将最多k个0翻转成1, 如果我们真的这样算就会十分麻烦, 所以我们可以换一种思路: 找到一个最长的子数组, 最多有k个0解法一: 暴力解法: 找到所有的最多有k个0的子字符串, 返回最长的解法二: 找到最长的子数组, 我们可以想到"滑动窗口算…

【win10相关】更新后出现未连接到互联网的问题及解决

问题背景 在win10更新完系统之后&#xff0c;第二天电脑开机后&#xff0c;发现无法上网&#xff0c;尝试打开百度&#xff0c;但是出现以下图片&#xff1a; 经过检查&#xff0c;发现手机是可以上网的&#xff0c;说明网络本身并没有问题&#xff0c;对防火墙进行了一些设置…

C++/BOOST filesystem fs::directory_iterator一个滑稽的错误

错误来源于&#xff0c;用 fs::directory_iterator iter(folderPath), end; 然后for循环 for (; iter ! iter_end; iter) {} 最开始没问题&#xff0c;后来说加个进度条&#xff0c;统计一下所有文件数量&#xff0c;用了std::distance&#xff0c; int totalFiles std::…

XYCTF2024 部分w

RE 1. 聪明的信使 基础爆破 #include<stdio.h> #include<string.h> int main() {char enc[] "oujp{H0d_TwXf_Lahyc0_14_e3ah_Rvy0acwc!}";char flag[41] {0};int i, j;for (i 0; i < strlen(enc); i){for (j 33; j < 127; j){if ((j < 9…

Skill Check: Fundamentals of Large Language Models

Skill Check: Fundamentals of Large Language Models 完结&#xff01;

Vue项目中引入高德地图步骤详解,附示例代码

vue中如何使用高德地图&#xff0c;下面为您详解。 步骤一&#xff1a;安装高德地图的JavaScript API 在Vue项目的根目录下打开终端&#xff0c;执行以下命令安装高德地图的JavaScript API&#xff1a; npm install amap/amap-jsapi-loader --save 步骤二&#xff1a;创建地…

什么?你还不懂文件系统和软硬链接?

文章目录 序言认识磁盘磁盘在系统中的管理熟悉磁盘各个分区 软硬链接软链接硬链接 序言 首先熟悉一下一些专有名词(了解即可,但必须有一个概念认识) 固态:SSD,笔记本中常装的,台式机中也可以装,常见的对应接口M.2和SATA接口 磁盘:90年代常用的数据存储设备,或是现在企业级数据…

IPv4 NAT(含Cisco配置)

IPv4 NAT&#xff08;含Cisco配置&#xff09; IPv4私有空间地址 类RFC 1918 内部地址范围前缀A10.0.0.0 - 10.255.255.25510.0.0.0/8B172.16.0.0 - 172.31.255.255172.16.0.0/12C192.168.0.0 - 192.168.255.255192.168.0.0/16 这些私有地址可在企业或站点内使用&#xff0c…

从零开始的软件测试学习之旅(四)web项目工作流程介绍

WEB手工项目 项目介绍项目技术分析项目学习准备工作如何快速熟悉项目举例熟悉TPshop项目 总体系统项目介绍项目与数据库测试流程什么是软件需求需求评审 测试计划测试方案测试计划和测试方案的区别 项目介绍 满足经典三层架构:前端 后端 数据库 前端:运行在用户端的浏览器和客…

同仁堂医养拟赴港上市,养老产业的盈利难题有了答案?

提及银发经济&#xff0c;大众可能最先想到的就是养老产业&#xff0c;在市场需求推动下&#xff0c;这一细分赛道的增长已势不可挡。单从入局者的积极性就可以把握到赛道前景之广阔。 天眼查专业版数据显示&#xff0c;截至目前&#xff0c;我国拥有养老相关企业36.2万家&…

线上办理离婚快速离婚,无需双方见面异地可办

现在离婚有两种方式 一种是协议离婚&#xff0c;双方都同意的情况下&#xff0c;可以去民政局协议离婚&#xff0c;有30天冷静期&#xff0c;冷静期过后需要双方再次去民政局办理离婚手续。 另一种是诉讼离婚&#xff0c;一方不同意离婚&#xff0c;可以选择诉讼离婚。可以全…

【PPT设计】颜色对比、渐变填充、简化框线、放大镜效果、渐变形状配图、线条的使用

目录 图表颜色对比、渐变填充、简化框线放大镜效果渐变形状配图 线条的使用区分标题与说明信息区分标题与正文,区分不同含义的内容**聚焦****引导****注解****装饰** 图表 颜色对比、渐变填充、简化框线 小米汽车正式亮相&#xff01;你们都在讨论价格&#xff0c;我全程只关…

jackson.dataformat.xml 反序列化 对象中包含泛型

重点&#xff1a; JacksonXmlProperty localName 指定本地名称 JacksonXmlRootElement localName 指定root的根路径的名称&#xff0c;默认值为类名 JsonIgnoreProperties(ignoreUnknown true) 这个注解写在类上&#xff0c;用来忽略在xml中有的属性但是在类中没有的情况 Jack…

python_django农产品物流信息服务系统6m344

Python 中存在众多的 Web 开发框架&#xff1a;Flask、Django、Tornado、Webpy、Web2py、Bottle、Pyramid、Zope2 等。近几年较为流行的&#xff0c;大概也就是 Flask 和 Django 了 Flask 是一个轻量级的 Web 框架&#xff0c;使用 Python 语言编写&#xff0c;较其他同类型框…

Eclipse C++ 无法debug 问题

环境&#xff1a; ubuntu20.04 Eclipse CDT&#xff08;x86_64) 工程&#xff0c;使用的是默认的CMake Project 现象&#xff1a; 1. 使用Eclipse&#xff0c; 加了断点后&#xff0c;debug 无法停在断点&#xff1b;step over 执行后是从main 直接执行到exit &#xff…

poi-tl自定义渲染策略学习

文章目录 实现逻辑参考代码注意点 实现逻辑 自定义渲染策略实现逻辑&#xff1a; 找到模板中的表格标签render方法接收java中对应模板表格标签的所有list数据执行自定义渲染逻辑 参考代码 word模板如下&#xff1a; 实体类&#xff1a; Data public class GksxRowData {/…

Linux多进程(二)进程通信方式三 共享内存

共享内存提供了一个在多个进程间共享数据的方式&#xff0c;它们可以直接访问同一块内存区域&#xff0c;因此比使用管道或消息队列等通信机制更高效。在多进程程序中&#xff0c;共享内存通常与信号量一起使用&#xff0c;以确保对共享内存的访问是线程安全的。 一、打开/创建…

07_for循环返回值while循环

文章目录 1.循环返回值2.yield接收for返回值3.scala调用yield方法创建线程对象4.scala中的while循环5.scala中的流程控制 1.循环返回值 for循环返回值是Unit 原因是防止产生歧义&#xff1b; 2.yield接收for返回值 // 2.yield关键字打破循环&#xff0c;可以使for循环输出…