JDK8加载源码分析

1.概述

现在大多数互联网公司都是使用java技术体系搭建自己的系统,所以对java开发工程师以及java系统架构师的需求非常的多,虽然普遍的要求都是需要熟悉各种java开发框架(如目前比较流行ssi或者ssh框架),但是对于java语言本身的理解才是本质。如果你熟悉jvm原理以及jdk本身的实现,我相信对于其他开发框架的学习和深入理解应该不是很困难,因为很多灵活和高大山的框架都使用了jdk最核心的功能。除了本身框架的使用之外,凡是使用java语言开发的系统都避免不了对jvm的调优(对于系统性能要求不高可能不需要,但是对于互联网公司来说性能好像是对系统的基本要求)。如果能够深入掌握jvm原理,对于调优jvm和解决各种java相关问题是很有帮助的,当然写的java代码自然质量是很高的。

虽然我以前使用java进行编码的时间很少,对很多java的高级功能也不是很熟悉,对于jvm原理和调优也是一知半解,但是这不影响我对jvm本身原理及代码实现的学习和研究。以前研究和学习linux的源代码就觉得其乐无穷,相信现在研究jvm的源码应该也有同样的感受,并且将有非常大的收获。

正好现在java 8已经推出,业界对java8也是比较满意。作为自己学习和研究完全就可以从java8开始了,直接通过hg工具(类似git)下载jdk8的源代码进行研究学习:hg clone http://hg.openjdk.java.net/jdk8/jdk8。下载源码以后就可以开始编译了,具体请查看帮助文档吧。编译完成以后就可以运行java或者javac等相关命令了。

2.Java启动

在学习源码的时候,首先需要找到程序入口函数main,但是由于源代码太庞大而且可能有多个main函数,那么怎么可以快速的找到真正的入口main函数呢?这里在linux就可以借助调试工具gdb了。例如我们要快速找到java的启动入口函数,首先执行下面的命令gdb ./java会出现如下的信息:

GNU gdb (Ubuntu 7.8-1ubuntu4) 7.8.0.20141001-cvs

Copyright (C) 2014 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "x86_64-linux-gnu".

Type "show configuration" for configuration details.

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>.

Find the GDB manual and other documentation resources online at:

<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".

Type "apropos word" to search for commands related to "word"...

Reading symbols from ./java...done.

(gdb)

然后就进入了gdb的命令行了,这个时候使用l命令就可以看到启动文件的代码了,如下:

(gdb) l

80 char **__initenv;

81

82 int WINAPI

83 WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)

