iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >java命令中本质逻辑的示例分析
  • 265
分享到

java命令中本质逻辑的示例分析

2023-06-15 07:06:59 265人浏览 独家记忆
摘要

这篇文章将为大家详细讲解有关java命令中本质逻辑的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。前言在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一

这篇文章将为大家详细讲解有关java命令中本质逻辑的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

前言

在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一个项目了。然而我们有没有想过,一个简单的java命令背后究竟做了些什么事情?让我们看下下面几个简单的问题

java命令之后可以跟很多参数,那么这些参数是如何被解析的?为何-version会返回版本号而如果紧跟一个类名则会启动JVM

为何我们自己定义的入口方法必须满足如下的签名?是否还有其他可能性?

public static void main(String[] args) {}

如果我们需要调用自己写的native方法,必须显式地通过 System.loadLibrary() 加载动态链接库。而如果我们查看java的基础类(Thread、Object、Class等,这些类中有非常多的native方法),则会发现其内部并没有调用 System.loadLibrary() 方法,而是由静态构造函数中的 reGISterNatives() 负责注册其它的natvie方法。

例如:Thread.java

class Thread implements Runnable {    private static native void registerNatives();    static {        registerNatives();    }    ...}

不过 registerNatives() 本身也是一个native方法,那它所在动态链接库又是何时被加载的?

问题1和问题2自不必多言,答案一定在java命令中

而对于问题3,因为Thread、Object、Class等等作为jdk的原生类,其相关的动态链接库就是jvm本身(windows系统是 jvm.dll ,linux 系统是libjvm.so,Mac 系统是 libjvm.dylib),所以很容易推测其加载动态链接库的过程一定是在jvm的启动流程中。

今天我们就以上面3个问题为引子,探究一下java命令背后的本质,即jvm的启动流程

jvm的启动流程分析

既然需要分析jvm的启动流程,那么jdk和hotspot的源码是不可少的。下载地址:Http://hg.openjdk.java.net/jdk8

主入口方法

查看 java.c,jdk 目录 /src/java.base/share/native/libjli,该目录会因为不同版本的jdk有不同

入口方法是 JLI_Launch ,当然其中内容很多,我们挑选其中的重点部分来看

intJLI_Launch(args){  ...  //创建执行环境  CreateExecutionEnvironment(&arGC, &argv,                               jrepath, sizeof(jrepath),                               jvmpath, sizeof(jvmpath),                               jvmcfg,  sizeof(jvmcfg));  ...  //加载jvm  if (!LoadJavaVM(jvmpath, &ifn)) {        return(6);  }  ...  //解析命令行参数,例如-h,-version等等  if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))  {        return(ret);  }  ...  //启动jvm  return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);}

那么接下去就分别查看这几个主要方法的逻辑

CreateExecutionEnvironment:创建执行环境

这个方法根据操作系统的不同有不同的逻辑,下面以linux系统为例

查看 java_md_solinux.c,jdk 目录 /src/java.base/unix/native/libjli

CreateExecutionEnvironment(args) {        if (!GetJREPath(jrepath, so_jrepath, JNI_FALSE) ) {        JLI_ReportErrORMessage(JRE_ERROR1);        exit(2);    }    JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%sjvm.cfg",                    jrepath, FILESEP, FILESEP, FILESEP);        if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {        JLI_ReportErrorMessage(CFG_ERROR7);        exit(1);    }    jvmpath[0] = '\0';        jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);    if (JLI_StrCmp(jvmtype, "ERROR") == 0) {        JLI_ReportErrorMessage(CFG_ERROR9);        exit(4);    }        if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, 0 )) {        JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);        exit(4);    }}

主要有以下几4个步骤

确定jre的路径

这里会优先寻找应用程序当前目录

if (GetApplicationHome(path, pathsize)) {    ...}if (GetApplicationHomeFromDll(path, pathsize)) {    ...}

根据jre拼接 jvm.cfg 的路径,并读取可用的jvm配置

一般 jvm.cfg 文件在 /jre/lib 中,其内容如下:

-server KNOWN-client IGNORE

上述2行配置分别对应不同的jvm的版本,例如第一行 -server KNOWN ,那么在加载jvm动态链接库的时候就会去 /jre/lib/server 目录中寻找

检查jvm类型

在执行java命令的时候,可以通过命令指定jvm版本,如果没有指定,那么就采用jvm.cfg中的第一个jvm版本

i = KnownVMIndex(arg);if (i >= 0) {    ...}else if (JLI_StrCCmp(arg, "-XXaltjvm=") == 0 || JLI_StrCCmp(arg, "-J-XXaltjvm=") == 0) {    ...}

获取动态链接库的路径

根据前面检查jvm类型的结果,获取到对应的jvm动态链接库的路径,全部按照默认的话,在Mac系统中获取到的lib路径如下

路径中的server正是之前在cfg文件中读取到的-server

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/server/libjvm.dylib

LoadJavaVM:加载jvm

查看 java_md_solinux.c,jdk 目录 /src/java.base/unix/native/libjli

jbooleanLoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){           libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);    ...        ifn->CreateJavaVM = (CreateJavaVM_t)        dlsym(libjvm, "JNI_CreateJavaVM");            if (ifn->CreateJavaVM == NULL) {        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());        return JNI_FALSE;    }    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");          if (ifn->GetDefaultJavaVMInitArgs == NULL) {        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());        return JNI_FALSE;    }    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)        dlsym(libjvm, "JNI_GetCreatedJavaVMs");          if (ifn->GetCreatedJavaVMs == NULL) {        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());        return JNI_FALSE;    }}

