深入理解JVM,類加載機(jī)制ClassLoader流程

編譯原理請(qǐng)查看之前內(nèi)容滚秩,.java文件編譯過(guò)程和執(zhí)行過(guò)程分析以及計(jì)算機(jī)簡(jiǎn)單認(rèn)識(shí)

需要了解更多Java原創(chuàng)資料,請(qǐng)加qq:1811112688淮捆,或者加老薛的微信郁油,lukun0402。

本內(nèi)容全部原創(chuàng)攀痊,純手敲桐腌,對(duì)你有幫助可以點(diǎn)贊、關(guān)注哦9毒丁Aú簟!轉(zhuǎn)載請(qǐng)注明出處:http://www.reibang.com/p/deb040582060

1 類加載器

1:編譯涩笤、加載過(guò)程

image

2:java虛擬機(jī)何時(shí)會(huì)結(jié)束什么周期嚼吞?

 i:正常執(zhí)行完畢程序
ii:執(zhí)行了System.exit();
iii:程序執(zhí)行期間發(fā)生了異常或者錯(cuò)誤而終止
iv:由于底層操作系統(tǒng)出錯(cuò)蹬碧,導(dǎo)致虛擬機(jī)結(jié)束進(jìn)程

3:ClassLoader將數(shù)據(jù)加載到內(nèi)存中經(jīng)過(guò)的步驟:

image
i舱禽、加載:加載類的二進(jìn)制數(shù)據(jù)
ii、鏈接
  .驗(yàn)證 確保加載的類的正確性恩沽。 
  .準(zhǔn)備 類中的靜態(tài)變量分配內(nèi)存誊稚,并且其初始化為默認(rèn)值。
  .解析 把類中的符號(hào)引用變?yōu)橹苯右谩?iii、初始化為類中的類中的靜態(tài)變量賦值(正確的初始值)

3-1:驗(yàn)證:

類文件的結(jié)構(gòu)檢查:

確保類文件遵從Java類文件的固定格式(防止偽造字節(jié)碼文件)

語(yǔ)義檢查:

確保類本身符合Java語(yǔ)言的語(yǔ)法定義里伯,這一步在編譯器也會(huì)操作城瞎。比如在java規(guī)范中方法的重寫和void無(wú)關(guān)的,但是在虛擬機(jī)規(guī)范中疾瓮,重寫的定義和Java規(guī)范中的重寫定義不同脖镀。在虛擬機(jī)規(guī)范中方法返回值+方法簽名構(gòu)成了一個(gè)方法的重寫定義。這一部分有可能會(huì)被惡意用戶利用狼电。

字節(jié)碼驗(yàn)證:

確保字節(jié)碼可以被Java虛擬機(jī)安全的執(zhí)行蜒灰。一條指令包含操作碼+一個(gè)或多個(gè)操作數(shù)。例如:invokespecial #1

二進(jìn)制兼容性驗(yàn)證

確保引用的類之間協(xié)調(diào)一致肩碟。比如某個(gè)類A中引用了其他類B的的f();方法强窖,name在執(zhí)行A類的時(shí)候會(huì)檢查B類中是否存在f();方法,如果不存在削祈?就會(huì)拋出NoSuchMethodException翅溺。

PS:這里不存在的原因是由于:我們都知道如果出現(xiàn)這種情況下,編譯期間就會(huì)報(bào)錯(cuò)髓抑,但是如果別人惡意篡改了呢未巫?又或者A類和B類的編譯時(shí)通過(guò)不同的版本編譯的,那么就會(huì)出現(xiàn)不兼容情況启昧。

3-2:準(zhǔn)備:

準(zhǔn)備比階段Java虛擬機(jī)會(huì)為了的靜態(tài)變量分配內(nèi)存叙凡,并設(shè)置默認(rèn)的初始值。例如對(duì)于Person類密末,在準(zhǔn)備階段握爷。會(huì)給Person類中的int類型的靜態(tài)變量num分配4個(gè)字節(jié)的內(nèi)存空間,并且賦值默認(rèn)值0严里。

class Person{
    static int num = 10;//在準(zhǔn)備階段時(shí)新啼,num在內(nèi)存中的值為0
}

3-3:解析:

在解析階段,Java虛擬機(jī)會(huì)把類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用換為直接引用刹碾。例如:

class A{
  B b = new B();
  void method(){
    b.fun();//這段代碼在A類的二進(jìn)制數(shù)據(jù)中其實(shí)就是符號(hào)引用
  }
}
class B{
    void fun(){ 
  }
}

PS:其實(shí)符號(hào)引用為了方便程序開發(fā)燥撞。程序員能夠看懂在什么情況下,調(diào)用了那個(gè)方法而已迷帜,但是在計(jì)算機(jī)執(zhí)行的過(guò)程中物舒,真正能夠讓計(jì)算機(jī)讀懂的是二進(jìn)制數(shù)據(jù)。在二進(jìn)制數(shù)據(jù)中戏锹,Java虛擬機(jī)會(huì)將符號(hào)引用替換為一個(gè)指針冠胯,這個(gè)指針指向了方法該方法的棧幀地址,由fun方法的全名和相關(guān)描述符組成锦针。</pre>

3-4:初始化:

在初始化階段Java虛擬機(jī)會(huì)為類的靜態(tài)變量賦予初始值荠察。靜態(tài)變量初始化分為兩種:

在靜態(tài)變量聲明處進(jìn)行初始化:

public class A{
   public static int num1 = 10置蜀;//準(zhǔn)備階段num值為0,初始化階段值為    10
   public static double num2;//準(zhǔn)備階段num值為0.0悉盆,初始化階段值為0.0
}

在靜態(tài)代碼塊中進(jìn)行初始化:

