源码学习准备

前言

java的跨平台是如何实现的,为了提升性能又做了哪些工作,所以学习jdk源码很有必要。我们知道hotspot是C++写的,其实里面有大量的汇编直接操作CPU寄存器,C++的很多特性和具体的汇编指令我们不必要全部了解,大概知道有栈创建对象、堆创建对象,方法调用,宏定义等基本的就可以。通过学习hotspot源码会对加深计算机的操作系统、CPU寄存器、内存以及指针压缩、线程栈内存分配、大页内存、NUMA架构等了解,下面就让我们进入hotspot源码的世界。

源码下载

openjdk地址:http://openjdk.java.net
jdk(jdk7):http://hg.openjdk.java.net/jdk7/jdk7/jdk/archive/tip.zip
jvm(hotspot):http://hg.openjdk.java.net/jdk8/jdk8/hotspot/archive/tip.zip
说明一下,这里的jdk只是我们JAVA开发环境的JAVA API及实现部分,而hotspot是就是我们JAVA开发环境JDK里面的JRE其中JVM部分。

工具安装

1、eclipse c++版本 :
eclipse 官网下载页面搜索框输入c++即可下载最新版本
https://iso.mirrors.ustc.edu.cn/eclipse/technology/epp/downloads/release/2021-06/R/eclipse-cpp-2021-06-R-win32-x86_64.zip
2、安装java 插件
安装插件输入java,选择最简单的java program language

image.png

导入项目

导入源码后调整一下jdk里面代码路径,将src/share/classes子目录拷贝到src目录下。
这个就是我们熟悉的JRE中rt.jar的源码(重点关注java安装包的src源码中不包含的sun目录),调整代码后就不会出现代码路径问题。


image.png

熟悉目录结构

jdk以及hotspot的源码目录结构有相同点,就是基础相同的代码在share目录,操作系统及CPU有区别的分别放置各自目录,比如jdk中除了share目录有windows、solaris(solaris、linux等unix派系),这个就是操作系统相关的代码。Hotspot虚拟机的源码除了share目录还有os(操作系统相关)、cpu(cpu架构指令相关)、os_cpu(操作系统和CPU架构相关)目录。

源码阅读小技巧

hotspot源码是c、c++写的,阅读起来比较困难,经常有类 或 方法不知道哪里来的,eclipse也跟踪不进去,我们一般看include了哪些头文件,按住CTRL点不进去的代码先在include的头文件找,有的是多层include,实在找不着的就全局搜索了。比如jdk的share/bin/main.c中方法JLI_Launch不知道哪里来的,我们看导main.c里面有#include "defines.h", 就去defines.h里面找,发现defines.h里面有#include "java.h",那我们基本知道是java.h或java.c里面定义的JLI_Launch。

C C++小知识

new对象

栈上分配对象 ObjectClass obj;
堆上分配对象 ObjectClass *obj = new ObjectClass(); ,调用方法用->

方法调用

1、::类静态方法调用 Lable p = Label::create();
2、->类对象方法指针调用 p.create();
3、.类对象方法实例调用 (
p).create();

引用与指针

&标识引用
标识指针 (p) 标识指针指向的值
指针和引用都可以做为参数,func(int *i) 、 func(int &i)

正式开始

我们都知道java程序运行过程,由main方法或者jar的manifest文件定义的启动类的main方法开始,这个过程又是怎样的呢,就从JVM启动开始一步一步探究。
java Test.class
则调jdk的src/share/bin/main.c中JLI_Launch方法


image.png

JLI_Launch我通过#include "defines.h"引入,defines.h中又引入了java.h


image.png

因此我们找到了java.c中定义的JLI_Launch方法


image.png

这个方法里面前面都是校验和准备工作,判断CPU架构、找到jre路径、解析java启动命令带的参数、装载动态库jvm.all或jvm.so(LoadJavaVM)等等,最后一步是ContinueInNewThread方法,启动了一个线程,也就是我们的main主线程。
ContinueInNewThread方法中调用ContinueInNewThread0方法,
image.png

ContinueInNewThread0方法我们只能在java.h中有定义,而java.h中有引入#include "java_md.h" ,这个根据操作系统不同实现不一样,我们看一下unix派系操作系统的实现,代码在jdk的src/solaris/bin/java_md.c。


image.png

