一、JVM內存結構
二、類加載(classLoader)機制
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的java類型。
將class文件加載到jvm虛擬機中去,程序就可以正確運行了。但是,jvm啟動的時候,並不會一次性加載所有的class文件,而是根據需要去動態加載。
1、類加載的過程:
加載:通過一個類的完全限定名查找此類字節碼文件,並利用字節碼文件創建一個Class對象
驗證:確保Class文件的字節流中包含信息符合當前虛擬機要求,並且不會危害虛擬機自身安全。主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。
准備:為類變量(被static修飾的變量)分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。
如public static int value = 5;這里只將value初始化為0。
解析:虛擬機將常量池中的符號引用替換為直接引用的過程。
符號引用就是一組符號來描述所引用的目標,可以是任何字面量,
直接引用可以是直接指向目標的指針、相對偏移量或一個能間接定位到目標的句柄。
初始化:是執行類構造器<clinit>()方法的過程。
- 遇到new,getstatic,putstatic,invokestatic這4條字節碼指令時,如果類還沒進行初始化,則需要先觸發初始化。
常見java代碼場景:用new實例化一個類時、讀取或者設置類的靜態字段時(不包括被final修飾的靜態字段,在編譯期已經被塞進常量池了)、以及調用一個類的靜態方法的時候。
- 使用java.lang.reflect.*的方法對類進行反射調用的時候,如果類還沒有進行過初始化,馬上對其進行。
- 初始化一個類的時候,如果發現其父親還沒有被初始化,則先去初始化其父類
- jvm啟動時,用戶需要指定一個要執行的主類(包含static void main(String[] args)的那個類),則jvm會先去初始化這個類。
- 當使用 JDK1.7 動態語言支持時,如果一個 java.lang.invoke.MethodHandle實例最后的解析結果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,並且這個方法句柄所對應的類沒有進行初始化,則需要先出觸發其初始化。
被動引用:
- 子類引用父類的靜態字段,不會導致子類初始化
public class SuperClass { public static int value = 123; static { System.out.println("superClass init"); } } public class SubClass extends SuperClass { static { System.out.println("subClass init"); } } public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); }
- 通過數組定義來引用類,不會觸發此類的初始化
public class SuperClass { public static int value = 123; static { System.out.println("superClass init"); } } public class NotInitialization { public static void main(String[] args) { SuperClass[] sca = new SuperClass[10]; }
- 常量在編譯階段會存入調用類的常量池中,因此不會觸發定義常量的類的初始化
public class ConstClass { public static final String HELLOWORLD = "hello world"; static { System.out.println("constClass init"); } } public class NotInitialization { public static void main(String[] args) { System.out.println(ConstClass.HELLOWORLD); } }
使用
卸載
2、類加載器有哪些:
通過類的權限定名獲取該類的二進制字節流的代碼塊叫做類加載器。
主要有一下四種類加載器:
1) 啟動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。
2) 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找並加載 Java 類。
3) 系統類加載器(system class loader)也叫應用類加載器:它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。
4) 用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實現。
自定義類加載器:父類加載器肯定為AppClassLoader。
3、雙親委派機制:
當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。
4、沙箱安全機制
三、JVM運行時數據區:
java虛擬機主要分為以下幾個區:
1) 方法區:
a. 有時候也成為永久代,在該區內很少發生垃圾回收,但是並不代表不發生GC,在這里進行的GC主要是對方法區里的常量池和對類型的卸載
b. 方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯后的代碼等數據。
c. 該區域是被線程共享的。
d. 方法區里有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量並不一定是編譯時確定,運行時生成的常量也會存在這個常量池中。
2) 虛擬機棧:
a. 虛擬機棧也就是我們平常所稱的棧內存,它為java方法服務,每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接和方法出口等信息。
b. 虛擬機棧是線程私有的,它的生命周期與線程相同。
c. 局部變量表里存儲的是基本數據類型、returnAddress類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有可能是指向對象起始地址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間確定
d. 操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同於局部變量表通過索引來訪問,而是壓棧和出棧的方式
e. 每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接.動態鏈接就是將常量池中的符號引用在運行期轉化為直接引用。
3) 本地方法棧:
本地方法棧和虛擬機棧類似,只不過本地方法棧為Native方法服務。
4) 堆:
java堆是所有線程所共享的一塊內存,在虛擬機啟動時創建,幾乎所有的對象實例都在這里創建,因此該區域經常發生垃圾回收操作。
5) 程序計數器:
內存空間小,字節碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都需要依賴這個計數器完成。該內存區域是唯一一個java虛擬機規范沒有規定任何OOM情況的區域。
四、常見的垃圾回收算法:
1、引用計數:難以處理對象間的循環引用問題(JVM不使用)
2、復制算法:(新生代) :
- 復制:eden、survivorFrom復制到survivorTo,年齡+1
- 清空:清空eden、survivorFrom
- 互換:survivorFrom和survivorTo互換,交換15次,最終還存活的對象進入老年代。
優點:不會產生內存碎片
缺點:浪費空間
3、標記-清除:(老年代)
- 先標記出要回收的對象,然后統一回收這些對象
優點:節約內存空間
缺點:產生內存碎片
4、標記-整理:(老年代)
優點:不會產生內存碎片
缺點:移動對象需要成本
五、GC的作用域
java堆+方法區