JVM源碼分析-JVM源碼編譯與調試

来源:https://www.cnblogs.com/mushan/archive/2020/02/05/12266446.html
-Advertisement-
Play Games

要分析JVM的源碼,結合資料直接閱讀是一種方式,但是遇到一些想不通的場景,必須要結合調試,查看執行路徑以及參數具體的值,才能搞得明白。所以我們先來把JVM的源碼進行編譯,並能夠使用GDB進行調試。 編譯環境 本文使用的JDK版本:OpenJDK7,分支b147 下載頁面:https://downlo ...


要分析JVM的源碼,結合資料直接閱讀是一種方式,但是遇到一些想不通的場景,必須要結合調試,查看執行路徑以及參數具體的值,才能搞得明白。所以我們先來把JVM的源碼進行編譯,並能夠使用GDB進行調試。

編譯環境

本文使用的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:c284c89a104f64a95afde3a96138ef0f

其他環境說明:

  • CentOS 7.4 64位
  • gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
  • GNU Make 3.82

安裝依賴

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.bin

$ sh jdk-6u38-linux-x64-rpm.bin
$ sudo rpm -ivh jdk-6u38-linux-amd64.rpm

編寫編譯腳本

解壓openjdk:

unzip openjdk-7-fcs-src-b147-27_jun_2011.zip

在openjdk目錄下添加一個build.sh腳本:

#!/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 進行編譯。如果編譯過程中遇到問題,可以查閱下文的編譯問題解決的部分。

運行HotSpot

編譯成功後,編譯的輸出預設在openjdk/build目錄下。HotSpot的編譯輸出在openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg目錄下。

使用HotSpot提供的命令執行測試:

cd build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg

# test_gamma是HotSpot提供的一個測試程式,可以成功執行說明編譯成功
./test_gamma

# 使用hotspot腳本進行GDB調試
./hotspot -gdb HelloWorld

./hotspot是一個腳本,查閱代碼可以看出他做了一些簡單的事情,主要是會設置環境變數:

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

Hotspot代碼調試技巧

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行的參數

資料

  • 《深入理解Java虛擬機》
  • openjdk7之編譯和debug | YDDMAX https://yddmax.github.io/2017/06/11/openjdk7%E4%B9%8B%E7%BC%96%E8%AF%91%E5%92%8Cdebug/
  • centos7編譯openjdk7常見問題 - clover灬 - OSCHINA https://my.oschina.net/zhangdq/blog/2250314
  • How to build and package OpenJDK 7 on Linux · hgomez/obuildfactory Wiki https://github.com/hgomez/obuildfactory/wiki/How-to-build-and-package-OpenJDK-7-on-Linux
  • ubuntu16.04編譯JDK7 - 簡書 https://www.jianshu.com/p/32dc1a850e23
  • Building OpenJDK7 with CentOS7 – Rtfsc8 http://blog.rtfsc8.top/2018/07/07/building-openjdk7-with-centos7/
  • CentOS上編譯OpenJDK8源碼 以及 在eclipse上調試HotSpot虛擬機源碼 - tjiyu的博客 - CSDN博客

本文獨立博客地址:JVM源碼分析-JVM源碼編譯與調試 | 木杉的博客


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 運用在React 中 setState的對象、數組的操作時是不能用類似array.push()等方法,因為push沒有返回值,setState後會出現state變成Number,為了方便他人和自己查看,因此寫個數組和對象的操作方法小筆記。 1、修改object中某項 this.setState({ ...
  • 1.最後生成的效果是這樣的: 2.第一個對話框如下(包含了樣圖),用於輸入維度個數【最小為4,最大不限】: 3.第二個對話框如下,根據維度個數生成了信息錄入表【每個維度3個信息,每3個一次重覆,不清楚意義清查看第一步】 4.第三個對話框如下,該錄入名稱最終展示在圖形中央上部,以及命名為圖片的文件名 ...
  • 1. 切勿混用帶符號類型和無符號類型 如果表達式里既有帶符號類型又有無符號類型,帶符號類型會自動轉換為無符號類型。 2. 列表初始化 列表初始化當用於內置類型時,如果存在精度丟失編譯器將報錯。 3. 變數聲明和定義的關係 4. C++操作符代替名 5. const限定符 6. 類型別名 7. dec ...
  • 親愛的兒子: 當你打開這封信的時候,我已經離開波士頓回到加州了,你也已經結束自己最後一個暑假,去往自己非常喜歡的公司。 雖然你我都是程式員,但是你卻很少向我咨詢過技術相關的問題,咱們上一次一起寫代碼,也是你高考結束那個暑假了。不過前幾天你問了我一個問題,你說,如果讓你寫一封信,跟當初剛剛成為程式員的 ...
  • That syntax is called an indexed part-select. The first term is the bit offset and the second term is the width. It allows you to specify a variable f ...
  • 官方文檔 猛戳這裡 在settings中配置以下代碼 #LOGGING_DIR 日誌文件存放目錄 LOGGING_DIR = "logs" # 日誌存放路徑 if not os.path.exists(LOGGING_DIR): os.mkdir(LOGGING_DIR) import loggin ...
  • 伴隨著移動互聯網的飛速發展,越來越多用戶被互聯網連接在一起,用戶所積累下來的數據越來越多,市場對數據方面人才的需求也越來越大,由此也帶火瞭如數據分析、數據挖掘、演算法等職業,而作為其中入門門檻相對較低、工資高於大多傳統行業崗位的數據分析一職,則成為了許多想轉行進入數據領域的同學的首要選擇。 那麼在現在 ...
  • A類調用B類的靜態方法,除了載入B類,但是B類的一個未被調用的方法間接使用到的C類卻也被載入了,這個有意思的場景來自一個提問: "方法中使用的類型為何在未調用時嘗試載入?" 。 場景如下: 添加JVM varbose參數進行執行,輸出是: main方法執行 ,而 方法裡面只有列印語句,所以理論上應該 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...