public class A{
  public static double num2;//準(zhǔn)備階段num值為0.0
  static{
    num2 = 12;//初始化階段值為12.0
  }
}

3-5:初始化的面試題

public class Test02 {//啟動(dòng)器類 會(huì)被加載
  public static void main(String[] args) {
      Singleton s1 = Singleton.getInstance();//調(diào)用靜態(tài)方法 類被加載
      System.out.println(s1.count1);
      System.out.println(s1.count2);
 }
}
class Singleton{
  / /1:準(zhǔn)備階段各個(gè)變量的值分別為null盯荤,0,0
  private static Singleton singleton = new Singleton();//2:初始化階段 開始賦值
  public static int count1;//5:count1變?yōu)?
  public static int count2 = 0;//6:count2重新賦值0
  private Singleton(){
    count1++;//3:初始化階段變成1
    count2++;//4:初始化階段變成1
  }
  public static Singleton getInstance(){
    return singleton;
  }
}

public class Test02 {//啟動(dòng)器類 會(huì)被加載
   public static void main(String[] args) {
     Singleton s1 = Singleton.getInstance();//調(diào)用靜態(tài)方法 類被加載
     System.out.println(s1.count1);
     System.out.println(s1.count2);
   }
  }
class Singleton{
 //1:準(zhǔn)備階段各個(gè)變量的值分別為0焕盟,0秋秤,null,
 public static int count1;//2:初始化階段 開始賦值 0
 public static int count2 = 0;//3:初始化階段 開始賦值 0
 private static Singleton singleton = new Singleton();//4:初始化階段 開始賦值
 private Singleton(){
   count1++;//5:初始化階段變成1
   count2++;//6:初始化階段變成1
 }
  public static Singleton getInstance(){
   return singleton;
 }
}

4:類的初始化步驟:

  • 類如果沒有被加載和連接京髓,先加載和連接

  • 類存在直接父類航缀,且這個(gè)類沒有被初始化商架,那就先初始化直接父類

  • 類中若存在初始化語(yǔ)句堰怨,依次執(zhí)行初始化語(yǔ)句

5:如何確定一個(gè)類被加載了?

Java程序?qū)τ陬惖氖褂梅譃閮蓚€(gè)部分:

i:主動(dòng)使用
 A:創(chuàng)建一類的實(shí)例 new Person();
 B*:訪問(wèn)某個(gè)類蛇摸、接口中的靜態(tài)變量备图,或者對(duì)于靜態(tài)變量做讀寫;
 C:調(diào)用類中的靜態(tài)方法赶袄;
 D:反射 (Class.forName("xxx.xxx.xxx"));
 E*:初始化一個(gè)類的子類的揽涮,當(dāng)前類加載。
 F:Java虛擬機(jī)標(biāo)明的啟動(dòng)器類  (Test.class(main)|Person.class|Student.class饿肺,此時(shí)Test就是啟動(dòng)器類).
ii:被動(dòng)使用
 a:引用常量不會(huì)導(dǎo)致該類發(fā)生初始化[常量它是在編譯器確定的]
 b: 通過(guò)數(shù)組定義類的引用蒋困,不會(huì)觸發(fā)該類的初始化
 c:通過(guò)子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化敬辣。

剩余的都是被動(dòng)使用雪标。。溉跃。

PS:注意村刨,java程序首次通過(guò)主動(dòng)使用時(shí),系統(tǒng)會(huì)初始化該類或者接口撰茎。

5-1: 創(chuàng)建實(shí)例

public class Tes01{
 public static void main(String[] args){
   new A();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
 }
}
class A{
}

5-2: 訪問(wèn)某個(gè)類的靜態(tài)變量

public class Tes01{
   public static void main(String[] args){
   System.out.println(A.num);//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
 }
}
class A{
 static{
   System.out.println("我被初始化了嵌牺。。");
 }
 static int num = 10;
}
5-2-1:特例:
public class Tes01{
   public static void main(String[] args){
     System.out.println(A.num);//此時(shí)會(huì)導(dǎo)致A類初始化
   }
}
class A{
   static{
     System.out.println("我被初始化了龄糊。逆粹。");
   }
 static final int num = new Random().nextInt(50);//編譯器無(wú)法確定,必須要初始化當(dāng)前A類才能確定值炫惩,所以訪問(wèn)時(shí)A會(huì)被初始化
}

 public class Tes01{
   public static void main(String[] args){
     System.out.println(A.num);//此時(shí)會(huì)不會(huì)導(dǎo)致A類初始化
   }
}
class A{
   static{
     System.out.println("我被初始化了枯饿。。");
   }
   static final int num = 10/2;//編譯器可以確定值诡必,所以訪問(wèn)時(shí)A不會(huì)被初始化
}

public class Tes01{
   public static void main(String[] args){
     System.out.println(S.num);//此時(shí)會(huì)不會(huì)導(dǎo)致S類初始化,只會(huì)初始化F類
 }
}
 class F{
 static int num = 10;
 static{
   System.out.println("F 我被初始化了奢方。搔扁。");
 }
}
class S extends F{
  static{
     System.out.println("S 我被初始化了。蟋字。");
   }
}

PS:

1:如果final修飾的靜態(tài)常量可以在編譯期間確定值稿蹲,那么不會(huì)導(dǎo)致當(dāng)前類初始化,如果訪問(wèn)的final修飾的靜態(tài)常量不可以在編譯期間確定值鹊奖,那么會(huì)導(dǎo)致當(dāng)前類初始化苛聘。

2:只有當(dāng)程序訪問(wèn)的靜態(tài)變量或靜態(tài)方法確實(shí)在當(dāng)前類或當(dāng)前接口中定義時(shí),才會(huì)認(rèn)為是對(duì)于當(dāng)前類和接口的主動(dòng)使用忠聚。

5-3:調(diào)用類的靜態(tài)方法