84 {

85     int margc;

86     char** margv;

87     const jboolean const_javaw = JNI_TRUE;

88

89     __initenv = _environ;

但是之看到这个启动文件中的开始代码,它不是第一行执行的代码,而且现在也不知道具体那个文件。不过我还是可以利用断点功能,我们都知道c语言的入口都是main函数,所以我们只需要对main进行打断点即可,相关命令和输出如下:

(gdb) b main

Breakpoint 1 at 0x4005f0: file /home/brucewoo/hg/jdk8/jdk/src/share/bin/main.c, line 94.

怎么样?现在足够明显了吗?其他程序可以采用同样的方式获得程序的入口函数在哪一个文件的哪一行。我们打开这个文件验证一下确实是。那我们就一起看看这个入口代码,如下:

#ifdef JAVAW

省略的windows平台相关的代码

#else /* JAVAW */

int main(int argc, char **argv)

{

int margc;

char** margv;

const jboolean const_javaw = JNI_FALSE;

#endif /* JAVAW */

#ifdef _WIN32

省略的windows平台相关的代码

#else /* *NIXES */

margc = argc;

margv = argv;

#endif /* WIN32 */

return JLI_Launch(margc, margv,

sizeof(const_jargs) / sizeof(char *), const_jargs,

sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,

FULL_VERSION,

DOT_VERSION,

(const_progname != NULL) ? const_progname : *margv,

(const_launcher != NULL) ? const_launcher : *margv,

(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,

const_cpwildcard, const_javaw, const_ergo_class);

}

然后继续看函数JLI_Launch,它接着进行java的启动。代码如下:

static jlong threadStackSize    = 0;  /* stack size of the new thread */

static jlong maxHeapSize        = 0;  /* max heap size */

static jlong initialHeapSize    = 0;  /* inital heap size */

int JLI_Launch(int argc, char ** argv,          /* main argc, argc */

int jargc, const char** jargv,          /* java args */

int appclassc, const char** appclassv,  /* app classpath */

const char* fullversion,                /* full version defined */

const char* dotversion,                 /* dot version defined */

const char* pname,                      /* program name */

const char* lname,                      /* launcher name */

jboolean javaargs,                      /* JAVA_ARGS */

jboolean cpwildcard,                    /* classpath wildcard*/

jboolean javaw,                         /* windows-only javaw */

jint ergo                               /* ergonomics class policy */

)

{

int mode = LM_UNKNOWN;

char *what = NULL;

char *cpath = 0;

char *main_class = NULL;

int ret;

InvocationFunctions ifn;//函数指针的集合

jlong startend;

char jvmpath[MAXPATHLEN];//jvm的路径

char jrepath[MAXPATHLEN];//jre的路径

char jvmcfg[MAXPATHLEN]; //jvm配置路径

_fVersion = fullversion;

_dVersion = dotversion;

_launcher_name = lname;

_program_name = pname;

_is_java_args = javaargs;

_wc_enabled = cpwildcard;

_ergo_policy = ergo;

//Initialize platform specific settings,

//会根据_JAVA_LAUNCHER_DEBUG环境变量是否设置来设置是否打印debug信息

InitLauncher(javaw);

DumpState();//根据是否设置debug来选择输出一些配置信息

if (JLI_IsTraceLauncher()) {//同样如果设置了debug信息就输出命令行参数的输出

int i;

printf("Command line args:\n");

for (i = 0; i < argc ; i++) {

printf("argv[%d] = %s\n", i, argv[i]);

}

AddOption("-Dsun.java.launcher.diag=true", NULL);

}

/*

* Make sure the specified version of the JRE is running.

*

* There are three things to note about the SelectVersion() routine:

*  1) If the version running isn't correct, this routine doesn't

*     return (either the correct version has been exec'd or an error

*     was issued).

*  2) Argc and Argv in this scope are *not* altered by this routine.

*     It is the responsibility of subsequent code to ignore the

*     arguments handled by this routine.

*  3) As a side-effect, the variable "main_class" is guaranteed to

*     be set (if it should ever be set).  This isn't exactly the

*     poster child for structured programming, but it is a small

*     price to pay for not processing a jar file operand twice.

*     (Note: This side effect has been disabled.  See comment on

*     bugid 5030265 below.)

*/

SelectVersion(argc, argv, &main_class);//选择运行时jre的版本,规则看上面注释

//创建执行的环境变量

CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath),

jvmpath, sizeof(jvmpath), jvmcfg,  sizeof(jvmcfg));

ifn.CreateJavaVM = 0;

ifn.GetDefaultJavaVMInitArgs = 0;

if (JLI_IsTraceLauncher()) {

start = CounterGet();

}

if (!LoadJavaVM(jvmpath, &ifn)) {

return(6);

}

if (JLI_IsTraceLauncher()) {

end   = CounterGet();

}

JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",

(long)(jint)Counter2Micros(end-start));

++argv;

--argc;

if (IsJavaArgs()) {

/* Preprocess wrapper arguments */

TranslateApplicationArgs(jargc, jargv, &argc, &argv);

if (!AddApplicationOptions(appclassc, appclassv)) {

return(1);

}

else {

/* Set default CLASSPATH */

cpath = getenv("CLASSPATH");

if (cpath == NULL) {

cpath = ".";

}

SetClassPath(cpath);

}

/* Parse command line options; if the return value of

* ParseArguments is false, the program should exit.

*/

if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))

{

return(ret);

}

/* 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();

/* set the -Dsun.java.launcher.* platform properties */

SetJavaLauncherPlatformProps();

return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);

}

接下来详细分析这个主流程中的各个重要函数。

