咱們平時說的棧是指的Java棧,native method stack 裏面裝的都是native方法java
- 方法區並非存放方法的區域,其是存放類的描述信息(模板)的地方
- Class loader只是負責class文件的加載,至關於快遞員,這個「快遞員」並非只有一家,Class loader有多種
- 加載以前是「class」,加載以後就變成了「Class」,這是安裝java.lang.Class模板生成了一個實例。「Class」就裝載在方法區,模板實例化以後就獲得n個相同的對象
- JVM並非經過檢查文件後綴是否是
.class
來判斷是否須要加載的,而是經過文件開頭的特定文件標誌
注意:加載階段失敗會直接拋出異常api
把.class文件讀入到java虛擬機中數組
動態編譯:jsp-->java-->class安全
將字節流所表明的靜態存儲結構轉換爲方法區的運行時數據結構數據結構
在java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口架構
確保class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身安全。dom
驗證階段主要包括四個檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。jvm
虛擬機常量池內的符號引用替換爲直接引用 ,類名、字段名、方法名--->具體內存地址或偏移量jsp
類變量被賦值、實例變量被初始化ide
每一個類/接口被Java程序首次主動使用的時候纔會被java虛擬機初始化
從上到下初始化、
初始化一個類時,要求它的父類都已經被初始化了(除接口)
當初始化一個類的時候並不會先初始化它實現的接口
當初始化一個接口的時候,並不會初始化它的父接口
一個父接口並不會由於它的子接口或實現類的初始化而初始化,只有當首次使用其特定的靜態變量時(即運行時常量,如接口中引用類型的變量)時纔會初始化
[L+自定義類全類名
(一維)這種形式public class ClassLoaderTest { public static void main(String[] args) { //單獨測試下列語句 //1. System.out.println(Child.str1); /*輸出 * Parent static block * hello I'm Parent */ //2. System.out.println(Child.str2); /*輸出 * Parent static block * Child static block * hello I'm Child */ //3. System.out.println(Parent.str3); /*輸出 * hello I'm Parent2 * */ //4. System.out.println(Parent.str4); /*輸出 * Parent static block * 78f59c0d-b91c-4e32-8109-dec5cb23aa13 * */ //5. Parent[] parents1=new Parent[1]; System.out.println(parents1.getClass()); Parent[][] parents2=new Parent[2][2]; System.out.println(parents2.getClass()); /*輸出 * class [Lcom.lx.Parent; * class [[Lcom.lx.Parent; * */ //6. System.out.println(Singleton1.count1); System.out.println(Singleton1.count2); System.out.println(Singleton2.count1); System.out.println(Singleton2.count2); /*輸出 * 1,1,1,0 * */ } } class Parent{ public static String str1 = "hello I'm Parent"; public static final String str3 = "hello I'm Parent2"; public static final String str4 = UUID.randomUUID().toString(); static { System.out.println("Parent static block"); } } class Child extends Parent{ public static String str2 = "hello I'm Child"; static { System.out.println("Child static block"); } } class Singleton1 { public static int count1; public static int count2=0; public static Singleton1 singleton1=new Singleton1(); public Singleton1() { count1++; count2++; } public Singleton1 getInstance(){ return singleton1 ; } } class Singleton2 { public static int count1; public static Singleton2 singleton2=new Singleton2(); public Singleton2() { count1++; count2++; } public static int count2=0; public Singleton2 getInstance(){ return singleton2 ; } }
類加載狀況
狀況1:
類的加載並不是必定要該類被主動使用化
狀況2:同上
狀況3:
自定義的類只加載了啓動類(調用常量的方法所在的類)
狀況4:加載啓動類以及Parent類
反編譯結果
狀況1:
狀況2:相似1
狀況3:沒有引用到Parent類(定義常量的類)
狀況4:相似1
接口中定義的變量都是常量
常量又分爲編譯期常量和運行期常量,編譯期常量的值在編譯期間就能夠肯定,直接存儲在了調用類的常量池中,因此訪問接口中的編譯期常量並不會致使接口的初始化,只有訪問接口中的運行期常量纔會引發接口的初始化。
父接口並不會由於子接口或是實現類的初始化而初始化,當訪問到了其特定的靜態變量時(即運行時常量,如接口中引用類型的變量)纔會初始化
public class ClassLoaderTest2 { public static void main(String[] args) { System.out.println(new demo2().a); System.out.println("====="); System.out.println(son1.a); new demo1().show(); System.out.println(demo1.str); System.out.println(son1.b); System.out.println(demo1.s);//System.out.println(son1.s); /*輸出 * father2 singleton * 1 * ===== * 1 * show method * string * father1 singleton * com.lx.father1$1@1b6d3586 * */ } } interface father1{ int a=1; void show(); String str="string"; Singleton1 s=new Singleton1(){ { System.out.println("father1 singleton"); } }; } interface son1 extends father1 { int b=0; Singleton1 s1=new Singleton1(){ { System.out.println("son1 singleton"); } }; } class demo1 implements father1{ @Override public void show() { System.out.println("show method"); } } class father2{ int a=1; void show(){} String str="string"; Singleton1 s=new Singleton1(){ { System.out.println("father2 singleton"); } }; } class demo2 extends father2{ }
第3行:子類初始化前必須初始化父類
第5-8行:訪問到編譯時常量(已經存入了調用方法類的常量池中),不會致使初始化
第9行: 訪問了運行時常量,須要初始化定義該運行時常量的類
1、java虛擬機自帶的類加載器
啓動類加載器(Bootstrap) ,C++所寫,不是ClassLoader子類
擴展類加載器(Extension) ,Java所寫
應用程序類加載器(AppClassLoader)。
2、用戶自定義的類加載器
import com.gmail.fxding2019.T; public class Test{ //Test:查看類加載器 public static void main(String[] args) { Object object = new Object(); //查看是那個「ClassLoader」(快遞員把Object加載進來的) System.out.println(object.getClass().getClassLoader()); //查看Object的加載器的上一層 // error Exception in thread "main" java.lang.NullPointerException(已是祖先了) //System.out.println(object.getClass().getClassLoader().getParent()); System.out.println(); Test t = new Test(); System.out.println(t.getClass().getClassLoader().getParent().getParent()); System.out.println(t.getClass().getClassLoader().getParent()); System.out.println(t.getClass().getClassLoader()); } } /* *output: * null * * null * sun.misc.Launcher$ExtClassLoader@4554617c * sun.misc.Launcher$AppClassLoader@18b4aac2 * */
- 若是是JDK自帶的類(Object、String、ArrayList等),其使用的加載器是Bootstrap加載器;若是本身寫的類,使用的是AppClassLoader加載器;Extension加載器是負責將把java更新的程序包的類加載進行
- 輸出中,sun.misc.Launcher是JVM相關調用的入口程序
- Java加載器個數爲3+1。前三個是系統自帶的,用戶能夠定製類的加載方式,經過繼承Java. lang. ClassLoader
Java虛擬機採用按需加載的方式,當須要使用該類是纔會去講class文件加載到內存生成class對象,加載類是採用的是雙親委派機制
自底向上檢查類是否已經被加載
自頂向下嘗試加載類
原理圖:
另一種機制:
雙親委派優點:
- 避免類的重複加載。
- 保護程序安全、防止核心api被惡意篡改(以下例子)
用戶自定義的類加載器不可能加載到一個有父加載器加載的可靠類,從而防止不可靠惡意代碼代替父加載器加載的可靠的代碼。例如:Object類老是有跟類加載器加載,其餘用戶自定義的類加載器都不可能加載含有惡意代碼的Object類
//測試加載器的加載順序 package java.lang; public class String { public static void main(String[] args) { System.out.println("hello world!"); } } /* * output: * 錯誤: 在類 java.lang.String 中找不到 main 方法 * */
解釋:
交給啓動類加載器以後(java.lang.String/由java開頭的包名)歸它管,因此它首先加載這個類(若是核心api內沒有改類也會報錯),輪不到讓系統類加載器去加載該類,即沒法加載到本身所寫的String類,核心api中的String類沒有main方法,因此會報錯說找不到main方法
類的實例化
判斷爲同一個類的必要條件
使用類加載器的緣由
自定義類加載器
獲取類加載器方法:
沙箱安全機制
命名空間
loadClass方法
經過調用ClassLoader類的loadClass方法加載一個類,並非對一個類的主動使用,不會致使初始化。
類的卸載