背景
大家知道,jdk安裝的目錄下,一般會有個src.zip包,這個包基本對應了rt.jar這個包。rt.jar這個包裡面,就放了jdk中,jdk采用java實作的那部分類庫代碼,比如java.lang包下面的,什麼ArrayList之類的。
如何才能調試這部分代碼呢,這裡的調試,是說,能夠修改源代碼、加注釋、直接debug。
步驟
經過一番思考和探索後,可以這樣:
- 解壓src.zip包,因為解壓後,裡面有8000多個檔案,比較大,我們也不需要調試所有的代碼,我就挑了這個包下面的代碼: 上面看到,類比較多,我們不需要那麼多,隻用下面這部分:
-
建立一個普通的maven工程,然後把上面的java包下面的,拷貝到自己的工程的src目錄下
因為awt、applet之類的,現在都沒人用了,我也就沒拷貝那部分。
pom.xml真的沒東西,不過還是貼一下:
最後,工程大概就是這樣的。 然後,自己在test檔案夾下,我建了一個HelloWorld:<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>
理論上來說,就可以調試了嗎?naive!import java.util.ArrayList; public class HelloWorld { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("abc"); } }
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)
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時候,要去查找的路徑。大家也可以參考這兩篇文章:
https://www.cnblogs.com/ahudyan-forever/p/6007458.html
https://blog.csdn.net/briblue/article/details/54973413
是以,我的最終方案就是,把我們的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去做修改。
總結
希望大家調試愉快。謝謝大家。有幫助的話,點個推薦。