(1)SelectVersion:选择jre的版本,这个函数实现的功能比较简单,就是选择正确的jre版本来作为即将运行java程序的版本。选择的方式,如果环境变量设置了_JAVA_VERSION_SET,那么代表已经选择了jre的版本,不再进行选择;否则,根据运行时给定的参数来搜索不同的目录选择,例如指定版本和限制了搜索目录等,也可能执行的是一个jar文件,所以需要解析manifest文件来获取相关信息,对应Manifest文件的数据结构,通过函数ParseManifest解析,具体请看下面注释。

/*

* Information returned from the Manifest file by the ParseManifest() routine.

* Certainly (much) more could be returned, but this is the information

* currently of interest to the C based Java utilities (particularly the

* Java launcher).

*/

typedef struct manifest_info {  /* Interesting fields from the Manifest */

char        *manifest_version;      /* Manifest-Version string */

char        *main_class;            /* Main-Class entry */

char        *jre_version;           /* Appropriate J2SE release spec */

char        jre_restrict_search;    /* Restricted JRE search */

char        *splashscreen_image_file_name; /* splashscreen image file */

} manifest_info;

最终会解析出一个真正需要的jre版本并且判断当前执行本java程序的jre版本是不是和这个版本一样,如果不一样调用linux的execv函数终止当前进出并且使用新的jre版本重新运行这个java程序,但是进程ID不会改变。

(2)CreateExecutionEnvironment,这个函数主要创建执行的一些环境,这个环境主要是指jvm的环境,例如需要确定数据模型,是32位还是64位以及jvm本身的一些配置在jvm.cfg文件中读取和解析。里面有一个重要的函数就是专门解析jvm.cfg的,如下:jint ReadKnownVMs(const char *jvmCfgName, jboolean speculative)。这个函数解析jvm.cfg文件来确定jvm的类型,jvm的类型有如下几种(是一个枚举定义):

/* Values for vmdesc.flag */

enum vmdesc_flag {

VM_UNKNOWN = -1,

VM_KNOWN,

VM_ALIASED_TO,

VM_WARN,

VM_ERROR,

VM_IF_SERVER_CLASS,

VM_IGNORE

};

然后还有一个结构体专门描述jvm的信息,如下:

struct vmdesc {

char *name;//名字

int flag;//上面的枚举定义类型

char *alias;//别名

char *server_class;//服务器类

};

总结:这个函数主要就是确定一下jvm的信息并且初始化相关信息,为后面的jvm执行准备环境。

(3)LoadJavaVM:动态加载jvm.so这个共享库,并把jvm.so中的相关函数导出并且初始化,例如JNI_CreateJavaVM函数。后期启动真正的java虚拟就是通过这里面加载的函数,里面重要的代码如下:

libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);

ifn->CreateJavaVM = (CreateJavaVM_t)dlsym(libjvm, "JNI_CreateJavaVM");

ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)

dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");

ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)

dlsym(libjvm, "JNI_GetCreatedJavaVMs");

总结:这个函数就是初始化jvm相关的初始化函数和入后函数,后面就是调用这里的JNI_CreateJavaVM函数真正的开始启动一个jvm的,这个函数会做很多的初始化工作,基本上一个完整的jvm信息在这个函数里面都能够看到,后面单独详细讲解这个函数。

(4)ParseArguments:解析命令行参数,就不多解析了,不同的命令行参数具体使用到来详细介绍其作用。

(5)JVMInit:这是启动流程最后执行的一个函数,如果这个函数返回了那么这个java启动就结束了,所有这个函数最终会以某种形式进行执行下去。具体先看看这个函数的主要流程,如下:

JVMInit->ContinueInNewThread->ContinueInNewThread0->(可能是新线程的入口函数进行执行,新线程创建失败就在原来的线程继续支持这个函数)JavaMain->InitializeJVM(初始化jvm,这个函数调用jvm.so里面导出的CreateJavaVM函数创建jvm了,JNI_CreateJavaVM这个函数很复杂)->LoadMainClass(这个函数就是找到我们真正java程序的入口类,就是我们开发应用程序带有main函数的类)->GetApplicationClass->后面就是调用环境类的工具获得main函数并且传递参数调用main函数,查找main和调用main函数都是使用类似java里面支持的反射实现的。