主要步骤如下:

加载动态链接库,也正是我们第一个问题的答案所在

dlopen方法是dynamic link open的缩写,在打开文件的同时,加载动态链接库。可以通过 man dlopen 命令查看说明

man dlopendlopen -- load and link a dynamic library or bundle

链接并调用jvm中的 JNI_CreateJavaVM 、GetDefaultJavaVMInitArgs、GetCreatedJavaVMs

dlsym方法是dynamic link symbol的缩写,将动态链接库中的方法链接到当前方法上

man dlsymdlsym -- get address of a symbol

这3个方法顾名思义,分别是创建jvm、获取默认的jvm启动参数、获取创建完成的jvm。这3个方法的入口在

hotspot 目录 /src/share/vm/prims/jni.cpp

文件中,有兴趣的同学可以自行查看

ParseArguments:解析命令行参数

查看 java.c,jdk 目录 /src/java.base/share/native/libjli

static jbooleanParseArguments(int *pargc, char ***pargv,               int *pmode, char **pwhat,               int *pret, const char *jrepath){  ...  if (JLI_StrCmp(arg, "--version") == 0) {      printVersion = JNI_TRUE;      printTo = USE_STDOUT;      return JNI_TRUE;  }  ...  if (JLI_StrCCmp(arg, "-ss") == 0 ||              JLI_StrCCmp(arg, "-oss") == 0 ||              JLI_StrCCmp(arg, "-ms") == 0 ||              JLI_StrCCmp(arg, "-mx") == 0) {      char *tmp = JLI_MemAlloc(JLI_StrLen(arg) + 6);      sprintf(tmp, "-X%s", arg + 1);       AddOption(tmp, NULL);  }  ...}

其中的参数一共有2大类。

类似于 --version 的参数在解析之后会直接返回

类似于 -mx、-mx 的参数则会通过 AddOption 方法添加成为 VM option

voidAddOption(char *str, void *info){  ...}

JVMInit:启动jvm

查看 java_md_solinux.c,jdk 目录 /src/java.base/unix/native/libjli

JVMInit(InvocationFunctions* ifn, jlong threadStackSize,        int argc, char **argv,        int mode, char *what, int ret){    //在一个新线程中启动jvm    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);}

在该方法中,会调用 ContinueInNewThread 创建一个新线程启动jvm

查看 java.c,jdk 目录 /src/java.base/share/native/libjli

intContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,                    int argc, char **argv,                    int mode, char *what, int ret){  ...    rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);  return (ret != 0) ? ret : rslt;}

