要分析JVM的源碼,結合資料直接閱讀是一種方式,可是遇到一些想不通的場景,必需要結合調試,查看執行路徑以及參數具體的值,才能搞得明白。因此咱們先來把JVM的源碼進行編譯,並可以使用GDB進行調試。html
本文使用的JDK版本:OpenJDK7,分支b147
下載頁面:https://download.java.net/openjdk/jdk7
下載地址:http://download.java.net/openjdk/jdk7/promoted/b147/openjdk-7-fcs-src-b147-27_jun_2011.zip
MD5:c284c89a104f64a95afde3a96138ef0fjava
其餘環境說明:node
yum -y install gcc gcc-c++ make yum -y install alsa-lib-devel yum -y install cups-devel yum -y install libX* yum -y install gcc gcc-c++ yum -y install libstdc++-static wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo yum -y install ant
編譯JVM,須要使用到更早以前一個版本的JDK,好比咱們編譯的是7,就須要安裝OracleJDK6:
下載地址:https://www.oracle.com/java/technologies/javase-java-archive-javase6-downloads.html
http://gcdncs.101.com/v0.1/static/test_mzb/jdk-6u38-linux-x64-rpm.bin
下載:jdk-6u38-linux-x64-rpm.binlinux
$ sh jdk-6u38-linux-x64-rpm.bin $ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
解壓openjdk:c++
unzip openjdk-7-fcs-src-b147-27_jun_2011.zip
在openjdk目錄下添加一個build.sh腳本:git
#!/bin/bash export LANG=C #將一下兩項設置爲你的BootstrapJDK安裝目錄 export ALT_BOOTDIR=/usr/java/jdk1.6.0_38 export ALT_JDK_IMPORT_PATH=/usr/java/jdk1.6.0_38 #容許自動下載依賴包 export ALLOW_DOWNLOADS=true #使用預編譯頭文件,以提高便以速度 export USE_PRECOMPILED_HEADER=true #要編譯的內容,我只選擇了LANGTOOLS、HOTSPOT以及JDK export BUILD_LANGTOOLS=true export BUILD_JAXP=false export BUILD_JAXWS=false export BUILD_CORBA=false export BUILD_HOSTPOT=true export BUILD_JDK=true #要編譯的版本 export SKIP_DEBUG_BUILD=false export SKIP_FASTDEBUG_BUILD=true export DEBUG_NAME=debug #避免javaws和瀏覽器Java插件等的build BUILD_DEPLOY=false #不build安裝包 BUILD_INSTALL=false #包含所有的調試信息 export ENABLE_FULL_DEBUG_SYMBOLS=1 #調試信息是否壓縮,若是配置爲1,libjvm.debuginfo會被壓縮成libjvm.diz,將不能被debug。 export ZIP_DEBUGINFO_FILES=0 #用於編譯線程數 export HOTSPOT_BUILD_JOBS=3 #設置存放編譯結果的目錄 #export ALT_OUTPUTDIR=/root/jvm/output unset CLASSPATH unset JAVA_HOME make sanity DEBUG_BINARIES=true make 2>&1
而後執行 sh build.sh 進行編譯。若是編譯過程當中遇到問題,能夠查閱下文的編譯問題解決的部分。github
編譯成功後,編譯的輸出默認在openjdk/build
目錄下。HotSpot的編譯輸出在openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
目錄下。shell
使用HotSpot提供的命令執行測試:apache
cd build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg # test_gamma是HotSpot提供的一個測試程序,能夠成功執行說明編譯成功 ./test_gamma # 使用hotspot腳本進行GDB調試 ./hotspot -gdb HelloWorld
./hotspot
是一個腳本,查閱代碼能夠看出他作了一些簡單的事情,主要是會設置環境變量:ubuntu
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH=/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64
而後生成GDB參數腳本,並運行GDB命令:
gdb -x /tmp/hsl.26037
/tmp/hsl.26037是腳本生成的gdb參數,咱們能夠看看都設置了什麼:
cd /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg handle SIGUSR1 nostop noprint handle SIGUSR2 nostop noprint set args HelloWorld file /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/gamma directory /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg # Get us to a point where we can set breakpoints in libjvm.so break InitializeJVM run # Stop in InitializeJVM delete 1 # We can now set breakpoints wherever we like
能夠看出設置了源碼目錄,設置了一個默認斷點。分析了hotspot腳本後,咱們能夠根據須要用最原始的方式來啓動hotspot和gdb來實現更復雜的調試需求,好比遠程調試。
直接執行的方式:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" ./gamma HelloWorld
GDB調試:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gdb ./gamma HelloWorld
GDB遠程調試:
JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gdbserver :8011 ./gamma HelloWorld
GDB的使用和技巧這裏就不說了,我本身也是遇到問題現查資料的,這裏列幾個經常使用的和HotSpot有關的調試技巧。
如何打印HotSpot內部符號對象Symbol對應的字符串?
Symbol是一個很是常見的類,全部的符號引用對應的字符串,都會用Symbol來表示,好比類名、方法名、方法簽名等等,能夠用一下方法輸出Symbol對應字符串:
p *name._body@name._length
如何打印KlassHandle對應的類名?
p Klass::cast(current_klass.obj())->external_name()
添加加載特定類時的斷點
break ClassFileParser::parseClassFile if strncmp(class_name._body, "XXX", 3) == 0 break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0
BootstrapJDK一開始設置爲JDK8會失敗,要改成JDK6
#錯誤 echo "*** This OS is not supported:" `uname -a`; exit 1; #解決 sudo vim openjdk/hotspot/make/linux/Makefile 註釋掉如下三行 238 #ifeq ($(DISABLE_HOTSPOT_OS_VERSION_CHECK)$(EMPTY_IF_NOT_SUPPORTED),) 239 # $(QUIETLY) >&2 echo "*** This OS is not supported:" `uname -a`; exit 1; 240 #endif
#錯誤 error:"__LEAF"redefined [-Werror] #解決 ubuntu12的glibc比較新,在linux的頭文件cdefs.h裏,有個__LEAF的宏, 這個和hotspot/src/share/vm/runtime/interfaceSupport.hpp 這個頭文件中的宏定義有衝突,咱們在428行下面增長一個#undef __LEAF以下: 428 // LEAF routines do not lock, GC or throw exceptions #ifdef __LEAF #undef __LEAF #define __LEAF(result_type, header) \ TRACE_CALL(result_type, header) \ debug_only(NoHandleMark __hm;) \ /* begin of body */ #endif
#錯誤 Error:/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:272:39: error: converting 'false' to pointer type 'methodOop' [-Werror=conversion-null] #解決 vi hotspot/src/share/vm/oops/constantPoolOop.cpp 將272行 return false 改成 return NULL
#錯誤 /usr/openjdk/hotspot/src/share/vm/opto/loopnode.cpp:896:49: error: converting 'false' to pointer type 'Node*' [-Werror=conversion-null] #解決 vi hotspot/src/share/vm/opto/loopnode.cpp 將896行 return false 改成 return NULL
#錯誤 Using java runtime at: /usr/lib/jvm/java-1.6.0/jre ./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so with link time reference #解決 這裏是有一個坑的,爲了不你們踩坑,請提早安裝好Oracle JDK 1.6。 # ALT_BOOTDIR 用到的是 OpenJDK 1.6.0 會有此報錯, OpenJDK 的bug,須要使用 Oracle JDK # 見到相似下方的報錯了,恭喜童鞋您入坑了 # ./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so with link time reference 下載傳送門可能須要登錄Oracle,沒有賬號的童鞋請註冊一下。筆者下載安裝的是jdk-6u38-linux-x64-rpm.bin。 若是上一個傳送門失效,請繼續傳送!找到此頁面上的Java SE 6進入傳送哦~若是這個傳送也失效了(T_T),那接着傳送,拉到頁面最下方,找到Java Archive欄,點擊右側DOWNLOAD按鈕自行傳送。再不行就只能找baidu了~~~ # 對下載到的bin動動手腳(不要想多,釋放裏面的rpm包而已) $ sh jdk-6u38-linux-x64-rpm.bin # 查看下獲得的rpm包 $ ll *.rpm # 安裝Oracle JDK $ sudo rpm -ivh jdk-6u38-linux-amd64.rpm # OK 至此已完成Oracle JDK安裝 # 查找安裝的Oracle JDK目錄 # 查找jdk安裝名稱 $ rpm -qa | grep ^jdk-1.6.0 jdk-1.6.0_38-fcs.x86_64 # 根據安裝名稱查找安裝到本地的文件列表 $ rpm -ql jdk-1.6.0_38-fcs.x86_64 ... /usr/java/jdk1.6.0_38 # Oracle JDK HOME ... # 以上查找到的目錄後面會用到
錯誤: gcc: error: unrecognized command line option '-mimpure-text' 解決: vi jdk/make/common/shared/Compiler-gcc.gmk 在70行remove the command "-mimpure-text" in the code:
錯誤: Error: time is more than 10 years from present: 1136059200000 解決: # 修改如下文件,將日期改成十年之內,JDK的Bug。 vi jdk/src/share/classes/java/util/CurrencyData.properties # line: 108 377 439 529 555
錯誤: ../../../src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java:661: error: no suitable constructor found for SslRMIServerSocketFactory(SSLContext,String[],String[],boolean) 解決: vi ./jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java 註釋掉662行的參數
本文獨立博客地址:JVM源碼分析-JVM源碼編譯與調試 | 木杉的博客