到此java这个启动命令全部流程解析完毕,但是其中还有很重要的两个流程没有分析。一个就是初始化和启动真正的jvm,由动态链接库jvm.so中的JNI_CreateJavaVM实现,另外一个就是最后查找入口类以及查找main入口函数的具体实现。这两个都涉及到很多的内容,后面会分别单独一篇文章来分析。

【源】:https://blog.csdn.net/qiangweiloveforever/article/details/51810294

Jvm(jdk8)源码分析1-java命令启动流程详解的更多相关文章

  1. JDK源码分析(12)之 ConcurrentHashMap 详解

    本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...

  2. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  3. jQuery 源码分析(十九) DOM遍历模块详解

    jQuery的DOM遍历模块对DOM模型的原生属性parentNode.childNodes.firstChild.lastChild.previousSibling.nextSibling进行了封装 ...

  4. jQuery 源码分析(十) 数据缓存模块 data详解

    jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...

  5. jQuery源码分析(九) 异步队列模块 Deferred 详解

    deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...

  6. vuex源码分析(二) state及strict属性 详解

    state也就是vuex里的值,也即是整个vuex的状态,而strict和state的设置有关,如果设置strict为true,那么不能直接修改state里的值,只能通过mutation来设置 例1: ...

  7. Vue.js 源码分析(九) 基础篇 生命周期详解

    先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated   .beforeDes ...

  8. Vue.js 源码分析(十二) 基础篇 组件详解

    组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...

  9. Vue.js 源码分析(十) 基础篇 ref属性详解

    ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...

随机推荐

  1. 库的操作&表的操作

    一 库的操作 掌握库的增删改查 一.系统数据库 执行如下命令,查看系统库 show databases; information_schema: 虚拟库,不占用磁盘空间,存储的是数据库启动后的一些参数 ...

  2. 由于html元素加载导致的问题

    js中要求执行的事件是在完全加载完,但由于本地环境测试一直没发现出问题,在上线后由于网络延迟导致元素加载慢,而事件执行完,没达到预期目标. 这时就需要用到属性 readyState readyStat ...

  3. poj1256(贪心+并查集)

    题目链接:http://poj.org/problem?id=1456 题意:给n件商品的价格和卖出截至时间,每一个单位时间最多只能卖出一件商品,求能获得的最大利润. 思路:首先是贪心,为获得最大利润 ...

  4. Python hash() 函数

    Python hash() 函数  Python 内置函数 描述 hash() 用于获取取一个对象(字符串或者数值等)的哈希值. 语法 hash 语法: hash(object) 参数说明: obje ...

  5. 前端 websocket用法

    <!DOCTYPE html> <meta charset="utf-8" /> <title>WebSocket Test</title ...

  6. 自动化运维工具 SaltStack 搭建

    原文地址:https://www.ibm.com/developerworks/cn/opensource/os-devops-saltstack-in-cloud/index.html#N10072 ...

  7. 遇到返回键会退到页面的问题(window.location)

    我的需求是a全局列表页->b展示列表页->c新增页(编辑页)我从b展示列表页,通过编辑进入c编辑页,保存回到b展示列表页. 重,我的b展示列表页,返回要返回的其实是a全局列表页*使用rep ...

  8. python 面向对象编程 之 上下文管理协议

    with open('path', 'r' ,encoding='utf-8') as f: 代码块 上述就叫做上线文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明 ...

  9. Jenkins构建.net项目

    一.环境搭建 1.安装所需软件 Jenkins\JDK\GIT\VS\IIS\nginx(可选) 1.1 安装iis服务: 控制面板—>程序和功能—>启用或关闭windows功能,勾选所有 ...

  10. ASCII、Unicode和UTF-8编码的区别;中英文混合截取

    ASCII编码是128个字符 中国把汉字编入GB2312,Shift_JIS/Euc-kr各国标准..... Unicode是为了解决各国乱码的,但浪费存储空间 UTF-8编码把一个Unicode字符 ...