jvm原理及性能调优合集(JVM系列二)
jvm原理及性能调优合集(JVM系列二)// solaris/bin/java_md_solinux.c /* * Block current thread and continue execution in a new thread */ int ContinueInNewThread0(int (JNICALL *continuation)(void *) jlong stack_size void * args) { int rslt; #ifdef __linux__ pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr PTHREAD_CREATE_JOINABLE); if (stack_size > 0) {
上一篇粗略讲了下jvm的启动过程,但很多路子还没跑通。其中非常核心的,加载vm的过程。这个可以在hotspot中找到端倪。但jvm启动,又是如何载入java代码呢。
1. JavaMain加载流程我们知道,java中入口是在main方法中,可以在命令行中指定main类,或者jar包中指定的manifest.xml中指定的main类。在java.c中,我们可以看到一个JavaMain方法,不知从何而来,但很像是直接加载java入口的方法。
// share/bin/java.c
// 加载 main 函数类
// 通过引入 JavaMain() 接入java方法
// #define JNICALL __stdcall
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;
// 一些jvm的调用实例,在之前的步骤中,通过加载相应动态链接方法,保存起来的
/**
* ifn->CreateJavaVM =
* (void *)GetProcAddress(handle "JNI_CreateJavaVM");
* ifn->GetDefaultJavaVMInitArgs =
* (void *)GetProcAddress(handle "JNI_GetDefaultJavaVMInitArgs");
*/
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start end;
// collector
RegisterThread();
/* Initialize the virtual machine */
start = CounterGet();
// 初始化jvm,失败则退出
if (!InitializeJVM(&vm &env &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
// jvm检查完毕,如果只是一些展示类请求,则展示信息后,退出jvm
if (showSettings != NULL) {
ShowSettings(env showSettings);
/**
* 宏是神奇的操作,此处 *env 直接引用
#define CHECK_EXCEPTION_LEAVE(CEL_return_value) \
do { \
if ((*env)->ExceptionOccurred(env)) { \
JLI_ReportExceptionDescription(env); \
ret = (CEL_return_value); \
LEAVE(); \
} \
} while (JNI_FALSE)
*/
CHECK_EXCEPTION_LEAVE(1);
}
// 调用 LEAVE() 方法的目的在于主动销毁jvm线程
// 且退出当前方法调用,即 LEAVE() 后方法不再被执行
/*
* Always detach the main thread so that it appears to have ended when
* the application's main method exits. This will invoke the
* uncaught exception handler machinery if main threw an
* exception. An uncaught exception handler cannot change the
* launcher's return code except by calling System.exit.
*
* Wait for all non-daemon threads to end then destroy the VM.
* This will actually create a trivial new Java waiter thread
* named "DestroyJavaVM" but this will be seen as a different
* thread from the one that executed main even though they are
* the same C thread. This allows mainThread.join() and
* mainThread.isAlive() to work as expected.
*/
/**
*
*
#define LEAVE() \
do { \
if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \
JLI_ReportErrorMessage(JVM_ERROR2); \
ret = 1; \
} \
if (JNI_TRUE) { \
(*vm)->DestroyJavaVM(vm); \
return ret; \
} \
} while (JNI_FALSE)
*/
if (printVersion || showVersion) {
PrintJavaVersion(env showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
/* 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[-] = '%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).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/
// 加载 main 指定的class类
mainClass = LoadMainClass(env mode what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* In some cases when launching an application that needs a helper e.g. a
* JavaFX application with no main method the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass -1);
/*
* PostJVMInit uses the class name as the application name for GUI purposes
* for example on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
// 加载main() 方法前执行初始化
PostJVMInit(env appClass vm);
CHECK_EXCEPTION_LEAVE(1);
/*
* 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.
*/
// 获取main()方法id main(String[] args)
mainID = (*env)->GetStaticMethodID(env mainClass "main"
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
// 构建args[] 参数
mainArgs = CreateApplicationArgs(env argv argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */
// 调用java实现的main()方法
// XX:: 重要实现
(*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();
}
/*
* Loads a class and verifies that the main class is present and it is ok to
* call it for more details refer to the java implementation.
*/
static jclass
LoadMainClass(JNIEnv *env int mode char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start end;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
// checkAndLoadMain(String) 方法作为中间main()调用
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env cls
"checkAndLoadMain"
"(ZILjava/lang/String;)Ljava/lang/Class;"));
str = NewPlatformString(env name);
CHECK_JNI_RETURN_0(
result = (*env)->CallStaticObjectMethod(
env cls mid USE_STDERR mode str));
if (JLI_IsTraceLauncher()) {
end = CounterGet();
printf("%ld micro seconds to load main class\n"
(long)(jint)Counter2Micros(end-start));
printf("----%s----\n" JLDEBUG_ENV_ENTRY);
}
return (jclass)result;
}
// 初始化jvm 主要是调用 CreateJavaVM() 方法,进行创建jvm操作
/*
* Initializes the Java Virtual Machine. Also frees options array when
* finished.
*/
static jboolean
InitializeJVM(JavaVM **pvm JNIEnv **penv InvocationFunctions *ifn)
{
JavaVMInitArgs args;
jint r;
memset(&args 0 sizeof(args));
args.version = JNI_VERSION_1_2;
args.nOptions = numOptions;
args.options = options;
args.ignoreUnrecognized = JNI_FALSE;
if (JLI_IsTraceLauncher()) {
int i = 0;
printf("JavaVM args:\n ");
printf("version 0xlx " (long)args.version);
printf("ignoreUnrecognized is %s "
args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE");
printf("nOptions is %ld\n" (long)args.nOptions);
for (i = 0; i < numOptions; i )
printf(" option[-] = '%s'\n"
i args.options[i].optionString);
}
r = ifn->CreateJavaVM(pvm (void **)penv &args);
JLI_MemFree(options);
return r == JNI_OK;
}
略去核心加载jvm的实现,要加载main类,还是比较简单的。主要就是通过前面查找出的main_class 然后通过在jvm的实例中获取到的各方法的函数指针,然后按照字节码的规范,调用 MainClass.main(String[]) 方法。当然了,为了兼容其他非main的场景,它还有很多附加处理逻辑。
另外,如何翻译java代码,并执行,这是jvm的重中之重。即编译原理的拿手好戏,也是我们普通程序员越不过的坎!但至少,我们来到了门槛前面!
2. jvm启动框架前面讲了加载main方法的过程,大致理解了c如何启动调用java的main的。那么,这又是如何调用准备的呢,在这之前都需要做哪些准备呢?
实际上,这是平台相关的实现。
// share/bin/java.c
/*
* Entry point.
*/
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 start end;
char jvmpath[MAXPATHLEN];
char jrepath[MAXPATHLEN];
char jvmcfg[MAXPATHLEN];
_fVersion = fullversion;
_dVersion = dotversion;
_launcher_name = lname;
_program_name = pname;
_is_java_args = javaargs;
_wc_enabled = cpwildcard;
_ergo_policy = ergo;
// 初始化启动器
InitLauncher(javaw);
// 打印状态
DumpState();
// 跟踪调用启动
if (JLI_IsTraceLauncher()) {
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.)
*/
// 解析命令行参数,选择一jre版本
SelectVersion(argc argv &main_class);
CreateExecutionEnvironment(&argc &argv
jrepath sizeof(jrepath)
jvmpath sizeof(jvmpath)
jvmcfg sizeof(jvmcfg));
if (!IsJavaArgs()) {
// 设置一些特殊的环境变量
SetJvmEnvironment(argc argv);
}
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
// 加载VM 重中之重
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);
}
// macos/bin/java_md_macos.c
// MacOSX we may continue in the same thread
int
JVMInit(InvocationFunctions* ifn jlong threadStackSize
int argc char **argv
int mode char *what int ret) {
if (sameThread) {
JLI_TraceLauncher("In same thread\n");
// need to block this thread against the main thread
// so signals get caught correctly
__block int rslt = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{
JavaMainArgs args;
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
// 调用 JavaMain()
rslt = JavaMain(&args);
}];
/*
* We cannot use dispatch_sync here because it blocks the main dispatch queue.
* Using the main NSRunLoop allows the dispatch queue to run properly once
* SWT (or whatever toolkit this is needed for) kicks off it's own NSRunLoop
* and starts running.
*/
[op performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES];
}
[pool drain];
return rslt;
} else {
return ContinueInNewThread(ifn threadStackSize argc argv mode what ret);
}
}
以上是mac调用 javaMain的方式。在windows 以及linux上则稍有不同。
// windows/bin/java_md.c
int
JVMInit(InvocationFunctions* ifn jlong threadStackSize
int argc char **argv
int mode char *what int ret)
{
ShowSplashScreen();
return ContinueInNewThread(ifn threadStackSize argc argv mode what ret);
}
// java.c
int
ContinueInNewThread(InvocationFunctions* ifn jlong threadStackSize
int argc char **argv
int mode char *what int ret)
{
/*
* If user doesn't specify stack size check if VM has a preference.
* Note that HotSpot no longer supports JNI_VERSION_1_1 but it will
* return its default stack size through the init args structure.
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1 0 sizeof(args1_1));
args1_1.version = JNI_VERSION_1_1;
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
if (args1_1.javaStackSize > 0) {
threadStackSize = args1_1.javaStackSize;
}
}
{ /* Create a new thread to create JVM and invoke main method */
JavaMainArgs args;
int rslt;
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
// 传入 JavaMain() 函数参数,在新线程中运行 JavaMain
rslt = ContinueInNewThread0(JavaMain threadStackSize (void*)&args);
/* If the caller has deemed there is an error we
* simply return that otherwise we return the value of
* the callee
*/
return (ret != 0) ? ret : rslt;
}
}
/*
* Block current thread and continue execution in a new thread
*/
int
ContinueInNewThread0(int (JNICALL *continuation)(void *) jlong stack_size void * args) {
int rslt = 0;
unsigned thread_id;
#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
#define STACK_SIZE_PARAM_IS_A_RESERVATION (0x10000)
#endif
/*
* STACK_SIZE_PARAM_IS_A_RESERVATION is what we want but it's not
* supported on older version of Windows. Try first with the flag; and
* if that fails try again without the flag. See MSDN document or HotSpot
* source (os_win32.cpp) for details.
* 调用底层内核方法,创建新线程,将JavaMain(args)作为函数调用处理。创建后立即执行
* 如果第一次带参创建失败,则弃参重试一次
*/
HANDLE thread_handle =
(HANDLE)_beginthreadex(NULL
(unsigned)stack_size
continuation
args
STACK_SIZE_PARAM_IS_A_RESERVATION
&thread_id);
if (thread_handle == NULL) {
thread_handle =
(HANDLE)_beginthreadex(NULL
(unsigned)stack_size
continuation
args
0
&thread_id);
}
/* AWT preloading (AFTER main thread start) */
#ifdef ENABLE_AWT_PRELOAD
/* D3D preloading */
if (awtPreloadD3D != 0) {
char *envValue;
/* D3D routines checks env.var J2D_D3D if no appropriate
* command line params was specified
*/
envValue = getenv("J2D_D3D");
if (envValue != NULL && JLI_StrCaseCmp(envValue "false") == 0) {
awtPreloadD3D = 0;
}
/* Test that AWT preloading isn't disabled by J2D_D3D_PRELOAD env.var */
envValue = getenv("J2D_D3D_PRELOAD");
if (envValue != NULL && JLI_StrCaseCmp(envValue "false") == 0) {
awtPreloadD3D = 0;
}
if (awtPreloadD3D < 0) {
/* If awtPreloadD3D is still undefined (-1) test
* if it is turned on by J2D_D3D_PRELOAD env.var.
* By default it's turned OFF.
*/
awtPreloadD3D = 0;
if (envValue != NULL && JLI_StrCaseCmp(envValue "true") == 0) {
awtPreloadD3D = 1;
}
}
}
if (awtPreloadD3D) {
AWTPreload(D3D_PRELOAD_FUNC);
}
#endif /* ENABLE_AWT_PRELOAD */
if (thread_handle) {
// 等待线程结束,并获取返回码
WaitForSingleObject(thread_handle INFINITE);
GetExitCodeThread(thread_handle &rslt);
CloseHandle(thread_handle);
} else {
// 如果实在是创建线程失败,则自身直接执行该方法即可
rslt = continuation(args);
}
#ifdef ENABLE_AWT_PRELOAD
if (awtPreloaded) {
AWTPreloadStop();
}
#endif /* ENABLE_AWT_PRELOAD */
return rslt;
}
在linux中的创建线程如下:
// solaris/bin/java_md_solinux.c
/*
* Block current thread and continue execution in a new thread
*/
int
ContinueInNewThread0(int (JNICALL *continuation)(void *) jlong stack_size void * args) {
int rslt;
#ifdef __linux__
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr stack_size);
}
// 常见的 pthread_xx 方式 创建线程
if (pthread_create(&tid &attr (void *(*)(void*))continuation (void*)args) == 0) {
void * tmp;
pthread_join(tid &tmp);
rslt = (int)tmp;
} else {
/*
* Continue execution in current thread if for some reason (e.g. out of
* memory/LWP) a new thread can't be created. This will likely fail
* later in continuation as JNI_CreateJavaVM needs to create quite a
* few new threads anyway just give it a try..
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
#else /* ! __linux__ */
thread_t tid;
long flags = 0;
if (thr_create(NULL stack_size (void *(*)(void *))continuation args flags &tid) == 0) {
void * tmp;
thr_join(tid NULL &tmp);
rslt = (int)tmp;
} else {
/* See above. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#endif /* __linux__ */
return rslt;
}
即相同的语义,不同平台下的各自实现而已。
通过C语言的函数传递,从而进行调用。通过创建一个新线程,调用JavaMain(); 由javaMain处理所有java事务。而自身则只需等待JavaMain完成即可。这也符合面向过程编程思想。
3. jvm初始化小结我们平时是通过 java xxx.xxx 或者 java -jar xx.jar 启动jvm 通过这两篇文章,我们也看清了其背后的原理。就是通过解析各种参数,验证各种参数,验证jre环境,然后验证jre是否可用,最后将指定的mainClass加载出来。丢到一个新的线程中去执行,将执行权力转发给java代码,最后等待该线程完成。
应该说事个流程没有难点,或者说一切都很理所当然。但其中的核心,都是通过加载JavaVM()去做的,也就是真正的jvm. 然后通过或者对应的几个接口方法地址,进行调用,从而完成启动任务。所以,java虽是一个启动命令,但核心并不在这里。而是在 hotspot 或其他地方。jdk 毕竟只是一个工具箱而已。jre 才是关键。
看完三件事❤️如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注头条号 『 JAVA后端架构 』,不定期分享原创知识。
- 同时可以期待后续文章ing
- 关注作者后台私信【888】有惊喜相送
作者:等你归去来
出处:https://www.cnblogs.com/yougewe/p/14400859.html