1. 程式人生 > 實用技巧 >java學習-每日一題-類載入死鎖

java學習-每日一題-類載入死鎖

@Slf4j
public class ClassLoaderDeadLockDemo {
    // 定義一個常量 ,在類載入時期寫入到 constant-pool中
    public static final Object OBJECT = new Object();

    static {
        log.info("載入執行靜態程式碼塊");
        log.info("{}", System.identityHashCode(Thread.currentThread().getContextClassLoader()));
        // TODO : case 1
        
// lambda 表示式 // 當對於 class檔案使用 javap -v 可以看到其中 有一個 InvokeDynamic jvm指令,這裡是對lambda 語法的位元組碼提升 // invokedynamic #64, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; // 而由於此處未使用外部類,而是依賴於當前類,因此會產生類載入依賴 // 當 ClassLoaderDeadLockDemo 類載入和初始化執行到該處時呼叫 invokedynamic,而 invokedynamic 此時又依賴於當前ClassLoaderDeadLockDemo的載入完畢,此時就出現了依賴的迴圈,從而導致了執行緒阻塞
// 對於此處是動態生成的而不是和匿名內建類一樣會靜態生成class檔案 // 對於ClassLoaderDeadLockDemo.class而言, InvokeDynamic指令屬於ClassLoaderDeadLockDemo類載入的一部分, 而 對於 InvokeDynamic run 實際又依賴於ClassLoaderDeadLockDemo的載入完成 // 因此此處就形成了類載入的依賴迴圈 Thread thread = new Thread(() -> { // log.info("{}執行緒執行", Thread.currentThread().getName());
// 引用外部類的共享變數 // Object o = ClassLoaderDeadLockDemo.OBJECT; }); // TODO : case 2 // 採用匿名內建類的形式 等價於 lambda表示式 // 當編譯之後可以看到會生成一個 ClassLoaderDeadLockDemo$1.class檔案 // 此時使用 javap -v 解析class檔案 可以看到 /** * new #46 // class com/xing/level/ClassLoaderDeadLockDemo$1 * * NestMembers: * com/xing/level/ClassLoaderDeadLockDemo$1 * InnerClasses: * #46; // class com/xing/level/ClassLoaderDeadLockDemo$1 */ // 在這裡可以看到 通過 new 關鍵字 來建立匿名內建類(Runnable介面的實現類)的例項 /** * 從類載入的順序進行分析 * ClassLoader#load 首先會載入 com.xing.level.ClassLoaderDeadLockDemo class檔案 * 當解析到 Thread thread = new Thread(new Runnable() { ....});時 * 此時就會載入 com.xing.level.ClassLoaderDeadLockDemo$1 class檔案 * Question1:是否是死鎖? * 雖然 load是加鎖的,但兩個類載入都是使用的相同的執行緒,且根據 synchronized 的可重入原則;因此其不是死鎖的問題 * * 當執行到 thread.start(); 時,com.xing.level.ClassLoaderDeadLockDemo$1已經載入完畢 * 此時繼續往後執行, 當執行到 thread.join(),此時由於內部呼叫了 Object#wait 會阻塞當前main執行緒,由於當前com.xing.level.ClassLoaderDeadLockDemo 尚未載入完畢因此對其內建類中的方法永遠無法執行;所以 main執行緒就被阻塞 * 在 thread.join(); 阻塞不會繼續往後執行 */ // Thread thread = new Thread(new Runnable() { // { // // 通過列印結果可以看到 當前匿名內建類的類載入器和外部類的類載入器是使用的相同的類載入器 // log.info("{}", System.identityHashCode(Thread.currentThread().getContextClassLoader())); // Object o = ClassLoaderDeadLockDemo.OBJECT; // log.info("{}", o); // } // // @Override // public void run() { // // 當 run 中沒有任何依賴外部類程式碼時,該程序也會執行結束 // System.out.println("內建類執行"); //// log.info("內建類執行"); //// log.info("{}執行緒執行", Thread.currentThread().getName()); //// // 引用外部類的共享變數 //// Object o = ClassLoaderDeadLockDemo.OBJECT; // } // }); //TODO : case3 方法引用 /** * 編譯後(javap -v )的結果可以看到這裡是引用的PrintStream類的方法資料 * #74 = InvokeDynamic #0:#75 // #0:run:(Ljava/io/PrintStream;)Ljava/lang/Runnable; * 由於引用的是外部類和當前類載入無關,因此不會出現類載入死鎖的問題 */ // Runnable target = System.out::println; // Runnable target = HelloWorld::helloWorld; // Thread thread = new Thread(target); thread.start(); try { /** * 當執行後 通過 jps 搭配 jstack pid 檢視 當前jvm程序執行緒資訊可以看到 * // 當前main執行緒被阻塞住 阻塞在 thread.join();處 * "main" #1 prio=5 os_prio=31 cpu=138.23ms elapsed=17.64s tid=0x00007f982c00da00 nid=0x2603 in Object.wait() [0x0000700007d9c000] * java.lang.Thread.State: WAITING (on object monitor) * at java.lang.Object.wait(java.base@15/Native Method) * - waiting on <0x000000070f83e6a8> (a java.lang.Thread) * at java.lang.Thread.join(java.base@15/Thread.java:1303) * - locked <0x000000070f83e6a8> (a java.lang.Thread) * at java.lang.Thread.join(java.base@15/Thread.java:1371) * at com.xing.level.ClassLoaderDeadLockDemo.<clinit>(java.storage.config/ClassLoaderDeadLockDemo.java:74) * // 顯示當前非同步執行緒在回撥 start中的run * "Thread-0" #22 prio=5 os_prio=31 cpu=0.07ms elapsed=17.50s tid=0x00007f982b04e000 nid=0x9103 in Object.wait() [0x00007000090d8000] * java.lang.Thread.State: RUNNABLE * at com.xing.level.ClassLoaderDeadLockDemo$1.run(java.storage.config/ClassLoaderDeadLockDemo.java:66) * - waiting on the Class initialization monitor for com.xing.level.ClassLoaderDeadLockDemo * at java.lang.Thread.run(java.base@15/Thread.java:832) */ // 當取消當前程式碼時,當前程序就會正常執行結束 thread.join(); // main 執行緒執行到此處, main執行緒被阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } /** * [main] INFO com.xing.level.ClassLoaderDeadLockDemo - 載入執行靜態程式碼塊 * 根據執行結果可以看到在載入執行靜態程式碼塊時就被阻塞了; 因此當前程式碼不是在執行時出現問題,而是在類加載出現了問題 * 對於 TODO : 1 沒有任何解決方法 * TODO : 2 和 TODO : 3 可以通過不依賴當前類的相關資料也可以實現 執行通過 * * @param args */ public static void main(String[] args) { log.info("{}執行緒執行", Thread.currentThread().getName()); } }

對於lambda 表示式中的 ()->{} 實際就是依賴於當前所在類來執行invokedynamic 指令; 類載入執行的流程變成了 CurrentClass -> invokedynamic -> CurrentClass; 因此這裡就出現了迴圈載入的問題

//TODO : 對 invokedynamic 分析