class文件
一個Java文件通過編譯工具javac編譯成class字節碼文件,通過JVM進行加載運行。因爲JVM屏蔽底層操作系統的差異,所以一次編譯到處運行。
JVM內存結構
JVM分爲外部和內部,內部就是數據區,其他部分就是外部
- 線程私有數據區隨着線程結束而結束,沒有安全問題;
- 線程公有數據區存在安全問題,內存優化和溢出都需要關注這個地方。
類加載子系統
- 加載:通過類的完全限定名來找到類文件所在位置,根據字節碼創建java.lang.class對象
- 驗證:確保加載的字節碼符合虛擬機要求,是一種自我保護機制,不讓危害虛擬機安全。包括四種驗證:字節碼驗證、文件格式驗證、元數據驗證、符號引用驗證。
- 準備:爲類變量分配地址和初始化值,分配到方法區。初始值是數值類型默認的初始值。
- 解析:把符號引用轉換爲直接引用。
- 初始化:爲變量賦予正確值。
雙親委派方式
自底向上檢查是否加載成功,自頂向下嘗試加載。
收到類加載請求後,先加載父類,父類加載完才加載子類。如果父類以上還存在父類,那麼繼續加載上面的父類,直到最高的父類加載完才往下加載。
類加載的三種方式
以下示範代碼中,A是類,a1、a2、a3是A實例化的對象
A a1=new A();
靜態加載,通過new關鍵字實例化對象
Class clazz=Class.forName("A");
Object a2=a.newInstance();
動態加載,通過forName來加載類,調用類的newInstance方法實例化對象
Class clazz=classLoader.loadClaass("A");
Object a3=clazz.newInstance();
動態加載,繼承ClassLoader實現自定義類加載器
程序計數器
cpu進行切換的時候,指向當前需要獲取指令的位置,線程私有,記錄程序執行位置,不會發生內存溢出,只佔一小塊區域。
虛擬機棧
作用是執行方法。棧的順序,先進後出。先執行的方法在下面,按順序放入,執行完畢從上往下退出。執行方法叫做壓棧,結束方法叫做出棧(銷燬棧幀)。
棧幀是虛擬機棧的單位,一個方法一個棧幀,一個棧幀分爲四部分:
- 局部變量表:存放方法參數或內部定義的變量列表,比如對象;
- 操作數棧:在執行方法的時候使用;
- 動態鏈接:和上面的解析一樣,將符號引用轉換爲直接引用
- 返回地址:不論方法成功失敗,返回方法被調用的地址
棧溢出
當棧的深度大於虛擬機,報StackOverflowError,-Xss可設置大小
內存溢出
當棧需要擴展而無法申請空間,報OutOfMemoryError
本地方法棧
和虛擬機棧類似。虛擬機棧爲JVM執行字節碼服務,本地方法棧爲本地(native)方法服務。
- Sun HotSpot將虛擬機棧和本地方法棧合併;
- 有StackOverflowError和OutOfMemoryError;
方法區(元空間)
加載類的時候,將類的一些元數據信息保存在這裏,比如變量,方法。方法區是線程共享的。
- jdk1.7合併方法區到堆裏
- jdk1.8保留方法區,稱之爲元空間
- HotSpot虛擬機中稱之爲永久代
- 存在OutOfMemoryError,可以通過-XX:MaxPermSize設置大小
堆
用於存放所有實例化對象和數組,是JVM分配內存最大的區域,在虛擬機啓動時創建,用垃圾回收器管理,分爲新生代(1/3)和老年代(2/3)。
新生代還可以分爲Eden、From Survivor、To Survivor空間,比例爲8:1:1,通過-Xmx,-Xms設置大小
新生代
新生代大部分對象不需要太長的存活時間,會頻繁觸發Minor GC進行回收
老年代
老年代存放的是存活時間較久或內存較大的對象,不會頻繁執行Full GC
Minor GC
新生代的垃圾回收機制,採用複製算法(掃描存活對象,複製到新內存區域)。
發生時,Eden區和一個Survivor區把存活的對象放到另一個Survivor區,然後清理自己。當達到一定年齡以後,把對象放入老年代。每發生一次Minor GC增加一歲,默認15歲。
Full GC
老年代的垃圾回收機制,採用標記清除(標記存活對象,清除未標記對象)。
發生的頻率比Minor GC低,但一次的時間會更長。