这个函数给jvm运行提前创建执行环境,主要做以下几件事情
1、找到执行程序的路径
2、确定执行平台的架构
3、确定执行模式:client/server
void
CreateExecutionEnvironment(int *pargc, char ***pargv,
char jrepath[], jint so_jrepath,
char jvmpath[], jint so_jvmpath,
char jvmcfg[], jint so_jvmcfg) {
jboolean jvmpathExists;
// 设置执行程序的路径
SetExecname(*pargv);
/* Check data model flags, and exec process, if needed */
{
// 得到linux平台下执行的架构:LIBARCH32NAME-32位机器、LIBARCH64NAME-64位机器、LIBARCHNAME-默认
char *arch = (char *)GetArch(); /* like sparc or sparcv9 */
char * jvmtype = NULL;
int argc = *pargc;
char **argv = *pargv;
int running = CURRENT_DATA_MODEL; // 表示正在执行的机器位数,这里我们只讲32位的
int wanted = running; /* What data mode is being
asked for? Current model is
fine unless another model
is asked for */
// 省略一些非重要代码
}
}
这个函数就是找出执行程序的路径,也就是java命令所在的路径,主要按4种方式找,下面按查找优先顺序列出
1、/proc/self/exe 链接路径
2、绝对路径
3、相对路径
4、全局搜索
const char*
SetExecname(char **argv)
{
char* exec_path = NULL;
#if defined(__solaris__) // 这块是针对solaris操作系统的,不做解析
{
Dl_info dlinfo;
int (*fptr)();
fptr = (int (*)())dlsym(RTLD_DEFAULT, "main");
if (fptr == NULL) {
JLI_ReportErrorMessage(DLL_ERROR3, dlerror());
return JNI_FALSE;
}
if (dladdr((void*)fptr, &dlinfo)) {
char *resolved = (char*)JLI_MemAlloc(PATH_MAX+1);
if (resolved != NULL) {
exec_path = realpath(dlinfo.dli_fname, resolved);
if (exec_path == NULL) {
JLI_MemFree(resolved);
}
}
}
}
#elif defined(__linux__) // 这块是针对linux操作系统的,我们就解析这里
{
// 在linux操作系统中"/proc/self/exe"链接到当前进程的执行程序目录,比如,当我们在linux操作脚本的时候,这个目录就会链接到bash脚本程序目录,如图3-1,这里是链接到java程序,也就是我们的java命令的目录
const char* self = "/proc/self/exe";
char buf[PATH_MAX+1];
// 把链接的目录读进buf字符数组中
int len = readlink(self, buf, PATH_MAX);
if (len >= 0) {
buf[len] = '\0'; /* readlink(2) doesn't NUL terminate */
// 将buf数组转成字符串(字符指针指向),供后面逻辑使用
exec_path = JLI_StringDup(buf);
}
}
#else /* !__solaris__ && !__linux__ */
{
/* Not implemented */
}
#endif
/* 到这一步,还没拿到执行程序路径,那就再通过别的方式去找,具体方式如下:
1、如果命令是/开头的,那就按绝对路径去找
2、绝对路径找不到,那再相对路径找
3、还找不到,就通过系统搜索的方式
*/
if (exec_path == NULL) {
exec_path = FindExecName(argv[0]);
}
execname = exec_path; // 将得到的执行程序路径存储到全局变量execname中,方便后续逻辑使用
return exec_path; // 返回执行程序路径
}
图3-1
这个函数主要就是设置了NativeMemoryTracking这个参数
static void
SetJvmEnvironment(int argc, char **argv) {
static const char* NMT_Env_Name = "NMT_LEVEL_";
int i;
for (i = 0; i < argc; i++) {
char *arg = argv[i];
/*
* 路过一些jvm不需要的参数 (比如 -version or -h), 或者一些应用级参数 (例如 the main class name, or
* the -jar argument).
*/
if (i > 0) {
char *prev = argv[i - 1];
// skip non-dash arg preceded by class path specifiers
if (*arg != '-' &&
((JLI_StrCmp(prev, "-cp") == 0
|| JLI_StrCmp(prev, "-classpath") == 0))) {
continue;
}
if (*arg != '-'
|| JLI_StrCmp(arg, "-version") == 0
|| JLI_StrCmp(arg, "-fullversion") == 0
|| JLI_StrCmp(arg, "-help") == 0
|| JLI_StrCmp(arg, "-?") == 0
|| JLI_StrCmp(arg, "-jar") == 0
|| JLI_StrCmp(arg, "-X") == 0) {
return;
}
}
/*
* NativeMemoryTracking 参数是记录jvm向系统申请内存时的埋点,有一定的性能消耗,使用需要谨慎
*/
if (JLI_StrCCmp(arg, "-XX:NativeMemoryTracking=") == 0) {
int retval;
// get what follows this parameter, include "="
size_t pnlen = JLI_StrLen("-XX:NativeMemoryTracking=");
if (JLI_StrLen(arg) > pnlen) {
char* value = arg + pnlen;
size_t pbuflen = pnlen + JLI_StrLen(value) + 10; // 10 max pid digits
/*
* ensures that malloc successful
* DONT JLI_MemFree() pbuf. JLI_PutEnv() uses system call
* that could store the address.
*/
char * pbuf = (char*)JLI_MemAlloc(pbuflen);
JLI_Snprintf(pbuf, pbuflen, "%s%d=%s", NMT_Env_Name, JLI_GetPid(), value);
retval = JLI_PutEnv(pbuf);
if (JLI_IsTraceLauncher()) {
char* envName;
char* envBuf;
// ensures that malloc successful
envName = (char*)JLI_MemAlloc(pbuflen);
JLI_Snprintf(envName, pbuflen, "%s%d", NMT_Env_Name, JLI_GetPid());
printf("TRACER_MARKER: NativeMemoryTracking: env var is %s\n",envName);
printf("TRACER_MARKER: NativeMemoryTracking: putenv arg %s\n",pbuf);
envBuf = getenv(envName);
printf("TRACER_MARKER: NativeMemoryTracking: got value %s\n",envBuf);
free(envName);
}
}
}
}
}
加载Java虚拟机,就是加载动态库libjvm.so,并解析几个用得上的函数CreateJavaVM、GetDefaultJavaVMInitArgs、GetCreatedJavaVMs,这几个函数的用途,后面都会讲
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
void *libjvm;
JLI_TraceLauncher("JVM path is %s\n", jvmpath);
/*
通过dlopen函数打开jvm的动态库libjvm.so,并把它装入内存中,为了后面做符号解析和重定位
RTLD_NOW:在dlopen返回前,需要解析出所有未定义符号,否则,在dlopen会返回NULL
RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库重定位
*/
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
if (libjvm == NULL) {
// 无法通过dlopen打开,那就按正常文件的方式读取,以下都是solaris的实现,本人没有研究过solaris,就不过多描述
#if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
FILE * fp;
Elf32_Ehdr elf_head;
int count;
int location;
// 打开文件
fp = fopen(jvmpath, "r");
if (fp == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/* 读取 elf 文件头*/
count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
fclose(fp);
if (count < 1) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/*
* Check for running a server vm (compiled with -xarch=v8plus)
* on a stock v8 processor. In this case, the machine type in
* the elf header would not be included the architecture list
* provided by the isalist command, which is turn is gotten from
* sysinfo. This case cannot occur on 64-bit hardware and thus
* does not have to be checked for in binaries with an LP64 data
* model.
*/
if (elf_head.e_machine == EM_SPARC32PLUS) {
char buf[257]; /* recommended buffer size from sysinfo man
page */
long length;
char* location;
length = sysinfo(SI_ISALIST, buf, 257);
if (length > 0) {
location = JLI_StrStr(buf, "sparcv8plus ");
if (location == NULL) {
JLI_ReportErrorMessage(JVM_ERROR3);
return JNI_FALSE;
}
}
}
#endif
JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
// dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_CreateJavaVM 的符号,则把对应符号的函数地址赋值给 CreateJavaVM
ifn->CreateJavaVM = (CreateJavaVM_t)
dlsym(libjvm, "JNI_CreateJavaVM");
if (ifn->CreateJavaVM == NULL) { // 解析失败,就报错并退出函数
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
// dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_GetDefaultJavaVMInitArgs 的符号,则把对应符号的函数地址赋值给 GetDefaultJavaVMInitArgs
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->GetDefaultJavaVMInitArgs == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
// dlsym函数对动态库libjvm.so做符号解析,如果库中有函数 JNI_GetCreatedJavaVMs 的符号,则把对应符号的函数地址赋值给 GetCreatedJavaVMs
ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
dlsym(libjvm, "JNI_GetCreatedJavaVMs");
if (ifn->GetCreatedJavaVMs == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
return JNI_TRUE;
}
这个函数才是正式开始执行程序的入口,主要做2件事
1、调用ShowSplashScreen函数展示Java虚拟机的欢迎画面
2、调用ContinueInNewThread函数创建一个新线程并在其中启动Java虚拟机,执行后续流程
int
JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
// 调用ShowSplashScreen函数展示Java虚拟机的欢迎画面
ShowSplashScreen();
// 创建一个新线程并在其中启动Java虚拟机,执行后续流程
return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}