曹工力薦:調試 jdk 中 rt.jar 包部分的源碼(可自由增長註釋,修改代碼並debug)

背景

你們知道,jdk安裝的目錄下,通常會有個src.zip包,這個包基本對應了rt.jar這個包。rt.jar這個包裏面,就放了jdk中,jdk採用java實現的那部分類庫代碼,好比java.lang包下面的,什麼ArrayList之類的。html

如何才能調試這部分代碼呢,這裏的調試,是說,可以修改源代碼、加註釋、直接debug。java

步驟

通過一番思考和探索後,能夠這樣:git

  1. 解壓src.zip包,由於解壓後,裏面有8000多個文件,比較大,咱們也不須要調試全部的代碼,我就挑了這個包下面的代碼:shell

    上面看到,類比較多,咱們不須要那麼多,只用下面這部分:apache

  2. 新建一個普通的maven工程,而後把上面的java包下面的,拷貝到本身的工程的src目錄下bootstrap

    由於awt、applet之類的,如今都沒人用了,我也就沒拷貝那部分。數組

    pom.xml真的沒東西,不過仍是貼一下:app

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <groupId>org.learnjdk</groupId>
    
        <modelVersion>4.0.0</modelVersion>
        <packaging>jar</packaging>
        <version>4.7.0</version>
    
        <artifactId>jdk-debug</artifactId>
        <name>jdk-debug</name>
    
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
        </properties>
    
        <dependencies>
    
        </dependencies>
    </project>

    最後,工程大概就是這樣的。jvm

    而後,本身在test文件夾下,我建了一個HelloWorld:maven

    import java.util.ArrayList;
    
    public class HelloWorld {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("abc");
        }
    }

    理論上來講,就能夠調試了嗎?naive!

    F:\gitee-ckl\rocketmq-all-4.7.0-source>java -version
    java version "1.8.0_11"
    Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)

    我這邊是jdk的版本,會有個報錯,是源碼不徹底匹配class致使的,我這邊會報找不到java.lang.Iterable#iterator方法,你們直接反編譯一下jdk的這個class,就能看到缺了啥了,我這邊加上這個方法就行了:

    public interface Iterable<T> {
        /**
         * Returns an iterator over elements of type {@code T}.
         *
         * @return an Iterator.
         */
        public abstract Iterator<T> iterator();
        
    	...
    }

測試demo有什麼問題

你們運行就知道了,根本走不到咱們工程裏定義的class

其實總體來講,那個maven工程是沒問題的。走不到那個class,是由於,classloader的問題。

在運行上面的helloWorld時,當前classloader是sun.misc.Launcher.AppClassLoader,它的父類是

sun.misc.Launcher.ExtClassLoader,而sun.misc.Launcher.ExtClassLoader的父類,就是BootStrap類加載器了。

由於AppClassLoader是遵循雙親委派的,因此,在運行下面這個代碼的時候:

import java.util.ArrayList;

public class HelloWorld {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");
    }
}

看到ArrayList,AppClassLoader會交給ExtClassLoader去加載,ExtClassLoader會交給BootStrapClassloader去加載,BootStrapClassloader自己負責加載jdk下的rt.jar等核心jar包,而Arraylist正好就是在jdk下面的rt.jar中,因此,最終,Arraylist是由BootStrapClassloader加載的。

那就和咱們的工程裏的代碼不要緊了,根本不加載你的。

怎麼讓BootStrap優先加載咱們的類

核心其實就是變成了,讓BootStrapClassLoader優先加載咱們的類,在個人知識理解裏,BootStrapClassLoader默認就是加載rt.jar的東西,怎麼才能加載咱們的呢?只能求助互聯網了。

而後我查到了這篇文章,-Xbootclasspath

裏面說,用這個參數能夠改變BootStrapClassLoader的加載路徑,因而我試了一下:

-Xbootclasspath/p:"F:\gitee-ckl\jdk-debug\target\classes"

idea裏,就加在這裏面:

而後,你們直接run的話,能夠發現,已經沒問題了。

由於我改了工程裏的源碼的:

public boolean add(E e) {
        System.out.println("xxxxx");
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

因此我這邊運行的時候,會打印xxxxx:

可是,若是你debug,行號應該是對不上的,因此,咱們還要這麼操做一波:

idea裏,在左側的項目樹立,對着module按F4,或者右鍵-》Open Module Settings,會打開以下窗口:

而後debug,就能夠了:

而後,這個方案,原本昨晚嘗試的時候,是有問題的,不知道今天爲啥就能夠了,你們也能夠試試。

另外一種可行的方案

由於昨晚嘗試上面方案的時候,不知道爲啥,沒生效;因而找出了下面的方法。

在helloWorld.java裏,修改以下:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.print(System.getProperty("sun.boot.class.path"));
        
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");
    }
}