public class Tes01{
  public static void main(String[] args){
   new A().fun();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
  }
}
class A{
 static void fun(){}
}

5-4:反射

public class Tes01{
   public static void main(String[] args){
     Class.forName("com.mage.jvm.A");//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載
   }
}
class A{
}

5-5:初始化子類

public class Tes01{
   public static void main(String[] args){
     new B();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載,包括B類的間接父類也會(huì)被加載
   }
}
class A{
}
class B extends A{
}

public class Tes01{
   public static void main(String[] args){
     new A();//先執(zhí)行a的初始化
     new B();//此時(shí)會(huì)導(dǎo)致A類會(huì)被加載,由于已經(jīng)初始化過(guò)了设哗,所以不會(huì)初始化。但是在一個(gè)類加載器的情況下两蟀,如果有其他加載器网梢,還是會(huì)加載的。
   }
}
class A{
   static{System.out.println("A");}
}
class B extends A{
 static{System.out.println("B");}
}

5-6:表明的啟動(dòng)器類

public class Tes01{
   public static void main(String[] args){
     System.out.println("hello");//執(zhí)行時(shí)會(huì)導(dǎo)致當(dāng)前Test01加載
   }
}

5-7:一些特殊的不會(huì)導(dǎo)致加載的情況

5-7-1:初始化父類并不包含接口
public class Test01{
   public static void main(String[] args){
     new Son();//會(huì)導(dǎo)致Father赂毯、Son加載战虏,但是不會(huì)導(dǎo)致Sun的父接口Run加載;
   }
}
class Father{
   static{System.out.println("父類加載了");}
}
class Son extends Father{
   static{System.out.println("子類加載了");}
}
interface Run{
   Thread rThread = new Thread(){
   {
     System.out.println("run 執(zhí)行了");
   }
  };
}
5-7-2:某些情境下會(huì)導(dǎo)致父接口初始化

程序首次使用特定接口的靜態(tài)變量時(shí)党涕,會(huì)導(dǎo)致該接口初始化烦感。

public class Test01 {
   public static void main(String[] args) {
     System.out.println(SRun.sThread);//不會(huì)導(dǎo)致FRun加載
   }
}
interface FRun{
   int fNum  = new Random().nextInt(3);
   Thread fThread = new Thread(){
   //每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
   {
     System.out.println("FRun invoked"+fNum);
   }
   };
}
interface SRun extends  FRun{
   int sNum  = new Random().nextInt(3);
   Thread sThread = new Thread(){
   //每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
   {
     System.out.println("SRun invoked"+sNum);
   }
   };
}
5-7-3:初始化一個(gè)接口時(shí),并不會(huì)初始化它的父接口
public class Test01 {
   public static void main(String[] args) {
     System.out.println(SRun.sThread);//不會(huì)導(dǎo)致FRun加載
   }
}
interface FRun{
   int fNum  = new Random().nextInt(3);
   Thread fThread = new Thread(){
   //每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
   {
     System.out.println("FRun invoked"+fNum);
   }
   };
}
interface SRun extends  FRun{
   int sNum  = new Random().nextInt(3);
   Thread sThread = new Thread(){
   //每個(gè)實(shí)例創(chuàng)建時(shí)都會(huì)執(zhí)行一次
   {
     System.out.println("SRun invoked"+sNum);
     System.out.println(fThread);//調(diào)用父類時(shí)膛堤,會(huì)導(dǎo)致父接口加載
   }
   };
}
5-7-4:被動(dòng)使用不到值初始化
A: 通過(guò)數(shù)組定義類的引用手趣,不會(huì)觸發(fā)該類的初始化。
public class Test01 {
   public static void main(String[] args) {
     Person[] ps = new Person[10];
   }
}
class Person{
   static{
     System.out.println("被初始化了肥荔。绿渣。。次企。");
   }
}
B:通過(guò)子類引用父類的靜態(tài)變量怯晕,不會(huì)導(dǎo)致子類初始化。
public class Test01 {
   public static void main(String[] args) {
     System.out.println(S.num);
   }
}
class P{
   static int num = 10;
   static{
      System.out.println("父類被加載了缸棵。舟茶。。");
   }
}
class S extends P{
   static{
     System.out.println("子類類被加載了堵第。吧凉。。踏志。");
   }
}
C:調(diào)用ClassLoader類的loadClass方法加載一個(gè)類阀捅,并不是對(duì)于類的主動(dòng)使用。
package com.mage.test;
public class Test03{
   public static void main(String[] args) throws Exception {
     F.class.getClassLoader().loadClass("com.mage.test.F");
     ClassLoader.getSystemClassLoader().loadClass("com.mage.test.F");
   }
}
class F{
  static{
    System.out.println("F 我被初始化了针余。饲鄙。");
 }
}

5:加載.class文件的方式:

-1:本地系統(tǒng)當(dāng)中直接加載 *
-2:通過(guò)網(wǎng)絡(luò)下載.class文件
-3:從jar凄诞、zip等歸檔文件中加載.class文件 *
-4:數(shù)據(jù)庫(kù)(.class文件)
-5:動(dòng)態(tài)編譯.class</pre>

當(dāng)一個(gè)類加載到內(nèi)存中之后,會(huì)在堆內(nèi)存當(dāng)中產(chǎn)生一個(gè)對(duì)應(yīng)的Class的對(duì)象忍级,所有當(dāng)前的類的 實(shí)例以及當(dāng)前該類都共享這一份Class實(shí)例帆谍。

6:類加載器

java虛擬機(jī)自帶的加載器:
 根類加載器(bootstrap)
 擴(kuò)展類加載器(PlatformClassLoader、低版本 Ext)
 系統(tǒng)類加載器(AppClassLoader) 
用戶自定義加載器
 java.lang.ClassLoader的子類
 可以定制類的加載方法 