在该方法中,会调用 ContinueInNewThread0 并传入 JavaMain 入口方法

查看 java_md_solinux.c,jdk 目录 /src/java.base/unix/native/libjli

intContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {    //创建一个新线程执行传入的continuation,其实也就是外面传入的main方法    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {      void * tmp;      //当前线程阻塞      pthread_join(tid, &tmp);      rslt = (int)(intptr_t)tmp;    }    ...}

在该方法中,会创建一个新线程调用传入的 main 方法,而当前线程则阻塞

因为这里pthread_join是等待在运行main方法的线程上,所以java程序运行时,如果main线程运行结束了,整个进程就会结束,而由main启动的子线程对整个进程是没有影响的

查看 java.c,jdk 目录 /src/java.base/share/native/libjli

int JNICALLJavaMain(void * _args){  //启动jvm  if (!InitializeJVM(&vm, &env, &ifn)) {      JLI_ReportErrorMessage(JVM_ERROR1);      exit(1);  }  ...  //加载主类  mainClass = LoadMainClass(env, mode, what);  //找到main方法id  mainID = (*env)->GetStaticMethodID(env, mainClass, "main",                                       "([Ljava/lang/String;)V");  //通过jni回调java代码中的main方法  (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);}

这里对于main方法的方法名和签名都是固定判断的,所以无论是什么java程序,入口方法必须是 public static void main(String[] args)

到此jvm从准备启动到最后执行main方法的代码流程就结束了。因为这个流程的方法分散在不同的文件中,会很让人头晕,所以我总结了成了以下结构,方便大家理解

入口方法:JLI_Launch        |--------->创建执行环境:CreateExecutionEnvironment        |          |--------->获取jre的路径:GetJREPath        |          |--------->读取jvm配置:ReadKnownVMs        |          |--------->检查jvm类型:CheckJvmType        |          |--------->获取jvm动态链接库路径:GetJVMPath         |--------->加载jvm动态链接库:LoadJavaVM        |          |--------->加载动态链接库:dlopen        |          |--------->链接jvm方法:dlsym        |--------->解析命令行参数:ParseArguments        |          |--------->类似于 --version 的参数在解析之后会直接返回        |          |--------->类似于 -mx、-mx 的参数则会通过 AddOption 方法添加成为 VM option        |--------->启动jvm并执行main方法:JVMInit                          |--------->创建一个新线程并执行后续任务:ContinueInNewThread                               |--------->创建新线程执行main方法:ContinueInNewThread0(JavaMain)                                 |--------->创建新线程,用于执行传入的main方法:pthread_create                                 |--------->阻塞当前线程:pthread_join                               |--------->获取main方法:JavaMain                                 |--------->加载主类:LoadMainClass                                 |--------->根据签名获取main方法的id:GetStaticMethodID                                 |--------->执行main方法:CallStaticVoidMethod

关于“java命令中本质逻辑的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

--结束END--

本文标题: java命令中本质逻辑的示例分析

本文链接: https://www.lsjlt.com/news/278583.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java命令中本质逻辑的示例分析
    这篇文章将为大家详细讲解有关java命令中本质逻辑的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。前言在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一...
    99+
    2023-06-15
  • java命令中本质逻辑的实例分析
    这篇文章主要介绍java命令中本质逻辑的实例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一个项目了。然而我们有没有...
    99+
    2023-06-15
  • 详谈java命令的本质逻辑揭秘
    前言 在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一个项目了。然而我们有没有想过,一个简单的java命令背后究竟做了些什么事情?让我们...
    99+
    2024-04-02
  • 关于java命令的本质逻辑揭秘过程
    前言 在日常编码中,有了ide的支持,我们已经很少直接在命令行中直接执行java XXX命令去启动一个项目了。然而我们有没有想过,一个简单的java命令背后究竟做了些什么事情?让我...
    99+
    2024-04-02
  • Java中逻辑结构的示例分析
    这篇文章主要介绍Java中逻辑结构的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Java中的逻辑结构逻辑结构 Java中的逻辑结构 顺序结构分支结构循环结构顺序结构顺序结构顾名思义,就是按照代码的顺序依次往...
    99+
    2023-06-14
  • Java中逻辑控制的示例分析
    这篇文章将为大家详细讲解有关Java中逻辑控制的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、逻辑控制语句1. 顺序结构像我们写的代码,执行时会按照从上到下一行一行的执行。这就是顺序结构,不同...
    99+
    2023-06-20
  • Oracle逻辑读的示例分析
    这篇文章给大家分享的是有关Oracle逻辑读的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1.物理读(physical read)当数据块第一次读取到,就会缓存到buf...
    99+
    2024-04-02
  • MySQL中逻辑查询的示例分析
    这篇文章主要介绍了MySQL中逻辑查询的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。在MySQL中,查询是用于构建DELET...
    99+
    2024-04-02
  • Mysql逻辑架构的示例分析
    小编给大家分享一下Mysql逻辑架构的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 整体架构图和其它数据库相比,M...
    99+
    2024-04-02
  • Linux中使用文本编辑器vi命令的示例分析
    小编给大家分享一下Linux中使用文本编辑器vi命令的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!vi/vim是什么?Linux世界几乎所有的配置文件都...
    99+
    2023-06-09
  • Linux Shell脚本多命令执行逻辑的示例详解
    目录简介一、分号二、&&三、||案例剖析简介 Linux 中可以使用分号";“、双and号”&&“和双竖...
    99+
    2022-11-13
    Shell脚本多命令执行逻辑 Shell 多命令执行逻辑 Shell 多命令执行
  • python中逻辑回归限制的示例分析
    这篇文章主要为大家展示了“python中逻辑回归限制的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“python中逻辑回归限制的示例分析”这篇文章吧。1.逻辑回归的限制逻辑回归分类的时候...
    99+
    2023-06-25
  • InnoDB中逻辑存储结构的示例分析
    这篇文章主要为大家展示了“InnoDB中逻辑存储结构的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“InnoDB中逻辑存储结构的示例分析”这篇文章吧。I...
    99+
    2024-04-02
  • shell中基本计算、逻辑运算、位运算的示例分析
    这篇文章给大家分享的是有关shell中基本计算、逻辑运算、位运算的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。以下面的格式提供运算表达式:$(( expression )) $ echo $((5*(3...
    99+
    2023-06-09
  • MySQL逻辑体系架构的示例分析
    这篇文章主要为大家展示了“MySQL逻辑体系架构的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“MySQL逻辑体系架构的示例分析”这篇文章吧。Mysql...
    99+
    2024-04-02
  • R语言逻辑回归的示例分析
    这篇文章主要介绍R语言逻辑回归的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!逻辑回归> ###############逻辑回归> setwd("/Users/yao...
    99+
    2023-06-14
  • mysql中desc esc基本命令的示例分析
    小编给大家分享一下mysql中desc esc基本命令的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧! Sql代码&n...
    99+
    2024-04-02
  • python中and和or逻辑运算符的示例分析
    这篇文章给大家介绍python中and和or逻辑运算符的示例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、概述python中的逻辑操作符and 和or,也叫惰性求值,由于是惰性,只要确定了值就不往后解析代码了。...
    99+
    2023-06-26
  • R语言逻辑型运算的示例分析
    这篇文章主要介绍R语言逻辑型运算的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、逻辑型向量与比较运算        逻辑型是...
    99+
    2023-06-29
  • C#事件本质的示例分析
    这篇文章将为大家详细讲解有关C#事件本质的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。C#事件本质上是对消息的封装,是IDE编程环境为了简化编程而提供的有用的工具。这个封装是在窗体过程中实现的。...
    99+
    2023-06-17
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作