深入理解Java之jvm启动流程

概述

当我们写一个Java类,并重写Main方法,程序就能运行起来。main方法的背后,程序为什么能运行,jvm究竟做了什么处理?要理解这些,就需要了解jvm的设计原理以及启动的流程。

虚拟机的启动入口位于share/tools/launcher/java.c的main方法,整个流程分为如下几个步骤:
1、配置JVM装载环境
2、解析虚拟机参数
3、设置线程栈大小
4、执行Java main方法

jvm启动流程分析

配置JVM装载环境

Java代码执行时需要一个JVM环境,JVM环境的创建包括两部分:JVM.dll文件的查找和装载。

JVM.dll文件查找

我们来看一段Jre通过环境的路径和系统版本寻找jvm.cfg文件的代码。
这里写图片描述

说明:
1、GetJREPath()查找当前JRE环境的所在路径;
2、ReadKnownVms()读取JRE路径\lib\ARCH(CPU构架)\JVM.cfg文件,其中ARCH(CPU构架)通过GetArch方法获取,在window下有三种情况:amd64、ia64和i386;
3、CheckJvmType确定当前JVM类型,先判断是否通过-J、-XXaltjvm=或-J-XXaltjvm=参数指定,如果没有,则读取JVM.cfg文件中配置的第一个类型;
4、GetJVMPath根据上一步确定的JVM类型,找到对应的JVM.dll文件;

JVM.dll文件的装载

调用JVM.dll文件中定义的函数初始化虚拟机中的函数。

这里写图片描述

JVM.dll文件的装载:

1、LoadLibrary方法装载JVM.dll动态连接库;
2、把JVM.dll文件中定义的函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs绑定到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上;

虚拟机参数解析

装载完JVM环境之后,需要对启动参数进行解析,其实在装载JVM环境的过程中已经解析了部分参数,该过程通过ParseArguments方法实现,并调用AddOption方法将解析完成的参数保存到JavaVMOption中,JavaVMOption结构实现如下:
这里写图片描述

AddOption代码:
这里写图片描述

AddOption核心就是对-Xss参数进行特殊处理,并设置threadStackSize,因为参数格式比较特殊,其它是key/value键值对,它是-Xss512的格式。后续Arguments类会对JavaVMOption数据进行再次处理,并验证参数的合理性。

参数处理

Arguments::parse_each_vm_init_arg方法负责处理经过解析过的JavaVMOption数据,部分实现如下:
这里写图片描述

这里列出了JavaVMOption三个常用的参数:
1、-Xmn:设置新生代的大小NewSize和MaxNewSize;
2、-Xms:设置堆的初始值InitialHeapSize,也是堆的最小值;
3、-Xmx:设置堆的最大值MaxHeapSize;

参数验证

Arguments::check_gc_consistency方法负责验证虚拟机启动参数中配置GC的合理性,实现如下:
这里写图片描述

1、如果参数为-XX:+UseSerialGC -XX:+UseParallelGC,由于UseSerialGC和UseParallelGC不能兼容,JVM启动时会抛出错误信息;
2、如果参数为-XX:+UseConcMarkSweepGC -XX:+UseParNewGC,其中UseConcMarkSweepGC和UseParNewGC可以兼容,JVM可以正常启动;

设置线程栈大小

这里写图片描述

如果启动参数未设置-Xss,即threadStackSize为0,则调用InvocationFunctions的GetDefaultJavaVMInitArgs方法获取JavaVM的初始化参数,即调用JVM.dll函数JNI_GetDefaultJavaVMInitArgs,定义在share\vm\prims\jni.cpp,实现如下:

这里写图片描述

ThreadStackSize定义在globals.hpp中,根据当前系统类型,加载对应的配置文件,所以在不同的系统中,ThreadStackSize的默认值也不同。

执行main方法

这里写图片描述

线程栈大小确定后,通过ContinueInNewThread方法创建新线程,并执行JavaMain函数,JavaMain函数的大概流程如下:

1、新建JVM实例

InitializeJVM方法调用InvocationFunctions的CreateJavaVM方法,即调用JVM.dll函数JNI_CreateJavaVM,新建一个JVM实例,该过程比较复杂,会在后续文章进行分析。

2、加载主类的class

Java运行方式有两种:jar方式和class方式。

jar方式:

这里写图片描述

1、调用GetMainClassName方法找到META-INF/MANIFEST.MF文件指定的Main-Class的主类名;
2、调用LoadClass方法加载主类的class文件;

class方式:

这里写图片描述

1、调用NewPlatformString方法创建类名的String对象;
2、调用LoadClass方法加载主类的class文件;

3,查找main方法

通过GetStaticMethodID方法查找指定方法名的静态方法,实现如下:
这里写图片描述

最终调用JVM.dll函数jni_GetStaticMethodID实现。

这里写图片描述

其中get_method_id方法根据类文件对应的instanceKlass对象查找指定方法。

4、执行main方法

这里写图片描述

1、重新创建参数数组;
2、其中mainID是main方法的入口地址,CallStaticVoidMethod方法最终调用JVM.dll中的jni_CallStaticVoidMethodV函数。

这里写图片描述

这里写图片描述

最终通过JavaCalls::call执行main方法,到此Jvm调用main方法启动类的完整流程就讲完了。

  • 13
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiangzhihong8

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值