注意:類加載器并不需要等到一個(gè)類"首次主動(dòng)使用"時(shí)在加載它轴咱。 可以預(yù)先加載汛蝙。JVM規(guī)范中定義類,類加載器可以在預(yù)料到某個(gè)類可能 需要加載時(shí)預(yù)先加載朴肺。 hotspot窖剑、jrokit、j9都是對(duì)于JVM規(guī)范的一種實(shí)現(xiàn)戈稿。

ps:注意用戶的自定義類加載的父加載器 不一定全是系統(tǒng)類加載器

6-1:JVM加載器詳解:

根加載器 無(wú)父加載器西土,負(fù)責(zé)加載虛擬機(jī)核心類庫(kù)。比如LANG包下的類器瘪。它的實(shí)現(xiàn)以來(lái)與底層操作系統(tǒng)翠储,屬于虛擬機(jī)實(shí)現(xiàn)的一部分绘雁,沒有繼承==JAVA.LANG.CLASSLOADER==類
擴(kuò)展加載器 它的父類是根加載器橡疼,一般加載jre/lib/ext下的目錄。如果將自定義創(chuàng)建好的jar文件放入該目錄庐舟,則jar文件會(huì)自動(dòng)交由擴(kuò)展類加載器加載欣除。擴(kuò)展類加載器是java.lang.ClassLoader的子類。
系統(tǒng)類加載器 應(yīng)用類加載器挪略,父加載器是擴(kuò)展類加載器历帚。從配置的ClassPath或者系統(tǒng)指定的(一般為當(dāng)前java命令執(zhí)行的當(dāng)前目錄)中加載類。是用戶自定義類加載器的父加載器杠娱。也是java.lang.ClassLoader的子類
自定義類加載器 用戶自定義類加載器需要繼承java.lang.ClassLoader挽牢。
public class Test01 {
  public static void main(String[] args) {
     String str = "";
     //NULL -> 根加載器 bootstrap[c、c++] 
     System.out.println(str.getClass().getClassLoader());

     //jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17
     //應(yīng)用類加載器
     System.out.println(new Person().getClass().getClassLoader());

     //jdk.internal.loader.ClassLoaders$PlatformClassLoader@2752f6e2                     
  //EXT加載  擴(kuò)展類加載器
     System.out.println(new Person().getClass().getClassLoader().getParent());

   //根加載器 bootstrap[c摊求、c++]
   System.out.println(new Person().getClass().getClassLoader().getParent().getParent());
  }
}
class Person{}

6-2:類加載器關(guān)系圖

image

6-3: 類加載器的雙親委派機(jī)制(父類委托機(jī)制)

類加載器將類加載到j(luò)ava虛擬機(jī)中禽拔。jdk1.2之后,類的加載機(jī)制采用父類委托機(jī)制室叉,可以更好的保證安全性睹栖。除了Java虛擬機(jī)自帶的根加載器以外,其他類加載都有且只有一個(gè)父加載器茧痕。當(dāng)Java程序請(qǐng)求加載loader1加載A類時(shí)野来,loader1首先委派自己的父加載器加載A類,若父加載器可以加載踪旷,交由父加載器完成該加載任務(wù)凿可,否則由loader1加載A類。

PS:各個(gè)加載器按照父子關(guān)系形成一個(gè)樹形結(jié)構(gòu)磨镶,除了根加載器外嗅义,其余加載器都有且包含一個(gè)父加載器。

6-3-1: 樹形結(jié)構(gòu)圖:
image

ps:執(zhí)行過(guò)程悬包,loader2首先會(huì)在自己的命名空間中查找A類是否被加載,如果已經(jīng)加載了,直接返回代表A類的Class對(duì)象的引用灰追。如果A類沒有被加載,loader2會(huì)請(qǐng)求loader1代為加載狗超,loader1再請(qǐng)求系統(tǒng)類加載器加載弹澎。一直請(qǐng)求到根加載器加載,如果在請(qǐng)求過(guò)程中都不能加載努咐,此時(shí)系統(tǒng)類加載器嘗試加載苦蒿,如果能夠加載成功,則將A對(duì)應(yīng)的Class對(duì)象返回給loader1渗稍,loader1再返回給loader2佩迟,如果系統(tǒng)類加載也不能加載成功,則交由loader1進(jìn)行加載竿屹,loader1如果也失敗报强,則loader2嘗試加載。如果所有的父加載器以及l(fā)oader2本身都沒有加載成功拱燃,則拋出ClassNotFountException異常秉溉。

注意,在這個(gè)過(guò)程中碗誉,如果有一個(gè)類加載器成功加載了A類召嘶,name這個(gè)類加載器稱之為==定義類加載器==,所有能夠成功返回Class對(duì)象的引用的類加載器包括定義類加載器在內(nèi)都稱之為==初始類加載器==哮缺。

例如:如果loader1加載A類成功弄跌,那么loader1稱之為A類的定義類加載器,而loader1和loader2都稱之為A類的初始類加載器尝苇。

6-3-2: 父類委托機(jī)制中的父類含義:

父類委托機(jī)制中的父子關(guān)系大多數(shù)場(chǎng)景下不見得是父子的繼承關(guān)系铛只,大多數(shù)場(chǎng)景下其實(shí)是組合的關(guān)系。并且在一些場(chǎng)景下我們還能看到父子加載器是同一個(gè)加載器的兩個(gè)實(shí)例茎匠。比如:

自定義類加載器中同一個(gè)加載器的兩個(gè)實(shí)例格仲,出現(xiàn)父子關(guān)系。
public class Test04 {
   //這里只是為了表現(xiàn)一個(gè)加載器的兩個(gè)不同實(shí)例诵冒,也可以出現(xiàn)父子關(guān)系
 //本次代碼寫的不作為具體的自定義類加載器
 public static void main(String[] args) {
   ClassLoader classLoader1 = new MyClassLoader();
   ClassLoader classLoader2 = new MyClassLoader(classLoader1);
   }
}
class MyClassLoader extends ClassLoader{
   public MyClassLoader(){
   }
   public MyClassLoader(ClassLoader cl){
   }
   @Override
   protected Class<?> findClass(String name) throws     ClassNotFoundException {
     return super.findClass(name);
   }
}
Java中ClassLoader構(gòu)造器:

ClassLoader文檔注釋:

CLASSLOADER
protected ClassLoader()
Creates a new class loader using the ClassLoader returned by the method getSystemClassLoader() as the parent class loader.If there is a security manager, its checkCreateClassLoader method is invoked. This may result in a security exception.
Throws:SecurityException -If a security manager exists and its checkCreateClassLoader method doesn't allow creation of a new class loader.

ps:這里我們能看到通過(guò)ClassLoader構(gòu)造器返回的是通過(guò)getSystemClassLoader()獲取到的加載器作為當(dāng)前加載器的父類凯肋。

ClassLoader源碼解讀:

public abstract class ClassLoader {
   protected ClassLoader() {
   //一:
     this(checkCreateClassLoader(), null, getSystemClassLoader());
   }
   //三:
   private ClassLoader(Void unused, String name, ClassLoader parent) 
   {
     this.name = name;
     //四:
     this.parent = parent;
     this.unnamedModule = new Module(this);
     if (ParallelLoaders.isRegistered(this.getClass())) {
       parallelLockMap = new ConcurrentHashMap<>();
       package2certs = new ConcurrentHashMap<>();
       assertionLock = new Object();
     } else {
       // no finer-grained lock; lock on the classloader instance
       parallelLockMap = null;
       package2certs = new Hashtable<>();
       assertionLock = this;
     }
       this.nameAndId = nameAndId(this);
   }
 //二:
 public static ClassLoader getSystemClassLoader() {
   switch (VM.initLevel()) {
   case 0:
   case 1:
   case 2:
   // the system class loader is the built-in app class loader during startup
     return getBuiltinAppClassLoader();
   case 3:
     String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
     throw new IllegalStateException(msg);
   default:
       // system fully initialized
     assert VM.isBooted() && scl != null;
     SecurityManager sm = System.getSecurityManager();
     if (sm != null) {
       checkClassLoaderPermission(scl, Reflection.getCallerClass());
     }
     return scl;
   }
 }
}

ps: 在源碼中我們看到當(dāng)要構(gòu)建ClassLoader對(duì)象時(shí)[一],需要通過(guò)getSystemClassLoader方法獲取到一個(gè)ClassLoader對(duì)象[二]汽馋,將獲取到的對(duì)象傳入帶參構(gòu)造器的中[三]侮东,執(zhí)行了this.parent = parent;[四]圈盔。

總結(jié)一句話,當(dāng)我們創(chuàng)建類加載器對(duì)象時(shí)悄雅,傳入的類加載器A是創(chuàng)建的類加載器B的父加載器驱敲。默認(rèn)情況下創(chuàng)建的類加載器的父加載器是系統(tǒng)類加載器。return getBuiltinAppClassLoader();

6-3-3:為什么需要設(shè)計(jì)父類委托機(jī)制
6-3-3-1:why? 出于何種考慮設(shè)計(jì)的父類委托機(jī)制宽闲?

父類委托機(jī)制可以提高軟件系統(tǒng)的安全性众眨,由于用戶自定義的類加載器是無(wú)法在父類委托機(jī)制下加載應(yīng)該由父加載器加載的類。防止了不安全甚至是惡意代碼的加載容诬。比如lang包下的類是由根加載器加載娩梨,其他任何用戶自定義加載器都無(wú)法加載包含了惡意代碼的java.lang.Object類。

例子:假設(shè)自己設(shè)計(jì)一個(gè)類览徒,然后是存在惡意代碼的狈定,如果沒有父類委托機(jī)制,通過(guò)自己編寫的加載器习蓬,直接加載到內(nèi)存中纽什,這就有問(wèn)題了。如果存在父類委托機(jī)制躲叼,那么交由父加載器加載芦缰,父加載器按照jvm規(guī)則加載,如果存在惡意代碼押赊,就不會(huì)加載饺藤。

6-3-3-2: how? 如何保證安全包斑?

需要提及兩個(gè)概念流礁,一個(gè)是命名空間,一個(gè)是運(yùn)行時(shí)包罗丰。

命名空間:存在的目的就是為了防止同名神帅。

每個(gè)類加載器都有自己的命名空間,命名空間=該類加載器+所有父加載器所加載的類組成萌抵。

JAVA虛擬機(jī)為每一個(gè)類加載器維護(hù)一個(gè)命名空間找御。在同一個(gè)命名空間下,不會(huì)出現(xiàn)名字完全相同的兩個(gè)類绍填,不同命名空間下霎桅,有可能會(huì)出現(xiàn)相同兩個(gè)類。

一個(gè)類在被類加載器加載到內(nèi)存中之后讨永,是可以被其他類加載器繼續(xù)加載的滔驶。只不過(guò)這兩個(gè)類加載器不能存在父子關(guān)系。如果一個(gè)類在加載到內(nèi)存中之前卿闹,已經(jīng)存在一個(gè)類加載器加載過(guò)了揭糕,那么在相同命名空間下就不會(huì)被加載萝快,

運(yùn)行時(shí)包:同一類加載器加載的屬于相同包的類組成了運(yùn)行時(shí)包。

運(yùn)行時(shí)包 = 包名 + 定義類加載器(第一次加載一個(gè)類成功的加載器稱之為當(dāng)前類的定義類加載器著角,后續(xù)所有的子加載器稱之為該類) 揪漩。

