高級Java知識

高級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算法

  1. Mark-sweep 標記清除。標記須要回收的對象,標記完後贊成清除。不足:標記和清除效率都不高;清除後產生大量零碎、不連續內存空間。
  2. copying 複製。 Eden空間+兩塊Survivor空間。Eden:Survivor=8:1,回收eden,將其中存活對象複製到survivor空間,清除eden。缺點:若是對象存活率高,則致使過多複製而下降gc性能;因爲10%的survivor空間不能保證必定能容納全部存活對象,須要額外空間保證。
  3. mark-compact 標記-整理。老年代中對象存活率很高,複製算法致使過多複製以及額外空間。標記-整理算法在gc時將存活對象整理到內存空間的一端連續存放,直接清理另外一端內存。
  4. Generational Collection 分代收集。將內存分爲新生代和老年代,根據特色使用copying、mark-sweep/mark-compact算法。

GC器:

  1. serial。 單線程,新生代用copying、老年代用mark-compact算法,準備收集時會「stop the world」。
  2. ParNew。serial的多線程版
  3. Parallel Scavenge。新生代收集器。關注點是可控的吞吐量(user-time / (user-time + gc-time)),而CMS等GC器關注GC時致使的停頓時間。該GC器可自適應調節新生代大小(—Xmn)、eden與survivor比例(_XX:SurvivorRatio)、晉升老年代年齡(-XX:PretenureSizeThreshold)。
  4. Serial Old。Serial的老年代版本。
  5. Parallel Old。Parallel Scavenge的老年代版本,使用mark-compact算法。
  6. CMS(Concurrent Mark Sweep)。併發GC、低停頓時間。 a. 初始標記(gc roots直接關聯的對象,stop the world) b. 併發標記(gc roots tracing,與用戶線程同時工做) c. 從新標記(併發標記期間產生的變更,stop the world) d. 併發清除。
  7. 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

相關文章
相關標籤/搜索