意思是根据线程栈大小创建线程,然后从continuation地址开始,等待CPU调度,也就是CPU执行的是continuation地址对应的代码,我们再回过头看下java.c中的调用传入参数,rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args); 因此实际调用的是java.c中的JavaMain方法。

//java.c
int JNICALL
JavaMain(void * _args)
{
   JavaMainArgs *args = (JavaMainArgs *)_args;
   int argc = args->argc;
   char **argv = args->argv;
   int mode = args->mode;
   char *what = args->what;
   InvocationFunctions ifn = args->ifn;

   JavaVM *vm = 0;
   JNIEnv *env = 0;
   jclass mainClass = NULL;
   jmethodID mainID;
   jobjectArray mainArgs;
   int ret = 0;
   jlong start, end;

   /* Initialize the virtual machine */
   start = CounterGet();
   if (!InitializeJVM(&vm, &env, &ifn)) {
       JLI_ReportErrorMessage(JVM_ERROR1);
       exit(1);
   }

   if (printVersion || showVersion) {
       PrintJavaVersion(env, showVersion);
       CHECK_EXCEPTION_LEAVE(0);
       if (printVersion) {
           LEAVE();
       }
   }

   if (showSettings != NULL) {
       ShowSettings(env, showSettings);
       CHECK_EXCEPTION_LEAVE(1);
   }
   /* If the user specified neither a class name nor a JAR file */
   if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
       PrintUsage(env, printXUsage);
       CHECK_EXCEPTION_LEAVE(1);
       LEAVE();
   }

   FreeKnownVMs();  /* after last possible PrintUsage() */

   if (JLI_IsTraceLauncher()) {
       end = CounterGet();
       JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
              (long)(jint)Counter2Micros(end-start));
   }

   /* At this stage, argc/argv have the application's arguments */
   if (JLI_IsTraceLauncher()){
       int i;
       printf("%s is '%s'\n", launchModeNames[mode], what);
       printf("App's argc is %d\n", argc);
       for (i=0; i < argc; i++) {
           printf("    argv[%2d] = '%s'\n", i, argv[i]);
       }
   }

   ret = 1;

   /*
    * Get the application's main class.
    *
    * See bugid 5030265.  The Main-Class name has already been parsed
    * from the manifest, but not parsed properly for UTF-8 support.
    * Hence the code here ignores the value previously extracted and
    * uses the pre-existing code to reextract the value.  This is
    * possibly an end of release cycle expedient.  However, it has
    * also been discovered that passing some character sets through
    * the environment has "strange" behavior on some variants of
    * Windows.  Hence, maybe the manifest parsing code local to the
    * launcher should never be enhanced.
    *
    * Hence, future work should either:
    *     1)   Correct the local parsing code and verify that the
    *          Main-Class attribute gets properly passed through
    *          all environments,
    *     2)   Remove the vestages of maintaining main_class through
    *          the environment (and remove these comments).
    */
   mainClass = LoadMainClass(env, mode, what);
   CHECK_EXCEPTION_NULL_LEAVE(mainClass);

   /*
    * The LoadMainClass not only loads the main class, it will also ensure
    * that the main method's signature is correct, therefore further checking
    * is not required. The main method is invoked here so that extraneous java
    * stacks are not in the application stack trace.
    */
   mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                      "([Ljava/lang/String;)V");
   CHECK_EXCEPTION_NULL_LEAVE(mainID);

   /* Build argument array */
   mainArgs = NewPlatformStringArray(env, argv, argc);
   CHECK_EXCEPTION_NULL_LEAVE(mainArgs);

   /* Invoke main method. */
   (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

   /*
    * The launcher's exit code (in the absence of calls to
    * System.exit) will be non-zero if main threw an exception.
    */
   ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
   LEAVE();
}

准备阶段判断如果JVM没有初始化则初始化JVM(InitializeJVM),初始化JVM先创建JVM(CreateJavaVM),这个会通过JNI创建虚拟机,我们后面再看下JVM是如何创建的。
JVM准备好了再执行main方法,核心有4步,1:mainClass = LoadMainClass(env, mode, what); 加载主class文件; 2: mainID = (env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); 获取静态方法main方法;3:mainArgs = NewPlatformStringArray(env, argv, argc); 获取方法参数列表;4:(env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);调用main方法。

后面章节将会详细讲解这个主方法,解开我们的疑惑1:class是如何加载的,2:main方法是怎么调用。

你可能感兴趣的:(源码学习准备)