這樣才能防止用戶自定義類冒充核心類庫(kù),調(diào)用一些包可見的方法吏口。

package com.mage.jvm01;
public class Test01 {
  public static void main(String[] args) {
     //測(cè)試調(diào)用String類下的lastIndexOf方法奄容,該方法是默認(rèn)的訪問(wèn)權(quán)限
     byte b = 12;
     String.lastIndexOf(new byte[12],b,2,"",1);//編譯報(bào)錯(cuò),由于方法不可訪問(wèn)到
   }
}

結(jié)論:不能直接訪問(wèn)默認(rèn)訪問(wèn)權(quán)限的不同包下的類产徊。

package java.lang;
public class System {
   public static void main(String[] args) {
     byte b = 12;
     //冒充為lang包下的類嫩海,訪問(wèn)默認(rèn)修飾符的方法,編譯通過(guò)囚痴,執(zhí)行出錯(cuò)叁怪。
     String.lastIndexOf(new byte[12],b,2,"",1);
   }
}

ps:由于運(yùn)行時(shí)包不光要看加載類的包名還要看加載該類的類加載器。由于lang下的類和我們自定義的lang包下的類不由同一類加載器加載深滚,所以他們不再一個(gè)包下奕谭。

6-3-3-3: 面試題

能不能自己寫個(gè)類叫java.lang.System

答案:通常不可以痴荐,但可以采取另類方法達(dá)到這個(gè)需求血柳。 解釋:為了不讓我們寫System類,類加載采用委托機(jī)制生兆,這樣可以保證爸爸們優(yōu)先难捌,爸爸們能找到的類,兒子就沒有機(jī)會(huì)加載鸦难。而System類是Bootstrap加載器加載的根吁,就算自己重寫,也總是使用Java系統(tǒng)提供的System合蔽,自己寫的System類根本沒有機(jī)會(huì)得到加載击敌。

但是,我們可以自己定義一個(gè)類加載器來(lái)達(dá)到這個(gè)目的拴事,為了避免雙親委托機(jī)制沃斤,這個(gè)類加載器也必須是特殊的。由于系統(tǒng)自帶的三個(gè)類加載器都加載特定目錄下的類刃宵,如果我們自己的類加載器放在一個(gè)特殊的目錄衡瓶,那么系統(tǒng)的加載器就無(wú)法加載,也就是最終還是由我們自己的加載器加載牲证。

6-4:自定義類加載器

6-4-1:自定義類加載器步驟

要?jiǎng)?chuàng)建用戶自定義類加載器哮针,需要繼承java.lang.ClassLoader類,然后重寫一些findClass(String str)方法。這個(gè)方法根據(jù)指定的類的名字诚撵,返回對(duì)應(yīng)的Class對(duì)象的引用缭裆。

源碼解析

第一步:

protected Class<?> findClass(String name) throws           ClassNotFoundException {
   throw new ClassNotFoundException(name);
 }

Finds the class with the specified binary name. This method 
should be overridden by class loader implementations that follow 
the delegation model for loading classes, and will be invoked by 
the loadClass method after checking the parent class loader for 
the requested class.</pre>

ps:這里findClass只會(huì)拋出一個(gè)異常,我們需要重寫findClass方法寿烟。在官方文檔中的描述澈驼,根據(jù)只指定的binary name(二進(jìn)制名稱)查找類。通過(guò)遵守父類委托機(jī)制的類加載器需要重寫這個(gè)方法區(qū)加載一個(gè)類筛武,在執(zhí)行請(qǐng)求一個(gè)類時(shí)會(huì)先檢查父加載器缝其,然后將執(zhí)行l(wèi)oadClass方法。

第二步:

public Class<?> loadClass(String name) throws     ClassNotFoundException {
   return loadClass(name, false);
}

Loads the class with the specified binary name. This 
method searches for classes in the same manner as 
the loadClass(String, boolean) method. It is invoked by the 
Java virtual machine to resolve class references. Invoking 
this method is equivalent to invoking loadClass(name, false).

ps:執(zhí)行該方法和執(zhí)行l(wèi)oadClass(String, boolean) 這個(gè)方法的搜索類的方式相同徘六。該方法的執(zhí)行時(shí)通過(guò)jvm區(qū)識(shí)別類的引用内边,和執(zhí)行l(wèi)oadClass(String, boolean)相同。在代碼中我們也發(fā)現(xiàn)其實(shí)底層就是調(diào)用loadClass(String, boolean)待锈。

第三步:

protected Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException
{
   synchronized (getClassLoadingLock(name)) {
   // First, check if the class has already been loaded
   Class<?> c = findLoadedClass(name);
   if (c == null) {
     long t0 = System.nanoTime();
     try {
       if (parent != null) {
         c = parent.loadClass(name, false);
       } else {
         c = findBootstrapClassOrNull(name);
       }
     } catch (ClassNotFoundException e) {
     // ClassNotFoundException thrown if class not found
     // from the non-null parent class loader
     }
 if (c == null) {
   // If still not found, then invoke findClass in order
   // to find the class.
   long t1 = System.nanoTime();
   c = findClass(name);
   // this is the defining class loader; record the stats
   PerfCounter.getParentDelegationTime().addTime(t1 - t0);                         PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
   PerfCounter.getFindClasses().increment();
   }
 }
 if (resolve) {
   resolveClass(c);
 }
 return c;
 }
}

ps:這里我們要關(guān)注幾點(diǎn)漠其,第一點(diǎn)就是當(dāng)前方法首先會(huì)檢查類是否被加載過(guò),如果沒有被加載竿音,會(huì)執(zhí)行parent != null的判定和屎,這里的parent就是當(dāng)前加載器的父加載器。如果存在父加載器春瞬,就會(huì)交由父加載器加載柴信。如過(guò)是空則交由根加載器加載。

