一、前言
前一陣子比較好奇,想看到底層(虛擬機、匯編)怎么實現的java 並發那塊。
volatile是在匯編里加了lock前綴,因為volatile可以通過查看JIT編譯器的匯編代碼來看。
但是原子類,本來在jvm中就是匯編實現的,反而沒法看。如果能實際跟蹤一下斷點,應該也算實際驗證了。
這邊基本參照下面文章來的,補充了很多讓初學者頭疼的細節,並拓展了一部分,
包括調試java 原子類在jvm中的實現的一些細節。
https://marcin-chwedczuk.github.io/debugging-openjdk8-with-netbeans-on-ubuntu
源碼編譯OpenJDK8,主要有以下幾個步驟:
- 下載Ubuntu
- 下載OpenJdk源碼
- 下載Boot JDK,一般要比當前要編譯的版本低
- 安裝必要的依賴
- configure && make
上面幾步搞完,基本虛擬機就可用了。但離調試,還有一點點距離。
用NetBeans調試JVM代碼,有以下幾個步驟:
- 下載NetBeans
- 配置OpenJdk工程
- 配置Java工程
- Debug OpenJdk(即虛擬機源碼)
二、源碼編譯OpenJDK8
1、下載Ubuntu
我用的16.04,鏈接地址:https://www.ubuntu.com/download/alternative-downloads
我是用vmvare裝的,配置建議給高一點。
2、下載OpenJdk源碼
據原文說法,OpenJDK 使用Mercurial進行版本管理。另外一個名叫AdoptOpenJDK project.提供了OpenJDK的鏡像,可以讓我們用git下載。
站點的官網如下:https://adoptopenjdk.net/about.html
主頁上說他們的目標就是:
Provide a reliable source of OpenJDK binaries for all platforms, for the long term future.
據我的使用體驗來說,之前編譯過一次OpenJDK,各種報錯,各種改源碼才能編譯通過。這次確實編譯很順,代碼一句沒改。
看起來,代碼還是比較可靠的。
不扯別的了,直接clone搞下來吧,我這邊是直接在/home/ckl目錄下執行的shell:
git clone --depth 1 -b master https://github.com/AdoptOpenJDK/openjdk-jdk8u.git
3、下載Boot JDK
編譯過jdk的同學應該知道,我們得先有只母雞才能編譯openJDK源碼。
這邊我用的oracle的jdk 1.7,這邊貼個csdn的下載鏈接,我那天弄的時候官網速度太慢。
https://download.csdn.net/download/qq_33499492/10288883
怎么安裝就不說了,我解壓后放在/usr/local
記得修改環境變量(/ect/profile):
export JAVA_HOME=/usr/local/jdk1.7.0_80 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH
然后,source /ect/profile 使之生效。
4、安裝依賴
sudo apt install \ libx11-dev \ libxext-dev \ libxrender-dev \ libxtst-dev \ libxt-dev \ libcups2-dev \ libfreetype6-dev \ libasound2-dev
這個依賴不夠,我這邊裝的時候,還報了一些依賴缺失,直接安裝報錯提示里的執行命令下載就完了。
我這里遇到比較坑的一點是(當然我對ubuntu完全不熟),一開始用的是官方的repository 源,后來換成阿里雲的,各種報錯。
嚇得我趕緊改回來了,就沒問題了。
這里遇到問題可以咨詢我。
5、配置腳本
在/home/ckl/openjdk-jdk8u下,新建腳本build.sh:
build.sh:
bash ./configure --with-target-bits=64 --with-boot-jdk=/usr/local/jdk1.7.0_80/ --with-debug-level=slowdebug --enable-debug-symbols ZIP_DEBUGINFO_FILES=0 make all ZIP_DEBUGINFO_FILES=0
給build.sh增加可執行權限並執行:
chmod +x build.sh ./build.sh
6、編譯成功的效果
切換到對應目錄下:
/home/ckl/openjdk-jdk8u/build/linux-x86_64-normal-server-slowdebug/jdk/bin
./java -version
在該目錄下,新建個HelloWorld來運行一下:
public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); } }
三、Netbeans調試JVM
1、下載NetBeans 8.2
下載安裝主要參考:
https://netbeans.org/community/releases/82/install.html
NetBeans主頁上,最新版本出到11.0了,但是在網上看到都是用的NetBeans 8開頭版本的,有時間再折騰吧。
我這里下載的是8.2,鏈接:
https://netbeans.org/downloads/8.2/
因為OpenJDK是c++寫的,所以我們必須選擇帶C/C++支持的,我這里直接選All。
另外,注意選linux平台,最好選英語,免得出幺蛾子。
2、下載oracle jdk 1.8
為啥又要下載jdk? 這個jdk不是編譯openJdk源碼用的那個,這個是運行NetBeans 8.2需要。
The Java SE Development Kit (JDK) 8 is required to install NetBeans IDE.
下完安裝后,把環境變量里設的jdk路徑改掉吧:
export JAVA_HOME=/usr/local/jdk1.8.0_211 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH
source /etc/profile
3、安裝net beans
安裝:
./netbeans
安裝過程,記得不要全部默認,可以自己定制化:
4、配置Open JDK工程
安裝完成后,在桌面上,會生成一個圖標,直接雙擊啟動。
后面的步驟,可以參考:
https://marcin-chwedczuk.github.io/debugging-openjdk8-with-netbeans-on-ubuntu
然后接下來幾步都可以直接next,最后點finish會開始configure。
5、運行HelloWorld
配置我們的openjdk工程去運行之前寫的HelloWorld程序。
對着openjdk-jdk8u工程,右鍵選擇properties:
選擇編譯出的jdk來運行我們的class:
應該能看到輸出Hello World了。
6、調試Hello World
System.out.println(...)會調用jdk/src/share/native/java/io/io_util.c的writeBytes
直接在該函數打個斷點,然后debug
不出意外,應該會停到該斷點。
7、在netbeans中新建java工程,並調試jvm
我們來調一個有用點的程序,看看原子類在jvm中的實現到底是不是像網上的博客那樣運行的。
先像上面這樣,建個java工程,寫點代碼,然后運行。
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * * @author ckl */ public class TestSample { private static AtomicInteger stop = new AtomicInteger(12); /** * @param args the command line arguments */ public static void main(String[] args) { Boolean result = stop.compareAndSet(1314, 1413); // TODO code application logic here if (result){ System.out.println(" true result "); }else { System.out.println("false result"); } } }
調試jvm:
對着openjdk-jdk8u工程點右鍵,properties。
建立斷點,cas調用一般會調用unsafe.cpp的以下代碼:
斷點ok了,然后點選中openjdk-jdk8u工程后,點擊debug按鈕:
果然,程序馬上就停在這了。但是,cas操作可能在很多地方都調用了,所以我們要仔細觀察Variables窗口,看看是不是我們發起的那個調用:
跳過了十多次以后。。。
。。。
稍微跟一下:
直接進到這段匯編了,用了cmpxchg指令來實現cas,還加了lock前綴(mp為1)。lock前綴下次講。主要是鎖總線,或者鎖緩存,達到原子操作的目的。
有問題歡迎留言,如果有問題,也可以加我微信交流。