學習源碼,是代碼技術提升的一個重要手段,在java相關的內容中,有各類框架的源碼、tomcat的源碼、android源碼、jdk標準庫的源碼和jvm的源碼等各種值得學習的內容。在這些內容當中,jvm的源碼是唯一一種純用c、c++完成的,因此對於很多寫習慣了java的程序員來說,閱讀jvm是一件不那麼容易的事情,而且目前大多數的jvm源碼相當冗長,不易於閱讀,所以爲了簡單的瞭解jvm的運行機制,我們選擇了“麻雀雖小,五臟俱全”的jamvm來進行閱讀。
首先,下載編譯jamvm自行百度去,這篇文章只對jamvm的代碼做簡單理解
由於對jamvm還不是特別瞭解,我們簡單地從主函數開始進行閱讀
int main(int argc, char *argv[]) {
Class *array_class, *main_class;
Object *system_loader, *array;
MethodBlock *mb;
InitArgs args;
int class_arg;
char *cpntr;
int status;
int i;
setDefaultInitArgs(&args);
// 解析命令參數
class_arg = parseCommandLine(argc, argv, &args);
// 設置類棧
args.main_stack_base = &array_class;
if (!initVM(&args)) {
/**
* TODO: Read
* 啓動虛擬機, 失敗打印
*/
printf("Could not initialise VM. Aborting.\n");
exit(1);
}
if ((system_loader = getSystemClassLoader()) == NULL)
// TODO 設置主classLoader
goto error;
// TODO 給主線程設置classLoader
mainThreadSetContextClassLoader(system_loader);
for (cpntr = argv[class_arg]; *cpntr; cpntr++)
/**
* 把class的參數, 如‘jamVM HelloWorld’的‘HelloWorld’賦值給cpntr,對於路徑修改爲斜槓
*/
if (*cpntr == '.')
*cpntr = '/';
// TODO 查詢主函數
main_class = findClassFromClassLoader(argv[class_arg], system_loader);
if (main_class != NULL)
// TODO 初始化一個主函數
initClass(main_class);
if (exceptionOccurred())
// TODO 大概是處理異常
goto error;
// TODO 下面一整段
mb = lookupMethod(main_class, SYMBOL(main),
SYMBOL(_array_java_lang_String__V));
if (mb == NULL || !(mb->access_flags & ACC_STATIC)) {
signalException(java_lang_NoSuchMethodError, "main");
goto error;
}
/* Create the String array holding the command line args */
i = class_arg + 1;
if ((array_class = findArrayClass(SYMBOL(array_java_lang_String))) &&
(array = allocArray(array_class, argc - i, sizeof(Object *)))) {
Object **args = ARRAY_DATA(array, Object*) - i;
for (; i < argc; i++)
if (!(args[i] = Cstr2String(argv[i])))
break;
/* Call the main method */
if (i == argc)
executeStaticMethod(main_class, mb, array);
}
error:
/* ExceptionOccurred returns the exception or NULL, which is OK
for normal conditionals, but not here... */
if ((status = exceptionOccurred() ? 1 : 0))
uncaughtException();
/* Wait for all but daemon threads to die */
mainThreadWaitToExitVM();
// TODO 關閉虛擬機
exitVM(status);
/* Keep the compiler happy */
return 0;
}
上面簡單地進行了註釋,我們根據這個流程做分析,首先這一篇分析解析參數。
// 解析命令參數
class_arg = parseCommandLine(argc, argv, &args);
這裏傳入的參數有三個,前兩個是main函數的參數,代表了命令行的參數個數和參數數組,第三個是運行的初始化參數
typedef struct InitArgs {
int asyncgc;
int verbosegc;
int verbosedll;
int verboseclass;
int compact_specified; /* Whether compaction has been given on the
command line, and the value if it has */
int do_compact;
int trace_jni_sigs;
char *classpath; //classpath字面意思
char *bootpath;
char *bootpath_a;
char *bootpath_p;
char *bootpath_c;
char *bootpath_v;
int java_stack;
unsigned long min_heap; // 最小堆棧值
unsigned long max_heap; // 最大堆棧值
Property *commandline_props;
int props_count;
void *main_stack_base; // 運行的類棧
/* JNI invocation API hooks */
int (*vfprintf)(FILE *stream, const char *fmt, va_list ap);
void (*exit)(int status);
void (*abort)(void);
#ifdef INLINING
unsigned int codemem;
int replication_threshold;
int profile_threshold;
int branch_patching_dup;
int branch_patching;
int print_codestats;
int join_blocks;
int profiling;
#endif
#ifdef HAVE_PROFILE_STUBS
int dump_stubs_profiles;
#endif
} InitArgs;
這個是這個初始化參數的結構體,註釋瞭解析命令行參數需要用的參數的含義
然後看這個parseCommandLine函數
/**
* 對jamVM命令的參數做出解析
* @param argc 參數個數
* @param argv 參數數組
* @param args 初始化參數???
* @return
*/
int parseCommandLine(int argc, char *argv[], InitArgs *args) {
Property props[argc - 1];
int is_jar = FALSE;
int status = 0;
int i;
args->commandline_props = &props[0];
for (i = 1; i < argc; i++) {
/**
* 遍歷
*/
if (*argv[i] != '-') {
/**
* 如果不是-開頭,即不是操作位
*/
if (args->min_heap > args->max_heap) {
// 如果參數中最小堆棧值大於最大堆棧值就報錯
printf("Minimum heap size greater than max!\n");
status = 1;
goto exit;
}
if (args->props_count) {
args->commandline_props = sysMalloc(args->props_count *
sizeof(Property));
memcpy(args->commandline_props, &props[0], args->props_count *
sizeof(Property));
}
if (is_jar) {
/**
* 如果是jar就設置classpath
*/
args->classpath = argv[i];
argv[i] = "jamvm/java/lang/JarLauncher";
}
// 返回運行class的參數所在位置
return i;
}
先看第一部分,對參數進行遍歷,這裏進行了判斷,根據參數是否以'-'開頭判斷參數是表示操作還是表示class類。
這一部分參數是不以'-'開頭的,即參數是class或者jar之類的可執行的java文件,然後如註釋所說,先判斷一下下文解析結果中的一些參數是否合法,如最小棧值大於最大棧值,然後對於jar文件進行解析,將classpath賦值。最後返回改參數的下標以供下文使用。
第二部分自然是對於實際操作的參數,我們可以執行java命令(如果編譯過jamVM的可以用jamvm命令),來查看下java的參數種類
shabi@shabi-C-H110M-K-PRO:~$ java
用法: java [-options] class [args...]
(執行類)
或 java [-options] -jar jarfile [args...]
(執行 jar 文件)
其中選項包括:
-d32 使用 32 位數據模型 (如果可用)
-d64 使用 64 位數據模型 (如果可用)
-server 選擇 "server" VM
-zero 選擇 "zero" VM
-dcevm 選擇 "dcevm" VM
默認 VM 是 server,
因爲您是在服務器類計算機上運行。
-cp <目錄和 zip/jar 文件的類搜索路徑>
-classpath <目錄和 zip/jar 文件的類搜索路徑>
用 : 分隔的目錄, JAR 檔案
和 ZIP 檔案列表, 用於搜索類文件。
-D<名稱>=<值>
設置系統屬性
-verbose:[class|gc|jni]
啓用詳細輸出
-version 輸出產品版本並退出
-version:<值>
警告: 此功能已過時, 將在
未來發行版中刪除。
需要指定的版本才能運行
-showversion 輸出產品版本並繼續
-jre-restrict-search | -no-jre-restrict-search
警告: 此功能已過時, 將在
未來發行版中刪除。
在版本搜索中包括/排除用戶專用 JRE
-? -help 輸出此幫助消息
-X 輸出非標準選項的幫助
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
按指定的粒度啓用斷言
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
禁用具有指定粒度的斷言
-esa | -enablesystemassertions
啓用系統斷言
-dsa | -disablesystemassertions
禁用系統斷言
-agentlib:<libname>[=<選項>]
加載本機代理庫 <libname>, 例如 -agentlib:hprof
另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<選項>]
按完整路徑名加載本機代理庫
-javaagent:<jarpath>[=<選項>]
加載 Java 編程語言代理, 請參閱 java.lang.instrument
-splash:<imagepath>
使用指定的圖像顯示啓動屏幕
有關詳細信息, 請參閱 http://www.oracle.com/technetwork/java/javase/documentation/index.html。
shabi@shabi-C-H110M-K-PRO:~$ java -X
-Xmixed 混合模式執行 (默認)
-Xint 僅解釋模式執行
-Xbootclasspath:<用 : 分隔的目錄和 zip/jar 文件>
設置搜索路徑以引導類和資源
-Xbootclasspath/a:<用 : 分隔的目錄和 zip/jar 文件>
附加在引導類路徑末尾
-Xbootclasspath/p:<用 : 分隔的目錄和 zip/jar 文件>
置於引導類路徑之前
-Xdiag 顯示附加診斷消息
-Xnoclassgc 禁用類垃圾收集
-Xincgc 啓用增量垃圾收集
-Xloggc:<file> 將 GC 狀態記錄在文件中 (帶時間戳)
-Xbatch 禁用後臺編譯
-Xms<size> 設置初始 Java 堆大小
-Xmx<size> 設置最大 Java 堆大小
-Xss<size> 設置 Java 線程堆棧大小
-Xprof 輸出 cpu 配置文件數據
-Xfuture 啓用最嚴格的檢查, 預期將來的默認值
-Xrs 減少 Java/VM 對操作系統信號的使用 (請參閱文檔)
-Xcheck:jni 對 JNI 函數執行其他檢查
-Xshare:off 不嘗試使用共享類數據
-Xshare:auto 在可能的情況下使用共享類數據 (默認)
-Xshare:on 要求使用共享類數據, 否則將失敗。
-XshowSettings 顯示所有設置並繼續
-XshowSettings:all
顯示所有設置並繼續
-XshowSettings:vm 顯示所有與 vm 相關的設置並繼續
-XshowSettings:properties
顯示所有屬性設置並繼續
-XshowSettings:locale
顯示所有與區域設置相關的設置並繼續
-X 選項是非標準選項, 如有更改, 恕不另行通知。
然後對照着這些參數去看源代碼,先把源代碼全文貼一下:
switch (parseCommonOpts(argv[i], args, FALSE)) {
/**
* 如果是操作位,針對不同的操作做出處理
*/
case OPT_OK:
break;
case OPT_ERROR:
status = 1;
goto exit;
case OPT_UNREC:
default:
if (strcmp(argv[i], "-?") == 0 ||
strcmp(argv[i], "-help") == 0) {
goto usage;
} else if (strcmp(argv[i], "-X") == 0) {
showNonStandardOptions();
goto exit;
} else if (strcmp(argv[i], "-version") == 0) {
showVersionAndCopyright();
goto exit;
} else if (strcmp(argv[i], "-showversion") == 0) {
showVersionAndCopyright();
} else if (strcmp(argv[i], "-fullversion") == 0) {
showFullVersion();
goto exit;
} else if (strncmp(argv[i], "-verbose", 8) == 0) {
char *type = &argv[i][8];
if (*type == '\0' || strcmp(type, ":class") == 0)
args->verboseclass = TRUE;
else if (strcmp(type, ":gc") == 0 || strcmp(type, "gc") == 0)
args->verbosegc = TRUE;
else if (strcmp(type, ":jni") == 0)
args->verbosedll = TRUE;
} else if (strcmp(argv[i], "-jar") == 0) {
is_jar = TRUE;
} else if (strcmp(argv[i], "-classpath") == 0 ||
strcmp(argv[i], "-cp") == 0) {
if (i == argc - 1) {
printf("%s : missing path list\n", argv[i]);
goto exit;
}
args->classpath = argv[++i];
} else if (strncmp(argv[i], "-Xbootclasspath/c:", 18) == 0) {
args->bootpath_c = argv[i] + 18;
} else if (strncmp(argv[i], "-Xbootclasspath/v:", 18) == 0) {
args->bootpath_v = argv[i] + 18;
/* Compatibility options */
} else if (strcmp(argv[i], "-client") == 0 ||
strcmp(argv[i], "-server") == 0) {
/* Ignore */
} else {
printf("Unrecognised command line option: %s\n", argv[i]);
status = 1;
goto usage;
}
}
}
usage:
showUsage(argv[0]);
exit:
exit(status);
}
這是parseCommandLine函數的後半段,很顯然一開始執行了parseCommonOpts方法,並對返回的結果進行判斷,如果成功就break這個switch,如果失敗就跳轉到exit函數,這個exit函數顧名思義就是退出,一會再看,然後對於其他一些特殊的如-version這樣的參數就輸出指定的結果,然後執行showUsage函數,showUsage是輸出一個模板,即:
void showUsage(char *name) {
printf("Usage: %s [-options] class [arg1 arg2 ...]\n", name);
printf(" (to run a class file)\n");
printf(" or %s [-options] -jar jarfile [arg1 arg2 ...]\n", name);
printf(" (to run a standalone jar file)\n");
printf("\nwhere options include:\n");
printf(" -client\t compatibility (ignored)\n");
printf(" -server\t compatibility (ignored)\n\n");
printf(" -cp\t\t <jar/zip files and directories separated by :>\n");
printf(" -classpath\t <jar/zip files and directories separated by :>\n");
printf("\t\t locations where to find application classes\n");
printf(" -D<name>=<value> set a system property\n");
printf(" -verbose[:class|gc|jni]\n");
printf("\t\t :class print out information about class loading, etc.\n");
printf("\t\t :gc print out results of garbage collection\n");
printf("\t\t :jni print out native method dynamic resolution\n");
printf(" -version\t print out version number and copyright information\n");
printf(" -showversion show version number and copyright and continue\n");
printf(" -fullversion show jpackage-compatible version number and exit\n");
printf(" -? -help\t print out this message\n");
printf(" -X\t\t show help on non-standard options\n");
}
這段就不解釋了,我們來看看核心函數parseCommandLine:
/**
* 解析操作位
* @param string 操作位 如“-version”
* @param args 初始化參數
* @param is_jni 是否是jni的調用
* @return
*/
int parseCommonOpts(char *string, InitArgs *args, int is_jni) {
/**
* 初始返回值
*/
int status = OPT_OK;
if(strcmp(string, "-Xasyncgc") == 0)
args->asyncgc = TRUE;
else if(strncmp(string, "-Xms", 4) == 0 ||
(!is_jni && strncmp(string, "-ms", 3) == 0)) {
/**
* 將指針指向參數後的數字
*/
char *value = string + (string[1] == 'm' ? 3 : 4);
// 將初始化堆棧的大小賦值
args->min_heap = parseMemValue(value);
if(args->min_heap < MIN_HEAP) {
// 如果數據不合法就返回錯誤
optError(args, "Invalid minimum heap size: %s (min %dK)\n",
string, MIN_HEAP/KB);
status = OPT_ERROR;
}
} else if(strncmp(string, "-Xmx", 4) == 0 ||
(!is_jni && strncmp(string, "-mx", 3) == 0)) {
char *value = string + (string[1] == 'm' ? 3 : 4);
args->max_heap = parseMemValue(value);
if(args->max_heap < MIN_HEAP) {
optError(args, "Invalid maximum heap size: %s (min is %dK)\n",
string, MIN_HEAP/KB);
status = OPT_ERROR;
}
} else if(strncmp(string, "-Xss", 4) == 0 ||
(!is_jni && strncmp(string, "-ss", 3) == 0)) {
char *value = string + (string[1] == 'm' ? 3 : 4);
args->java_stack = parseMemValue(value);
if(args->java_stack < MIN_STACK) {
optError(args, "Invalid Java stack size: %s (min is %dK)\n",
string, MIN_STACK/KB);
status = OPT_ERROR;
}
} else if(strncmp(string, "-D", 2) == 0) {
char *key = strcpy(sysMalloc(strlen(string + 2) + 1), string + 2);
char *pntr;
for(pntr = key; *pntr && (*pntr != '='); pntr++);
if(*pntr)
*pntr++ = '\0';
args->commandline_props[args->props_count].key = key;
args->commandline_props[args->props_count++].value = pntr;
} else if(strncmp(string, "-Xbootclasspath:", 16) == 0) {
args->bootpath = string + 16;
args->bootpath_p = args->bootpath_a = NULL;
} else if(strncmp(string, "-Xbootclasspath/a:", 18) == 0) {
args->bootpath_a = string + 18;
} else if(strncmp(string, "-Xbootclasspath/p:", 18) == 0) {
args->bootpath_p = string + 18;
} else if(strcmp(string, "-Xnocompact") == 0) {
args->compact_specified = TRUE;
args->do_compact = FALSE;
} else if(strcmp(string, "-Xcompactalways") == 0) {
args->compact_specified = args->do_compact = TRUE;
} else if(strcmp(string, "-Xtracejnisigs") == 0) {
args->trace_jni_sigs = TRUE;
#ifdef INLINING
} else if(strcmp(string, "-Xnoinlining") == 0) {
/* Turning inlining off is equivalent to setting
code memory to zero */
args->codemem = 0;
} else if(strcmp(string, "-Xnoprofiling") == 0) {
args->profiling = FALSE;
} else if(strcmp(string, "-Xnopatching") == 0) {
args->branch_patching = FALSE;
} else if(strcmp(string, "-Xnopatchingdup") == 0) {
args->branch_patching_dup = FALSE;
} else if(strcmp(string, "-Xnojoinblocks") == 0) {
args->join_blocks = FALSE;
} else if(strcmp(string, "-Xcodestats") == 0) {
args->print_codestats = TRUE;
} else if(strncmp(string, "-Xprofiling:", 12) == 0) {
args->profile_threshold = strtol(string + 12, NULL, 0);
} else if(strncmp(string, "-Xreplication:", 14) == 0) {
char *pntr = string + 14;
if(strcmp(pntr, "none") == 0)
args->replication_threshold = INT_MAX;
else
if(strcmp(pntr, "always") == 0)
args->replication_threshold = 0;
else
args->replication_threshold = strtol(pntr, NULL, 0);
} else if(strncmp(string, "-Xcodemem:", 10) == 0) {
char *pntr = string + 10;
args->codemem = strncmp(pntr, "unlimited", 10) == 0 ?
INT_MAX : parseMemValue(pntr);
} else if(strcmp(string, "-Xshowreloc") == 0) {
showRelocatability();
#endif
#ifdef HAVE_PROFILE_STUBS
} else if(strcmp(string, "-Xdumpstubsprofiles") == 0) {
args->dump_stubs_profiles = TRUE;
#endif
/* Compatibility options */
} else {
int i;
for(i = 0; compat[i].option != NULL; i++) {
int len = strlen(compat[i].option);
if(strncmp(string, compat[i].option, len) == 0 &&
(((compat[i].flags & OPT_ARG) && string[len] == ':') ||
((compat[i].flags & OPT_NOARG) && string[len] == '\0')))
break;
}
if(compat[i].option == NULL)
status = OPT_UNREC;
}
return status;
}
代碼相當長,但其實只是對於不同的命令做分別的處理,我們簡單看一種命令,看一下上面代碼註釋了的那一段:
else if(strncmp(string, "-Xms", 4) == 0 ||
(!is_jni && strncmp(string, "-ms", 3) == 0)) {
/**
* 將指針指向參數後的數字
*/
char *value = string + (string[1] == 'm' ? 3 : 4);
// 將初始化堆棧的大小賦值
args->min_heap = parseMemValue(value);
if(args->min_heap < MIN_HEAP) {
// 如果數據不合法就返回錯誤
optError(args, "Invalid minimum heap size: %s (min %dK)\n",
string, MIN_HEAP/KB);
status = OPT_ERROR;
}
查看命令輸出我們可以看到-Xms是設置初始化堆棧大小的,首先將指針移到命令後面 即 ‘-Xms 100M’移到 '100M',然後調parseMemValue函數,將返回結果賦值給args,並且判斷是否合法
/**
* 解析參數後的那個數
* @param str
* @return
*/
unsigned long parseMemValue(char *str) {
char *end;
/**
* 將參數轉化爲number,其中end儲存第一個不能被轉換的字符指針,第三個參數爲0表示10進制
*/
unsigned long n = strtol(str, &end, 0);
switch(end[0]) {
case '\0':
break;
case 'G': case 'g':
n *= KB;
case 'M': case 'm':
n *= KB;
case 'K': case 'k':
n *= KB;
if(end[1] == '\0')
break;
default:
n = 0;
}
return n;
}
這個函數很好理解,註釋都有就不解釋了
這就是jamVM解析命令行參數的全部流程了,這裏只是將命令行的參數解析賦值到了args變量中,而args變量相當於執行過程的全局變量,而真正的執行過程在後面的文章中解析。