第四步:關(guān)于parent變量的解釋:

protected ClassLoader() {
   this(checkCreateClassLoader(), null, getSystemClassLoader());
}

i:默認(rèn)情況下創(chuàng)建ClassLoader時(shí)會(huì)調(diào)用其他構(gòu)造器宽气。且傳入的parent為系統(tǒng)類加載器随常。

private ClassLoader(Void unused, String name, ClassLoader parent) {
 this.name = name;
 this.parent = parent;//這行代碼很重要
 this.unnamedModule = new Module(this);
 if (ParallelLoaders.isRegistered(this.getClass())) {
   parallelLockMap = new ConcurrentHashMap<>();
   package2certs = new ConcurrentHashMap<>();
   assertionLock = new Object();
 } else {
   // no finer-grained lock; lock on the classloader instance
   parallelLockMap = null;
   package2certs = new Hashtable<>();
   assertionLock = this;
 }
 this.nameAndId = nameAndId(this);
 }

ii:這會(huì)將系統(tǒng)類加載作為當(dāng)前類加載器的父類。

編寫自定義類加載器
/**
 * 編寫自定義類加載器:
 *  1:繼承ClassLoader
 *  2:重寫findClass
 *  3:通過(guò)自定義的loadClassData萄涯,將字節(jié)碼文件讀取到字節(jié)數(shù)組中
 *  4:通過(guò)defineClass方法將字節(jié)數(shù)組中的數(shù)據(jù)轉(zhuǎn)換為Class對(duì)象
 *  5:測(cè)試
 */
public class MyClassLoader extends ClassLoader{
   //定義加載的目錄:
   private String path = "";
   //定義加載文件的后綴
   private final static  String fileType = ".class";
   //定義類加載的名稱
   private String name ;
  public MyClassLoader(String name){
    super();
    this.name = name;
   }
   public MyClassLoader(String name,ClassLoader parent){
     super(parent);
     this.name = name;
   }
   //重寫findClass
   @Override
   protected Class<?> findClass(String name) throws         ClassNotFoundException {
     byte[ ] data = loadClassData(name);
     final Class<?> aClass = defineClass(name, data, 0, data.length);
     return aClass;
   }
   //定義loadClass方法 通過(guò)全限定名稱 獲取字節(jié)數(shù)組
   private byte[] loadClassData(String name){
     //聲明返回?cái)?shù)據(jù)
     byte[ ] data = null;
     try(InputStream is = new FileInputStream(new File(path+name+fileType));
     ByteArrayOutputStream baos = new ByteArrayOutputStream() ){
       int len = 0;
       while((len = is.read())!= -1){
       baos.write(len);
     }
     data = baos.toByteArray();
   }catch (IOException e){
     e.printStackTrace();
   }
   return data;
  }
 public void setPath(String path) {
   this.path = path;
 }
 @Override
 public String toString() {
   return "MyClassLoader{" +
   "path='" + path + '\'' +
   ", name='" + name + '\'' +
   '}';
 }
 public static void main(String[] args) {
   MyClassLoader loader1 = new MyClassLoader("loader01");
   loader1.setPath("/Users/iongst/app/client/");
   MyClassLoader loader2 = new MyClassLoader("loader02",loader1);
   loader2.setPath("/Users/iongst/app/serverlib/");
   MyClassLoader loader3 = new MyClassLoader("loader03",null);
   loader3.setPath("/Users/iongst/app/otherlib/");
   loader(loader1);
   loader(loader2);
   loader(loader3);
 }
 private static void loader(ClassLoader classLoader) {
   try {
     Class clz = classLoader.loadClass("A");
     Constructor c = clz.getConstructor(null);
     c.newInstance(null);
   } catch (ClassNotFoundException e) {
     e.printStackTrace();
   } catch (NoSuchMethodException e) {
     e.printStackTrace();
   }catch (Exception e){
     e.printStackTrace();
  }
 }
}
自定義類加載器關(guān)系
image
一個(gè)類被加載多次
A 被加載:    jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
?B 被加載:jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
A 加載:MyClassLoader{path='/Users/iongst/app/otherlib/',name='loader03'}
?B 加載:
MyClassLoader{path='/Users/iongst/app/otherlib/', name='loader03'}

PS:A類和B類被重復(fù)加載多次绪氛。由于選擇的類加載器不同,導(dǎo)致系統(tǒng)類加載器和loader3在各自的命名空間中都存在A窃判、和B類

image
面試問(wèn)題:

在A類中的B類會(huì)交由那個(gè)類加載器加載呢钞楼?

答案分析:同樣會(huì)交由加載A類的加載器加載B類,同時(shí)在加載B類時(shí)也會(huì)遵守父類委托機(jī)制袄琳。測(cè)試以上代碼,主需要將將在A類的某個(gè)目錄下的B類文件刪除燃乍,查看B類時(shí)由那個(gè)類加載即可唆樊。

當(dāng)前 A 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29</pre>
6-4-2 命名空間不同導(dǎo)致的可見性關(guān)系

同一個(gè)命名空間下的類時(shí)互相可見的。
子加載器的命名空間包含的所有父加載器的命名空間刻蟹。因此子加載器記載的類可以看見父加載器加載的類逗旁。例如系統(tǒng)類加載器加載的類時(shí)可以看見根加載器加載的類的。
父加載器加載的類不能看見子加載器所加載的類。
如果兩個(gè)加載器之間沒有之間或間接的父子關(guān)系片效,那么他們各自加載的類互不可見红伦。

測(cè)試用例:

在原來(lái)的代碼的基礎(chǔ)上,保證client存在A和B類淀衣,然后在sys文件夾中運(yùn)行MyClassLoader.查看結(jié)果昙读。

