高級Java知識(JVM、字節碼、內存模型)
內存=方法區+棧空間+堆+程序計數器java
棧(stack)包括虛擬機棧(VM stack)和本地方法棧(native method stack)。算法
方法區(method area)內放置 類信息(Class)、常量、靜態變量、JIT編譯後代碼。 運行時常量池是方法區的一部分。 Class文件有一個常量池(Constant Pool Table),屬於Class的一部分。 運行時常量池(Runtime Constant Pool)屬於method area的一部分。數組
線程請求的棧容量大於VM容許的最大容量將引起StackOverflowError。多線程
JVM規範容許虛擬機棧(如Hotspot虛擬機容許本地方法棧納入VM棧)動態擴展,若果在擴展時申請不到更多的內存,將引起OutOfMemoryError。併發
堆上申請不到更多內存將引起OOM error。app
運行時常量池申請不到更多內存將引起OOM Error。jvm
直接內存(Direct Memory)?也可能致使OOM。工具
new 過程: 尋找new 參數(須要被new的類)是否在常量池中、是否被加載、解析、初始化。性能
指針碰撞(bump the pointer)是認爲內存是規整的,使用過的內存存在於(邏輯)連續的空間,用一個指針分割可用內存和已使用內存。優化
空閒列表(free list)是用列表記錄空閒內存塊。
對象頭信息(object header)主要分兩部分,一是類元數據指針(若是棧上reference經過句柄定位對象和類型數據,則沒有類元指針),另外一部分是對象hash、gc分代年齡、鎖等自身運行時信息,若是是數組,還有數組大小信息。
分配內存、設置object header,初始化對象<init>方法
對象在內存中分3塊區域:object header, instance data, padding.
棧上reference定位對象有兩種方式,一是句柄,二是直接指針。句柄是一種間接指針,該reference指向堆中句柄池中的一個句柄,句柄中存放對象實例數據指針和類型數據指針。直接指針則存放了包含類元數據指針的對象數據指針。
句柄定位方式優點在於對象在內存中被移動時(GC時被移動是很廣泛的)棧上reference保持不變。直接指針優點是訪問快。Sun HotSpot VM採用後者定位方式。
JVM參數:
- -Xss:棧大小
- -Xoss: native method stack,對Hotspot VM無效(其不區分vm stack、native method stack)
- -Xmx:堆最大內存量 -Xms:堆最小內存量
- -XX:PermSize=10M 永久代(位於方法區中)大小
- -XX:MaxPermSize=10M 永久代最大容量 jdk 1.7逐步去「永久代」,1.8徹底去除。上述vm參數無效。 jvm運行時檢測到的常常被拋出的jdk內置異常(NPE),可在其被拋出必定次數後優化其stacktrace爲空,經過jvm開關OmitStackTraceInFastThrow設置。
- -XX:+OmitStackTraceInFastThrow 打開優化。
- -XX:-OmitStackTraceInFastThrow 關閉優化。
- -verbose:class -XX:+TraceClassLoading
- -XX:+TraceClassUnLoading關於類加載、卸載信息的vm參數。
運行時JVM會對常常被拋出的JRE預約義異常進行輸出優化——將其stacktrace置爲空,減小性能損失,因此可能看到一個異常(如NPE)被拋出,但其堆棧信息爲空。
引用計數算法沒法解決循環引用問題,objA.x=objB; objB.y=objA;objA=null;objB=null; 因爲相互引用致使兩個計數都不爲0,但實際兩個都應該被GC。
可達性分析GC策略:能經過GC roots可達的對象是存活對象。 GC Roots包括如下對象:
- VM棧(棧幀中本地變量表)中的引用對象
- 方法區中靜態對象
- native方法中引用的對象
引用:強引用(Strong)、軟引用(soft)、弱引用(weak)、虛引用(phantom)。強度依次減弱。
軟引用:還有用但並不是必需。在內存不夠用(將要溢出前)纔會回收這些引用。
弱引用:弱引用不影響GC,但對象只有弱引用時會被正常GC。
虛引用:徹底不影響GC,惟一目的是在此對象被GC時能收到一條系統通知。
GC:GC roots不可達且有必要執行finalize()方法(對象重寫了finalize且沒有被vm調用過) -> 第一次標記,並放到F-Quene -> 低優先級執行F-Quene中對象的finalize(但不保證執行完該方法因其運行慢或死循環) -> finalize期間對象沒有將this指向其餘gc roots可達對象的字段:回收;不然不回收。
注:任一對象的finalize只被執行一次,即便再次面對GC。聽從建議:讓咱們忘了這個方法吧。
方法區的回收是針對無用常量和類。
GC算法:
- Mark-sweep 標記清除。標記須要回收的對象,標記完後贊成清除。不足:標記和清除效率都不高;清除後產生大量零碎、不連續內存空間。
- copying 複製。 Eden空間+兩塊Survivor空間。Eden:Survivor=8:1,回收eden,將其中存活對象複製到survivor空間,清除eden。缺點:若是對象存活率高,則致使過多複製而下降gc性能;因爲10%的survivor空間不能保證必定能容納全部存活對象,須要額外空間保證。
- mark-compact 標記-整理。老年代中對象存活率很高,複製算法致使過多複製以及額外空間。標記-整理算法在gc時將存活對象整理到內存空間的一端連續存放,直接清理另外一端內存。
- Generational Collection 分代收集。將內存分爲新生代和老年代,根據特色使用copying、mark-sweep/mark-compact算法。
GC器:
- serial。 單線程,新生代用copying、老年代用mark-compact算法,準備收集時會「stop the world」。
- ParNew。serial的多線程版
- Parallel Scavenge。新生代收集器。關注點是可控的吞吐量(user-time / (user-time + gc-time)),而CMS等GC器關注GC時致使的停頓時間。該GC器可自適應調節新生代大小(—Xmn)、eden與survivor比例(_XX:SurvivorRatio)、晉升老年代年齡(-XX:PretenureSizeThreshold)。
- Serial Old。Serial的老年代版本。
- Parallel Old。Parallel Scavenge的老年代版本,使用mark-compact算法。
- CMS(Concurrent Mark Sweep)。併發GC、低停頓時間。 a. 初始標記(gc roots直接關聯的對象,stop the world) b. 併發標記(gc roots tracing,與用戶線程同時工做) c. 從新標記(併發標記期間產生的變更,stop the world) d. 併發清除。
- G1(Garbage First)。優勢:a. 並行、併發;b. 分代收集;c. 內存碎片整理;d. 可控的停頓時間。思想:內存分爲大小相同的若干region,優先回收價值(得到的內存空間/所需時間)大的garbage。
JVM工具
jps -l
顯示jvm進程信息
jstack
[-F 強制輸出堆棧; -l 鎖信息; -m native方法堆棧] pid
VisualVM(jvisualvm): btrace插件,熱機跟蹤。jvisualvm在jdk9+後再也不默認提供。
(hotspot)JVM引擎會複用局部變量表slot,對GC影響的例子以下:
// VM參數: -verbose:gc void main(){ { byte[] mem=new byte[64*1024*1024]; } // int a=0; 無此代碼行時儘管mem已不能被訪問,但不會被回收,當啓用此代碼時因爲再次訪問了局部變量表,將致使$mem的內存被回收 System.gc(); }
方法正常完成退出、方法異常完成退出。後者須要異常處理器表幫助方法返回、繼續執行程序。
字節碼指令集
invokestatic: 調用靜態方法
invokespecial: <init>, private, 父類方法。
invokevirtual: 虛方法(除被invokestatic, invokespecial指令調用的方法即static, <init>, private, 父類方法的方法)及final方法。
invokeinterface:
invokedynamic:
非虛方法(static、<init>、private、父類方法、final方法)在類加載時便可被解析爲直接引用。final非虛方法由invokevirtual指令調用。
java是靜態多分派、動態單分派的語言。
類加載
編譯器會爲類生成<clinit>方法,接口的只有在常量初始化時纔會被生成。 類<clinit>執行前必定先執行父類<clinit>,不會執行類接口的<clinit>。 類只會被初始化一次,類<clinit>中(靜態初始化過程當中)應避免過長的執行時間,不然會致使阻塞與此類有關其餘線程。
類的Class<?>依賴於類加載器,不一樣類加載器加載同一份類字節碼獲得的Class<?>必定不同。兩個類的「相等」比較只有在類被同一個加載器加載的狀況下有意義(不然必定不相等),這裏的相等包括,Class<?>.equals, isAssignableFrom, inInstance, instanceof。
boostrap classloader <- ext classloader <- application classloader(system classloader) <- user classloader