類加載
什么是類加載,發(fā)生在什么時期(編譯/運行)
加載
1.1 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。
1.2 將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數據結構。
1.3 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數據的訪問入口。
驗證
準備
正式為類變量(靜態(tài)變量)分配內存并設置類變量初始值的階段,這些變量所使用的內存都將在方法區(qū)中進行分配。--默認值
解析
虛擬機將常量池內的符號引用替換為直接引用的過程(靜態(tài)鏈接,對應的有動態(tài)鏈接)
直接引用:直接引用可以是直接指向目標的指針、相對偏移量、或一個能間接定位到目標的句柄
初始化
在準備階段,變量已經賦值過一次系統(tǒng)初始化值。在初始化階段,則是根據程序中的賦值語句主動為類變量和其他資源賦值,或者說初始化階段是在執(zhí)行類構造器<clinit>()方法的過程。
加載-連接(驗證-準備-解析)-初始化
初始化-clinit解析
static變量代碼塊順序執(zhí)行,子類->觸發(fā)父類,父類靜態(tài)代碼塊優(yōu)先于子類執(zhí)行,接口不會觸發(fā)父接口靜態(tài)變量,實現類不會觸發(fā)接口靜態(tài)變量
什么時候會立即觸發(fā)初始化/主動引用一個類
new初始化,static方法調用,反射調用,子類->觸發(fā)父類,main方法主類
靜態(tài)變量,系統(tǒng)初始化值與賦值是在什么時候
準備-初始化
什么是類加載器
類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節(jié)流”這個動作放到java虛擬機外部去實現,以便讓應用程序可以自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類加載器”
什么是雙親委派機制?雙親委派機制時機?好處?
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的加載器都是如此,因此所有的請求最終都應該傳送到頂層的啟動類加載器 BootstrapClassLoader中。當父類加載器反饋無法處理這個加載請求,子加載器才會嘗試自己去加載。
時機:加載階段
好處:Java類具備了一種帶有優(yōu)先級的層級關系
沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被隨意篡改
避免類的重復加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性