public static void main(String[] args) {
   MyClassLoader loader1 = new MyClassLoader("loader01");
   loader1.setPath("/Users/iongst/app/client/");
   MyClassLoader loader2 = new MyClassLoader("loader02",loader1);
   loader2.setPath("/Users/iongst/app/serverlib/");
   loader(loader1);
   try {
     Class clz = loader1.loadClass("A");
     Constructor c = clz.getConstructor(null);
     Object obj = c.newInstance(null);
     A a = (A)obj;
     System.out.println(a.num);
   } catch (Exception e) {
     e.printStackTrace();
   }
 }

結(jié)果:

A加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
A 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
?B 被加載:MyClassLoader{path='/Users/iongst/app/client/', name='loader01'}
Exception in thread "main" java.lang.NoClassDefFoundError: Aat     MyClassLoader.main(MyClassLoader.java:77)
Caused by: java.lang.ClassNotFoundException: A
 at     java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassL    oader.java:583)
   at         java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
 at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
 ... 1 more</pre>

原因描述:由于MyClassLoader的類加載器是Sys系統(tǒng)類加載器。而A和B的加載時(shí)通過(guò)loader01加載的膨桥。也就MyClassLoader看不見A類蛮浑。導(dǎo)致java.lang.NoClassDefFoundError。

如果把client中的文件導(dǎo)入到sys中只嚣,就不會(huì)出現(xiàn)問(wèn)題沮稚。

7:類的卸載

7-1:測(cè)試用例

public static void main(String[] args) {
   MyClassLoader loader1 = new MyClassLoader("loader01");
   loader1.setPath("/Users/iongst/app/client/");
   try {
     Class clz = loader1.loadClass("A");
     System.out.println("class hashcode"+clz.hashCode());
     Constructor c = clz.getConstructor(null);
     Object obj = c.newInstance(null);
     loader1 = null;
     clz = null;
     obj = null;
     loader1 = new MyClassLoader("loader01");
     loader1.setPath("/Users/iongst/app/client/");
     clz = loader1.loadClass("A");
     System.out.println("class hashcode"+clz.hashCode());
     c = clz.getConstructor(null);
     obj = c.newInstance(null);
 } catch (Exception e) {
   e.printStackTrace();
}
 }

7-2 結(jié)果分析

7-2-1:結(jié)論分析:

A類有l(wèi)oader1加載。在類加載器的內(nèi)部實(shí)現(xiàn)中册舞,用一個(gè)集合存放了加載類的引用蕴掏。另一方面,一個(gè)Class對(duì)象總是會(huì)引用它的類加載器调鲸。調(diào)用getClassLoader()方法囚似,即可獲取對(duì)應(yīng)的類加載器。所以class實(shí)例和loader1之間是一個(gè)雙向的關(guān)聯(lián)關(guān)系线得。Class<->ClassLoader

一個(gè)類的實(shí)例總是引用代表這個(gè)類的Class對(duì)象饶唤。在Object中定義了getClass()方法獲取對(duì)于Class實(shí)例的引用。Java類中也存在靜態(tài)class屬性贯钩。引用代表當(dāng)前這個(gè)類的Class對(duì)象募狂。所以類的實(shí)例和Class對(duì)象是一個(gè)單向的引用關(guān)系。 Class <- instance.

7-2-2:畫圖分析
image
7-2-3: 結(jié)論

打印的兩次的哈希值不同角雷。因此clz變量?jī)纱我昧瞬煌腃lass對(duì)象祸穷。可見Java虛擬機(jī)的生命周期中勺三,對(duì)A類先后加載兩次雷滚。

7-3:卸載

當(dāng)A類被加載、鏈接吗坚、和初始化后祈远,它的聲明周期就開始了。代表A的Class對(duì)象不再引用商源,及不可被觸及车份,Class對(duì)象就會(huì)結(jié)束生命周期。A類的方法區(qū)內(nèi)的數(shù)據(jù)也會(huì)被卸載牡彻。從而結(jié)束A類的生命周期扫沼。所以一個(gè)類何時(shí)結(jié)束生命周期,取決于代表它的Class對(duì)象何時(shí)結(jié)束生命周期。

java虛擬機(jī)自帶的類加載所加載的類缎除,在虛擬機(jī)生命周期中严就,始終不會(huì)被卸載。因?yàn)閖ava虛擬機(jī)本身會(huì)一直引用這些類加載器器罐,而這些類加載器則會(huì)始終引用他們所加載的類的Class對(duì)象梢为,因此這些Class對(duì)象始終是可觸及的。

而用戶自定義的類加載所加載的類時(shí)可以被卸載的技矮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抖誉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衰倦,更是在濱河造成了極大的恐慌袒炉,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件樊零,死亡現(xiàn)場(chǎng)離奇詭異我磁,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)驻襟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門夺艰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沉衣,你說(shuō)我怎么就攤上這事郁副。” “怎么了豌习?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵存谎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肥隆,道長(zhǎng)既荚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任栋艳,我火速辦了婚禮恰聘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吸占。我一直安慰自己晴叨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布旬昭。 她就那樣靜靜地躺著篙螟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪问拘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音骤坐,去河邊找鬼绪杏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纽绍,可吹牛的內(nèi)容都是我干的蕾久。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼拌夏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼僧著!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起障簿,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盹愚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后站故,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皆怕,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年西篓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愈腾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岂津,死狀恐怖虱黄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吮成,我是刑警寧澤橱乱,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站赁豆,受9級(jí)特大地震影響仅醇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜魔种,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一析二、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧节预,春花似錦叶摄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至糠赦,卻和暖如春会傲,著一層夾襖步出監(jiān)牢的瞬間锅棕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工淌山, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裸燎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓泼疑,卻偏偏與公主長(zhǎng)得像德绿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子退渗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344