[资料] [HotSpot VM]《深入源码剖析Java虚拟机与故障调优实战》第2章样章

gao_xianglong 2014-11-04

2章 深入源码剖析HotSpotLauncher

 

2.1 HotSpot的源码目录结构

在开始正式深入学习HotSpot的源码之前,大家首先需要明白HotSpot的源码目录结构是怎样构成的,以及每一个目录中所包含的特定功能模块实现是什么。当只有在彻底弄清楚这些问题后,才能更有针对性的阅读和学习HotSpot的源码。尽管HotSpot只是OpenJDK项目中的一个子集,但是HotSpot却在整个OpenJDK项目中占据了非常重要的地位,所以本书只会针对HotSpot项目的源码进行讲解,大家只需关注HotSpot即可。

在解压后的HosSpot源码目录下,主要由agentmakesrctest4个子目录构成了HosSpot源码的整体目录结构。其中agent目录下包含了Serviceability Agent的客户端实现。make目录下包含了用于buildHotSpot的各种配置文件。而src目录则是其中最重要的一个子目录,该目录下包含了HotSpot的所有源码实现,比如:与CPU的相关实现、与操作系统的相关实现、与平台无关的通用实现、HotSpot的核心功能实现、各类GC的实现以及执行引擎的相关实现等。至于test目录下仅仅只是包含了HotSpot相关的一些单元测试用例。

在此需要提醒大家,尽管Java继承了C语言的语法结构,并改编了C++语言的对象模型,但是彼此之间仍然存在较大的语法差异。所以Java开发人员在阅读HotSpot的源码时,千万不要在语法细节上钻牛角尖,只需关注具体的数据结构和算法即可。

HotSpot的源码目录结构,如下所示(目录整理自撒加大神的博文)

├─agent                            Serviceability Agent的客户端实现

├─make                             用于buildHotSpot的各种配置文件

├─src                              HotSpot所有源代码

│  ├─cpu                           CPU相关代码

│  ├─os                            操作系相关代码

│  ├─os_cpu                        操作系统+CPU的组合相关的代码

│  └─share                         平台无关的共通代码

│      ├─tools                     工具

│      │  ├─hsdis                  反汇编插件

│      │  ├─IdealGraphVisualizer   server编译器的中间代码可视化的工具

│      │  ├─launcher               启动程序“java”

│      │  ├─LogCompilation         -XX:+LogCompilation输出的日志(hotspot.log)整理成更容易阅读的格式的工具

│      │  └─ProjectCreator         生成Visual Studioproject文件的工具

│      └─vm                        HotSpot VM的核心代码

│          ├─adlc                  平台描述文件(上面的cpuos_cpu里的*.ad文件)的编译器

│          ├─asm                   汇编器接口