自己實現雙親委派
實現邏輯:jdk1.2后已不提倡用戶去覆蓋loadClass()方法,而應當把自己的類加載邏輯寫到findClass()方法中,在loadClass方法的邏輯里如果父類加載失敗,則調用自己的findClass的方法來完成加載。這樣可以保證新寫出來的類加載器是符號雙親委派規(guī)則的。
打破雙親委派
典型的例子JNDI服務(對資源及性能集中管理和查找),它需要調用 由獨立廠商實現并部署在應用程序的classpath下的JNDI接口提供者(SPI)的代碼,但是啟動類加載器不可能認識這些代碼。
為了解決這個問題,java設計了“線程上下文類加載器(Thread Context ClassLoader)”。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法及性能設置,如果線程創(chuàng)建時還未設置,他將會從父線程中繼承一個,如果在應用程序的全局范圍內都沒有設置過的話,那這個類加載器默認就是程序類加載器。
JNDI服務使用線程上下文類加載器去加載需要的SPI代碼,也就是父類加載器其請求子類加載器去完成類加載的動作。java設計SPI的加載動作基本采用這種方式,如JDBC、JNDI、JCE、JAXB、JBI等。
關于JDBC打破雙親委派模型
參考:http://www.itdecent.cn/p/09f73af48a98
http://www.itdecent.cn/p/78f5e2103048
java內存區(qū)域
程序計數器
本地方法棧:提供native方法服務
虛擬機棧-棧幀:局部變量表、動態(tài)連接,返回地址
堆-逃逸分析、新生代老年代、垃圾回收
方法區(qū)-元空間/永久代;
永久代:靜態(tài)變量+常量池轉移到堆中,類的元信息轉移到元空間,靜態(tài)變量邏輯上屬于方法區(qū),物理上屬于堆
MetaspaceSize 表示的并非是元空間的大小,它的含義是:主要控制matesaceGC發(fā)生的初始閾值,也就是最小閾值。也就是說當使用的matespace空間到達了MetaspaceSize的時候,就會觸發(fā)Metaspace的GC,也就是fullGC。
動態(tài)鏈接:符號引用轉直接引用,每個棧幀包含一個指向運行時常量池中該棧幀所屬方法的引用(運行期間完成),同類加載時解析階段靜態(tài)鏈接-符號引用轉直接引用
靜態(tài)鏈接:將一些符號引用(靜態(tài)方法)替換為指向數據內存的指針或句柄(類加載解析期間完成)
逃逸分析
方法逃逸,線程逃逸,從不逃逸
分析對象動態(tài)作用域,判斷對象逃逸級別
棧上分配
不會逃逸線程外,可考慮棧上分配;對象所占用的內存可以隨棧楨出棧而被摧毀;HotSpot不支持
標量替換
不會逃逸出方法范圍外,可考慮標量替換;把一個java對象拆散,根據程序訪問的情況,將其用到的成員變量恢復為原始類型來訪問
同步消除
不會逃逸出線程外,代表對象非共享,即可以將同步鎖消除掉
對象
對象的創(chuàng)建過程
1.定位符號引用與加載 :當Java虛擬機遇到一條字節(jié)碼new指令時,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化。
2.分配內存(指針/空閑列表) :類加載檢查通過后,虛擬機將會為新生對象分配內存
3.對象頭設置:如這個對象是哪個類的實例,如何才能找到類的元數據信息、對象的GC分代年齡信息等。
對象的內存布局
1.對象頭:Mark Word,元數據類型指針,數組)
2.實例數據 :在程序代碼里定義的各種類型的字段內容
3.對齊填充
對象的訪問定位
句柄:本地變量表->句柄池(到對象實例的指針/元數據指針)->對象地址引用/元數據引用
直接指針:本地變量表->堆對象地址引用->元數據引用
HotSpot選擇指針,主要由于java對象訪問非常頻繁,節(jié)約開銷
垃圾回收
哪些內存需要回收
方法區(qū),堆:共享,動態(tài),需要回收
程序計數器,本地方法棧,虛擬機棧:線程級別,不需要回收
垃圾對象判定-可達性分析-GCRoot、引用計數法
引用計數法:相互引用沒辦法處理
可達性分析:判斷一個對象到GC Roots 沒有任何引用鏈
GC Roots:當前棧幀引用的,類靜態(tài)屬性引用的,常量引用的對象,同步鎖持有的
Native方法的引用對象,java虛擬機內部的引用,如基本數據類型對應的Class對象,一些常駐的異常對象(如NullPointException,OutOfMemoryError)等,還有系統(tǒng)類加載器
對象引用
強引用:永遠不會回收。
軟引用:內存溢出之前進行回收。
弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收之前。
虛引用:任何時候都可能被垃圾回收器回收。
方法區(qū)回收--內容,判定方式
內容:廢棄的常量,不再使用的類型(無用的類)
判定方式:1.該類所有的實例都已經被回收,也就是 Java 堆中不存在該類的任何實例。
2.加載該類的 ClassLoader 已經被回收。
3.該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾回收算法--復制、標記清除、標記整理
復制:只可使用一半內存
標記清除:標記+清除;碎片化問題,GC速度快;
標記整理:標記+移動+清除;內存規(guī)整,對整體吞吐友善,GC速度慢;
標記清除與標記整理對比
標記清除:標記+清除;碎片化問題,
標記整理:標記+移動+清除;
標記STW,移動STW
垃圾收集器
| 收集器 | 特點 |
|---|---|
| serial | 單線程 新生代 復制 |
| parnew | 多線程并行 新生代 復制 |
| parallel scavenge | 多線程并行 新生代 復制 注重吞吐量 |
| serial old | 單線程 老生代 標記整理 |
| parallel old | 多線程并行 老生代 標記整理 |
| cms | 多線程并發(fā) 老年代 標記清除 最短停頓時間 (初始標記+并發(fā)標記+重新標記+并發(fā)清理) 兩次stw(初始階段+重新標記) 初始標記簡單標記1次stw,重新標記解決并發(fā)標記時程序運作時產生的垃圾記錄 缺點:1.cpu敏感,占用cpu線程=(cpu數量+3)/4 2.cms無法處理浮動垃圾,預留空間92%如果不足出現并發(fā)失敗concurrent mode failure導致fullGC,啟動serialold,原因:并發(fā)清理用戶程序運行產生新的垃圾>8% 3.標記-清除,產生不連續(xù)空間碎片,導致提前fullGC |
| g1 | 多線程并發(fā)+并行 老年代+新生代 全局標記整理,局部復制(無碎片化問題) 并行并發(fā),分代收集,空間整合,可預測停頓 younggc動態(tài)調節(jié) mixedgc(初始標記+并發(fā)標記+最終標記+篩選回收) 化整為零 |
內存分配與回收策略
優(yōu)先eden,大對象老年代,15次存活老年代,空間擔保機制計算老年代空間是否足夠
cms
初始標記(initial mark,STW): 暫停所有的其他線程,僅僅標記下gc roots直接能關聯到的對象,速度很快 ;
并發(fā)標記(concurrent mark): 從GC Roots的直接聯系對象開始遍歷整個對象圖的過程,這個過程耗時較長,但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)進行。
重新標記(remark, STW): 重新標記階段就是為了修正并發(fā)標記期間,因為用戶程序繼續(xù)運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比并發(fā)標記階段時間短
并發(fā)清理(concurrent sweep): 清理刪除掉標記階段判斷的已經死亡的對象,由于不需要移動存活對象,所以這個階段也是可以與用戶線程同時并發(fā)的。
g1
整堆分成了2048個region,每個region可能是老年代/新生代
其他垃圾收集器,是整堆收集(新生代/老年代),g1面向局部
提供可停頓時間預測,能收集多少region收集多少region,對region進行優(yōu)先級劃分
對象創(chuàng)建->g1指定一個region作為eden,eden滿了之后不會立即觸發(fā)minorgc,會預測當前region收集時間與預測時間做對比,如果遠遠小于預測時間,擴容eden region數量,知道eden滿了且預測時間接近,觸發(fā)minor GC,或eden達到整堆空間60%,觸發(fā)minor GC->
minorGC后,eden轉到survier,多次后轉到old->old達到閾值45%后(當對象大小達到region大小一半,將其放入Humongous region,其也屬于old),觸發(fā)mixed GC->mixedGC)
初始標記stw(優(yōu)化:借助于minorGC標記的GC root),并發(fā)標記,最終標記,篩選回收stw(年輕代全部回收,minorgc回收只收集eden+servier,mixed會收集年輕代+老年代,region全是垃圾對象被收集,部分垃圾的region按效率看情況收集,會篩選8次,整個回收的region集合稱為cset)
region局部復制算法,整體標記整理,region空間不足觸發(fā)fullGC
rset跨region引用,從年輕代指向老年代
什么時候觸發(fā)fullgc
老年代空間不足
調用 System.gc()
空間擔保機制失敗(1每次晉升到老年代對象平均大小 > 老年代剩余空間2 MinorGC后存活對象超過了老年代剩余空間)
元空間MetaspaceSize達到閾值
jdk7永久代滿
什么時候觸發(fā)stw
標記移動-觸發(fā)stw
cms:初始標記,最終標記
g1:初始標記,最終標記,篩選回收(涉及移動)
jvm工具
jstat jstack jmap jvisual
heapdump
threaddump
內存泄露,頻繁fullgc問題