咱們打印了sun.boot.class.path的值,我這邊打印出來後以下:

C:\Program Files\Java\jdk1.8.0_11\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_11\jre\classes

這個參數啥意思,差很少就是BootStrap加載class時候,要去查找的路徑。你們也能夠參考這兩篇文章:

http://www.javashuo.com/article/p-muqbkbwg-dc.html

http://www.javashuo.com/article/p-vliilzaq-b.html

因此,個人最終方案就是,把咱們的class路徑,放到最前面,你們根據本身的路徑進行修改就行。

你們要注意的是,這裏的路徑,要仔細,粘錯一個字符都不行:

-Dsun.boot.class.path="F:\gitee-ckl\jdk-debug\target\classes;C:\Program Files\Java\jdk1.8.0_11\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_11\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_11\jre\classes"

最好直接用下面這個代碼來拼好路徑,省得人工出錯:

public class HelloWorld {
    public static void main(String[] args) {
        String property = System.getProperty("sun.boot.class.path");
        System.setProperty("sun.boot.class.path","F:\\gitee-ckl\\jdk-debug\\target\\classes;" + property);
        System.out.println(System.getProperty("sun.boot.class.path"));
		
    }
}

運行方式和前面同樣:

該方案爲何可行

稍微拓展一點,由於我也就知道這麼一點,在sun.misc.Launcher類中:

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    // 1
    private static String bootClassPath = System.getProperty("sun.boot.class.path");

1處,這裏定義了一個field,就是去獲取咱們前面用到的那個屬性。

這個類裏,有另外一個方法來解析這個field。

sun.misc.Launcher.BootClassPathHolder
private static class BootClassPathHolder
  {
    // 0
    static final URLClassPath bcp = new URLClassPath(arrayOfURL, Launcher.factory);

    static
    {
      URL[] arrayOfURL;
      if (Launcher.bootClassPath != null) {
        arrayOfURL = (URL[])AccessController.doPrivileged(new PrivilegedAction()
        {
          public URL[] run() {
            // 1
            File[] arrayOfFile = Launcher.getClassPath(Launcher.bootClassPath);
            int i = arrayOfFile.length;
            HashSet localHashSet = new HashSet();
            for (int j = 0; j < i; j++) {
              // 2
              File localFile = arrayOfFile[j];

              if (!localFile.isDirectory()) {
                localFile = localFile.getParentFile();
              }
              if ((localFile != null) && (localHashSet.add(localFile))) {
                MetaIndex.registerDirectory(localFile);
              }
            }
            // 3
            return Launcher.pathToURLs(arrayOfFile);
          }
        });
      }
      else
        arrayOfURL = new URL[0];
    }
  }
  • 1處,把那個屬性,用分隔符分開,解析爲一個文件數組
  • 2處,遍歷數組
  • 3處,解析爲URL,賦值給arrayOfURL。由於這裏是一個匿名內部類,因此第三步的return,只是return了匿名內部類中的方法
  • 0處,使用arrayOfURL,定義了一個static變量

而後在另外一個方法中,會去獲取那個bcp:

sun.misc.Launcher#getBootstrapClassPath    
public static URLClassPath getBootstrapClassPath() {
        return Launcher.BootClassPathHolder.bcp;
}

上面這個方法在哪被調用?

java.lang.ClassLoader#getBootstrapClassPath
// Returns the URLClassPath that is used for finding system resources.
static URLClassPath getBootstrapClassPath() {
    return sun.misc.Launcher.getBootstrapClassPath();
}

被同屬於ClassLoader類的下列方法調用:

java.lang.ClassLoader#getBootstrapResource    
	/**
     * Find resources from the VM's built-in classloader.
     */
    private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

再往上找,就是:

java.lang.ClassLoader#getResource    
public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

其餘就很少分析,你們也比較熟悉了。

兩種方案的差別

第一種方案,加了-Xbootclasspath,這個是在jvm層面去作修改,由於這個-X是虛擬機參數;

第二種方案,上面你們也看到了,是在rt.jar中,java層面的classloader去作修改。

總結

但願你們調試愉快。謝謝你們。有幫助的話,點個推薦。

相關文章
相關標籤/搜索