1. 程式人生 > >原始碼編譯OpenJdk 8,Netbeans除錯Java原子類在JVM中的實現(Ubuntu 16.04)

原始碼編譯OpenJdk 8,Netbeans除錯Java原子類在JVM中的實現(Ubuntu 16.04)

一、前言

前一陣子比較好奇,想看到底層(虛擬機器、彙編)怎麼實現的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字首下次講。主要是鎖匯流排,或者鎖快取,達到原子操作的目的。

 

有問題歡迎留言,如果有問題,也可以加我微信交流。

&n