│          ├─c1                         client编译器(又称“C1”

 

│          ├─ci                         动态编译器的公共服务/从动态编译器到VM的接口

│          ├─classfile                  类文件的处理(包括类加载和系统符号表等)

│          ├─code                       动态生成的代码管理

│          ├─compiler                   VM调用动态编译器的接口

│          ├─gc_implementation          GC的实现

│          │  ├─concurrentMarkSweep      Concurrent Mark Sweep GC的实现

│          │  ├─g1                       Garbage-First GC的实现(不使用老的分代式GC架)

│          │  ├─parallelScavenge        ParallelScavenge GC的实现(server VM默认不使用老的分代式GC框架)

│          │  ├─parNew                   ParNew GC的实现

│          │  └─shared                   GC的共通实现

│          ├─gc_interface               GC的接口

│          ├─interpreter                解释器,包括模板解释器(官方版在用)和“C++释器(官方版不在用)

│          ├─libadt                    一些抽象数据结构

│          ├─memory                     内存管理相关(老的分代式GC框架也在这里)

│          ├─oops                       HotSpot VM的对象系统的实现

│          ├─opto                       server编译器(又称“C2”“Opto”

│          ├─prims                     HotSpot VM的对外接口,包括部分标准库的native部分和JVMTI实现

│          ├─runtime                    运行时支持库(包括线程管理、编译器调度、锁、反射)

│          ├─services                   主要是用来支持JMX之类的管理功能的接口

│          ├─shark                      基于LLVMJIT编译器(官方版里没有使用)

│          └─utilities                  一些基本的工具类

└─test                            单元测试

 

当大家清楚HotSpot的源码目录结构后,才能更快的熟悉HotSpot每一个目录下所包含的特定功能模块实现。

 

2.2 Launcher简介

Launcher是一种用于启动JVM进程的启动器。在Java中,Launcher可以根据类别划分为2种。一种是正式版的启动器,也就是大家在Windows平台下经常使用到的java.exejavaw.exe程序。前者在运行时会保留控制台,以及显示程序的输出信息。而后者主要是用于执行JavaGUI程序,也就是说,使用javaw.exe执行Java程序时将不会显示程序的输出信息。关于Launcher的具体用法和标准选项配置,大家可以在控制台中输入命令“java -help”,如下所示:

具体用法

java [-options] class [args...](执行类)

或  

java [-options] -jar jarfile [args...](执行jar文件)

其中选项包括:

-d32

使用32位数据模型(如果可用)

-d64

使用64位数据模型(如果可用)

-client

选择"client"VM

-server

选择"server"VM

-hotspot

"client"VM的同义词[已过时]默认VMclient

-cp

目录和zip/jar文件的类搜索路径

-classpath

目录和zip/jar文件的类搜索路径用;分隔的目录,JAR档案和ZIP 档案列表用于搜索类文件

-D<name>=<value>

设置系统属性

-verbose[:class|gc|jni]

启用详细输出

-version

输出产品版本并退出

-version:<value>

需要指定的版本才能运行

-showversion

输出产品版本并继续

-jre-restrict-search|-no-jre-restrict-search

在版本搜索中包括/排除用户专用JRE

-? -help

输出此帮助消息

-X

输出非标准选项的帮助

-ea[:<packagename>...|:<classname>]

 

-enableassertions[:<packagename>...|:<classname>]

按指定的粒度启用断言

-disableassertions[:<packagename>...|:<classname>]

禁用具有指定粒度的断言

-esa | -enablesystemassertions

启用系统断言

-dsa | -disablesystemassertions

禁用系统断言

-agentlib:<libname>[=<options>]

加载本机代理库<libname>,例如-agentlib:hprof另请参阅 -agentlib:jdwp=help-agentlib:hprof=help

-agentpath:<pathname>[=<options>]

按完整路径名加载本机代理库

-javaagent:<jarpath>[=<options>]

加载Java编程语言代理请参阅java.lang.instrument

-splash:<imagepath>

使用指定的图像显示启动屏幕

 

大家千万不要认为Launcher就是虚拟机实现,其实从严格意义上来说,Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和Windows平台下的jvm.dll动态链接库(Linux平台下则是装载libjvm.so)。在一个JVM的进程内部,只能执行一个指定的Java程序,也就是说,当执行多个Java程序时,也就意味着同时启动了多个JVM进程。在1.5.6小节中,本书示例了如何编译一个Debug版本的HotSpot,所以为了调试跟踪方面,大家可以使用Java的另一种启动器gamma。在HotSpotLauncher是使用C语言编写的,对比gammajava后不难发现两者的源码几乎是一模一样的,仅存在少量差异,也就是说,在OpenJDKgammajava是共用的同一套Launcher源码实现。gamma的源码在HotSpot的源码目录下,大家可以在/hotspot/src/share/tools/launcher/java.c中找到。而java却并非包含在HotSpot的源码目录下,而是包含在/jdk/src/share/bin/main.c中。

尽管Launcher并非是HotSpot的核心,甚至应该算是HotSpot中比较“外围”的功能模块,但既然是这样,笔者为什么还需要大费周章的对Launcher的源码进行剖析?其实了解Launcher的执行原理是非常有意义的,Launcher既然是JVM的启动器,那么必然会由它负责调用HotSpot的核心代码对JVM进行初始化,以及由它负责维护JVM的整个生命周期。所以理解了Launcher的执行原理,才是迈进HotSpot的第一步。

 

2.3 跟踪Launcher的执行过程

本书并非只是一本单纯讲解HotSpot原理的理论性读物,从本章开始,大家将会从以往的枯燥和乏味中深入到HotSpot的具体实现细节上。对于那些曾经想要深入研究JVM技术却又止步于源码面前的Java开发人员而言,本书的知识点将会是你们迫切想要得到的答案。

尽管每一个Java开发人员对Launcher的使用都非常熟悉,但这并不代表对Launcher的执行过程也了如指掌,所以本章将会重点讲解关于Launcher的执行过程并剖析源码细节。由于HotSpotLauncher是采用C语言编写的,所以具备一定的C语言功底,必然是最好不过的。但如果你仅仅只是专攻于Java技术,也并非无法理解本章的一些源码示例,毕竟Java的语法结构就是继承自C语言,所以一些简单C语法相信你也一定能够理解。在正式开始讲解之前,笔者还是需要再次重申一次关于源码的阅读方式,希望大家千万不要过多停留在语法细节上,只需关注具体的数据结构和算法即可。

 

2.3.1 使用Launcher启动JVM

Launcher从启动到结束的整个执行链路,如图2-1所示。当成功启动Launcher后,会首先进入到Launcher的启动函数中,这一点和Java程序一样,Launcher的启动函数同样也是main()main()函数的主要任务是负责创建运行环境以及启动一个全新的线程去执行JVM的初始化和调用Java程序的main()方法。

 

2-1 Launcher的执行过程

 

main()函数成功创建运行后,就会启动一个全新的线程去调用JavaMain()函数,而JavaMain()函数的主要任务是负责调用InitializeJVM()函数。顾名思义InitializeJVM()函数肯定会负责JVM初始化的相关工作,但InitializeJVM()函数本身却并不具备初始化JVM的能力,而是由它调用本地函数JNI_CreateJavaVM()去完成真正意义上的JVM初始化。

JVM初始化完成后,Launcher接着调用LoadClass()函数和GetStaticMethodId()函数,分别获取Java程序的启动类和启动方法。当这2个步骤执行完后,Launcher就会调用本地函数jni_CallStaticVoidMethod()执行Java程序的main()方法。

最后Launcher还会调用本地函数jni_DetachCurrentThread()断开与主线程的连接,当成功与主线程断开连接后,Launcher就会一直等待程序中所有的非守护线程(non-daemon thread)全部执行结束,然后调用本地函数jni_DestroyJavaVM()JVM执行销毁。在此需要提醒大家,在JDK1.2版本之前,只有主线程才允许对JVM执行销毁,而在JDK1.2及后续版本中则没有此限制,非主线程也允许对JVM执行销毁。

 

2.3.2 启动函数main()

Launcher成功启动后,首先会进入到main()函数中对与运行环境相关的局部变量进行初始化。初始化后的局部变量不仅在程序后续创建运行环境时需要使用到,在调用JavaMain()函数时,也需要将这些变量传递过去,如下所示:

代码2-1 初始化与运行环境相关的局部变量

/* 初始化与运行环境相关的局部变量 */ 

<span style="letter-spacing: 0.4pt; font-

clearity 2014-11-05
哪个出版社,谁出的?
gao_xianglong 2014-11-05
clearity 写道
哪个出版社,谁出的?
额,小弟的,电子工业出版社出版,还没上市
RednaxelaFX 2014-11-05
喔喔欢迎!有ISBN了不?

话说样章里那个…请问出版前我可以读读整本书不?
gao_xianglong 2014-11-05
gao_xianglong 写道
clearity 写道
哪个出版社,谁出的?
额,小弟的,电子工业出版社出版,还没上市



书号应该我不用管吧,正式的出版合同是签订了,12月底,现在修修补补阶段。就等着大神你这句话!感觉心里有底了些。这本书蔡学镛看过前面4章,但是大部分他还是没看,我整理下,到时候发给你,大神,拜托了。。。。。
zking3 2014-11-06
楼主能把书目录放出吗
RednaxelaFX 2014-11-06
zking3 写道
楼主能把书目录放出吗

楼主现在肯定烦恼中呃呵呵(逃
gao_xianglong 2014-11-06
- -!秘密,秘密。。。。。。
shjgiser 2015-01-18
书还没出来么?
LeafInWind 2015-01-22
国内研究hotspot源码的同学好像越来越多了。
能否请教楼主在哪里高就啊,还是仅仅业余爱好!
Global site tag (gtag.js) - Google Analytics