JVM有哪些分區(qū)盗迟?程序計數(shù)器坤邪,java虛擬機棧,本地方法棧罚缕,堆艇纺,方法區(qū)。
java棧中存放的是一個個棧幀邮弹,每一個棧幀對應(yīng)每一個調(diào)用的方法黔衡,棧幀包括局部變量表,操作數(shù)棧方法的返回地址腌乡,當前方法所屬類的運行常量池的引用盟劫,附加信息。方法區(qū)有運行常量池与纽。.程序計數(shù)器是唯一一個在Java 虛擬機規(guī)范中沒有規(guī)定任何oom 情況的區(qū)域侣签。
2.在java 虛擬機規(guī)范中,對于java 虛擬機棧急迂,規(guī)定了2 中異常影所,1)若線程請求的棧深度> 虛擬機所允許的深度,則拋出Stack Overflowerror 異常2)若虛擬機可以動態(tài)擴展僚碎,若擴展時無法申請到足夠的內(nèi)存空間猴娩,則拋出oom 異常。
[if !supportLists]3.?[endif]java?虛擬機棧為執(zhí)行java?方法勺阐,本地方法棧為虛擬機使用native?方法服務(wù)卷中,本地方法棧也會拋出Stack?Overflowerror?和oom。
[if !supportLists]4.?[endif]Java?堆可以處于物理上連續(xù)的內(nèi)存空間渊抽,只要邏輯上是連續(xù)的即可仓坞。可固定腰吟,可擴展无埃。
若堆中沒有內(nèi)存完成實例分配徙瓶,并且堆也無法再擴展,則會拋出oom嫉称。
5.直接內(nèi)存不是運行時數(shù)據(jù)區(qū)的一部分侦镇。堆上的OOM 測試
垃圾回收:
對象是否存活
1)引用計數(shù)法缺點:很難解決對象之間循環(huán)引用的問題。
2)可達性分析法基本思想:通過一系列的稱為“GC roots”的對象作為起始點织阅,從這些節(jié)點壳繁,開始向下搜索,搜索所走過的路徑稱為引用鏈荔棉,當一個對象到GC root 沒有任何引用鏈相連(用圖論的話來說闹炉,就是從GC roots 到這個對象不可達),則證明此對象是不可用的润樱。
可作為GC roots 的對象
1)java 虛擬機棧(棧幀中的本地變量表)中引用的對象
2)方法區(qū)中類的靜態(tài)屬性引用的對象
3)方法區(qū)中常量引用的對象
4)本地方法棧中JNI 引用的對象
引用強度強引用>軟引用>弱引用>虛引用
任何一個對象的finalize()方法都只會被系統(tǒng)調(diào)用一次渣触。
若對象在進行可達性分析后發(fā)現(xiàn)沒有與GC roots 相連接的引用鏈,那么他將會被第一次標記并進行一次篩選壹若,篩選的條件是該對象是否有必要執(zhí)行finalize()方法嗅钻,當對象沒有重寫
finalize()方法或者finalize()方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為沒必要執(zhí)行店展。
若該對象被判定為有必要執(zhí)行finalize 方法养篓,則這個對象會被放在一個F-Queue 隊列,finalize 方法是對象逃脫死亡命運的最后一次機會赂蕴,稍后GC 將對F-queue 中的對象進行第二次小規(guī)模的標記柳弄,若對象要在finalize 中成功拯救自己—只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可,那么在第二次標記時他們將會被移出“即將回收”集合概说。
Finalize 方法不是c 或c++的析構(gòu)函數(shù)语御。
停止-復(fù)制算法:它將可用內(nèi)存按照容量劃分為大小相等的兩塊,每次只使用其中一塊席怪。當這一塊的內(nèi)存用完了应闯,則就將還存活的對象復(fù)制到另一塊上面,然后再把已經(jīng)使用過的內(nèi)存空間一次清理掉挂捻。商業(yè)虛擬機:將內(nèi)存分為一塊較大的eden?空間和兩塊較小的survivor 空間碉纺,默認比例是8:1:1,即每次新生代中可用內(nèi)存空間為整個新生代容量的90%刻撒,每次使用eden?和其中一個survivour骨田。當回收時,將eden?和survivor?中還存活的對象一次性復(fù)制到另外一塊survivor?上声怔,最后清理掉eden?和剛才用過的survivor态贤,若另外一塊survivor?空間沒有足夠內(nèi)存空間存放上次新生代收集下來的存活對象時,這些對象將直接通過分配擔(dān)保機制進入老年代醋火。
標記-清除算法:缺點1)產(chǎn)生大量不連續(xù)的內(nèi)存碎片2)標記和清除效率都不高
標記-清理算法:標記過程和“標記-清除”算法一樣悠汽,但后續(xù)步驟不是直接對可回收對象進行清除箱吕,而是讓all 存活對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存柿冲。
分代收集:新生代?停止-復(fù)制算法 老年代?標記-清理或標記-清除垃圾收集器茬高,前?3?個是新生代,后?3?個是老年代
[if !supportLists]1)?[endif]serial?收集器:單線程(單線程的意義不僅僅說明它會使用一個cpu?or?一條垃圾收集
線程去完成垃圾收集工作假抄,更重要的是在它進行垃圾收集的時候怎栽,必須暫停其他all 工作線程,直到他收集結(jié)束)宿饱。對于運行在client 模式下的虛擬機來說是個很好的選擇熏瞄。停止-復(fù)制
[if !supportLists]2)?[endif]parNew?搜集器:serial?收集器的單線程版本,是許多運行在server?模式下的虛擬機首選的新生代收集器谬以。停止-復(fù)制
[if !supportLists]3)?[endif]parallel?scaverge:目標達到一個可控制的吞吐量强饮,適合在后臺運算,沒有太多的交互蛉签。停止-復(fù)制。
[if !supportLists]4)?[endif]serial?old:serial?的老年代版本沥寥,單線程碍舍,標記-清理
[if !supportLists]5)?[endif]parallel ???old:parallel ???scaverge ???老年代的版本,多線程?標記-清理6)cms?收集器:一種以獲取最短回收停頓時間為目標的收集器?“標記-清除”邑雅,有?4?個過程初始標記(查找直接與?gc?roots?鏈接的對象)?并發(fā)標記(tracing?過程)?重新標記(因為并發(fā)標記時有用戶線程在執(zhí)行片橡,標記結(jié)果可能有變化)?并發(fā)清除 其中初始標記和重新標記階段,要“stop?the?world”(停止工作線程)淮野。優(yōu)點:并發(fā)收集捧书,低停頓?缺點:1)不能處理浮動垃圾?2)對?cpu?資源敏感?3)產(chǎn)生大量內(nèi)存碎片?{每一天具體的詳解請看《深入理解?Java?虛擬機:JVM?高級特性與最佳實踐》}
對象的分配:1)大多數(shù)情況下,對象在新生代eden 區(qū)中分配骤星,當Eden 區(qū)中沒有足夠的
內(nèi)存空間進行分配時经瓷,虛擬機將發(fā)起一次minor GC?{minor gc:發(fā)生在新生代的垃圾收集動作,非常頻繁洞难,一般回收速度也比較快full gc:發(fā)生在老年代的gc} 2)大對象直接進入老年代3)長期存活的對象將進入老年代4)若在survivor 空間中相同年齡all 對象大小的總和>survivor 空間的一半舆吮,則年齡>= 改年齡的對象直接進入老年代,無須等到
MaxTeuringThreshold(默認為15)中的要求队贱。
空間分配擔(dān)保
在發(fā)生minor?gc?前色冀,虛擬機會檢測老年代最大可用的連續(xù)空間是否>新生代all?對象總空間,若這個條件成立柱嫌,那么minor?gc?可以確保是安全的锋恬。若不成立,則虛擬機會查看
HandlePromotionFailure????設(shè)置值是否允許擔(dān)保失敗编丘。若允許与学,那么會繼續(xù)檢測老年代最大可用的連續(xù)空間是否>歷次晉升到老年代對象的平均大小彤悔。若大于,則將嘗試進行一次minor
gc癣防,盡管這次minor gc 是有風(fēng)險的蜗巧。若小于或HandlePromotionFailure 設(shè)置不允許冒險,則這時要改為進行一次full gc蕾盯。
類加載
類從加載到虛擬機內(nèi)存中開始幕屹,到卸載出內(nèi)存為止,它的整個生命周期包括:加載-驗證-準備-解析-初始化-使用-卸載级遭,其中驗證-準備-解析稱為鏈接望拖。
在遇到下列情況時,若沒有初始化挫鸽,則需要先觸發(fā)其初始化(加載-驗證-準備自然需要在此之前):
1)1.使用new 關(guān)鍵字實例化對象2.讀取或設(shè)置一個類的靜態(tài)字段3.調(diào)用一個類的靜態(tài)方法说敏。
2)使用java.lang.reflect 包的方法對類進行反射調(diào)用時,若類沒有進行初始化丢郊,則需要觸發(fā)其初始化
3)當初始化一個類時盔沫,若發(fā)現(xiàn)其父類還沒有進行初始化,則要先觸發(fā)其父類的初始
化枫匾。
4)當虛擬機啟動時架诞,用戶需要制定一個要執(zhí)行的主類(有main 方法的那個類),虛擬機會先初始化這個類干茉。
在加載階段谴忧,虛擬機需要完成下面3 件事:
1)通過一個類的全限定名獲取定義此類的二進制字節(jié)流;
2)將這個字節(jié)流所表示的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運行時數(shù)據(jù)結(jié)構(gòu)
3)在內(nèi)存中生成一個代表這個類的class 對象角虫,作為方法區(qū)的各種數(shù)據(jù)的訪問入口沾谓。
驗證的目的是為了確保clsss 文件的字節(jié)流中包含的信息符合當前虛擬機的要求,且不會危害虛擬機自身的安全戳鹅。驗證階段大致會完成下面4 個階段的檢驗動作:1)文件格式驗證2)元數(shù)據(jù)驗證3)字節(jié)碼驗證4)符號引用驗證{字節(jié)碼驗證將對類的方法進行校驗分析均驶,保證被校驗的方法在運行時不會做出危害虛擬機的事,一個類方法體的字節(jié)碼沒有通過字節(jié)碼驗證枫虏,那一定有問題辣恋,但若一個方法通過了驗證,也不能說明它一定安全}模软。
準備階段是正式為類變量分配內(nèi)存并設(shè)置變量的初始化值得階段伟骨,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。(不是實例變量燃异,且是初始值携狭,若public?static?int?a=123;準備階段后?a?的值為?0,而不是?123回俐,要在初始化之后才變?yōu)?123逛腿,但若被?final?修飾稀并,public?static final?int?a=123;在準備階段后就變?yōu)榱?23)
解析階段是虛擬機將常量池中的符號引用變?yōu)橹苯右玫倪^程。
靜態(tài)代碼塊只能訪問在靜態(tài)代碼塊之前的變量单默,在它之后的變量碘举,在前面的靜態(tài)代碼塊中可以復(fù)制,但是不可以使用搁廓。
通過一個類的全限定名來獲取定義此類的二進制字節(jié)流引颈,實現(xiàn)這個動作的代碼就是
“類加載器”。
比較兩個類是否相同境蜕,只有這兩個類是由同一個類加載器加載的前提下才有意義蝙场,否則即使這兩個類來源于同一個class 文件,被同一個虛擬機加載粱年,只要加載他們的加載器不同售滤,他們就是不同的類。
從Java 虛擬機的角度來說台诗,只存在兩種不同的類加載器:一種是啟動類加載器完箩,這個類加載器使用c++實現(xiàn),是虛擬機自身的一部分拉队。另一種就是所有其他的類加載器弊知,這些類加載器都由Java 實現(xiàn),且全部繼承自java.lang.ClassLoader氏仗。
從JAVA 開發(fā)人員角度吉捶,類加載器分為:
1)啟動類加載器夺鲜,這個加載器負責(zé)把\lib 目錄中或者–Xbootclasspath
下的類庫加載到虛擬機內(nèi)存中皆尔,啟動類加載器無法被Java 程序直接引用。
2)擴展類加載器:負責(zé)加載\lib\ext 下或者java.ext.dirs 系統(tǒng)變量指定路徑下all 類庫币励,開發(fā)者可以直接使用擴展類加載器慷蠕。
3)應(yīng)用程序類加載器,負責(zé)加載用戶路徑classpath 上指定的類庫食呻,開發(fā)者可以直接使用這個類加載器流炕,若應(yīng)用程序中沒有定義過自己的類加載器,一般情況下仅胞,這個就是程序中默認的類加載器每辟。
雙親委派模型:若一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類干旧,而是把所這個請求委派給父類加載器去完成渠欺,每一層的加載器都是如此,因此all 加載請求最終都應(yīng)該傳送到頂級的啟動類加載器椎眯。只有當父類加載器反饋自己無法加載時(他的搜索范圍中沒有找到所需的類)時挠将,子加載器才會嘗試自己去加載胳岂。
雙親委派模型好處:eg,object 類舔稀。它存放在rt.jar 中乳丰,無論哪個類加載器要加載這個類,最終都是委派給處于模型頂端的啟動類加載器加載内贮,因此object 類在程序的各種加載環(huán)境中都是同一個類产园。
線程安全與鎖優(yōu)化東西太多了,直接見《深入理解Java 虛擬機:JVM 高級特性與最佳實踐》最后一章贺归。
當運行一個程序時淆两,JVM 啟動,運行bootstrap classloader 拂酣,該classloader 加載核心API
(Ext?classloader?和app?classloader?也在此時被加載)秋冰,then?調(diào)用ext?classloader?加載擴展API,最后app?classloader?加載classpath?目錄下定義的class婶熬,這就是一個程序最基本的加載流程剑勾。
通過classloader?加載類實際上就是加載的時候并不對該類進行解析,因此也不會初始化赵颅,而class?類的forName?方法則相反虽另,使用forName?方法加載的時候會將class?進行解析與初始化。
Error?和exception?的區(qū)別饺谬?Error?類一般指與虛擬機相關(guān)的問題捂刺,比如系統(tǒng)崩潰,虛擬機錯誤募寨,內(nèi)存空間不足搂漠,對于這種錯誤導(dǎo)致的應(yīng)用程序中斷列肢,僅靠程序本身無法恢復(fù)和預(yù)防欧聘,遇到這樣的錯誤爱谁,建議讓程序終止衣盾。Exception?表示程序可以處理的異常果复,遇到這類異常迈窟,應(yīng)該盡可能處理異常湖员,使程序恢復(fù)運行晰筛,而不應(yīng)該隨意終止異常怜瞒。
1)final 定義的常量漾橙,一旦初始化藕各,不能被修改誉碴,對基本類型來說是其值不可變逻杖,而對引用來說是其引用不可變。其初始化只能在兩個地方1.其定義處2.構(gòu)造函數(shù)中荸百,二者只能選其一闻伶,不能在定義時給了值,又在構(gòu)造函數(shù)中賦值够话。2)不管有無異常發(fā)生蓝翰,finally?總會執(zhí)行,若catch 中有return 語句女嘲,也執(zhí)行畜份,在return 語句之前執(zhí)行。3)一個類不能既被聲明為abstract欣尼,也被聲明為final 4)finalize 方法定義在object 中爆雹。
在Java 中,內(nèi)存泄漏就是存在一些被分配的對象,這些對象存在以下一些特點:1)對象是可達的钙态,即在有向圖中慧起,存在通路可以與其相連2)對象是無用的,即程序以后不會再使用這些對象册倒。這些對象不會被gc 所回收完慧,然而他們卻占用內(nèi)存。
發(fā)生內(nèi)存泄漏的第一個跡象通常是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryErroe(OOM)
內(nèi)存溢出:程序在申請內(nèi)存時剩失,沒有足夠的內(nèi)存空間供其使用屈尼。內(nèi)存泄漏:分配出去的內(nèi)存不再使用,但是無法回收拴孤。
JVM 中脾歧,一個字節(jié)以下的整形數(shù)據(jù)byte,-128 到127 會在jvm 啟動時加載入內(nèi)存演熟,除非用new Integer()顯示的創(chuàng)建對象鞭执,否則都是同一個對象。Integer 的value 方法返回一個對
象芒粹,先判斷傳入的參數(shù)是否在-128?到127?之間兄纺,若已經(jīng)存在引用,則直接返回引用化漆,否則返回new?Integer(n).
Integer?i1=59估脆;
Int i2=59;
Integer i3=Integer.valueOf(59);
Integer?i4=new?Integer(59); i1==i2?是true,因為只有一份座云;i1==i3?是true疙赠,因為59 在-128?到127?之間,因為內(nèi)存中已經(jīng)有了59?的引用朦拖,則直接返回圃阳;i3==i4?是false?;i4==i2 是true璧帝,因為i2?為int?類型捍岳,在和i4?比較時,i4?會自動拆箱睬隶。
Integer i=100锣夹;相當于編譯器為我們做了Integer?i=Integer.value(100).
Java 虛擬機里的對象由3 部分組成:1)對象頭:標記字段(在32 位和64 位jvm 中分別為32bits 和64bits)+類型指針2)實例數(shù)據(jù)3)對齊填充。
若對象是一個java 數(shù)組理疙,則對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)晕城。因為虛擬機可以通過普通java 對象的元數(shù)據(jù)信息確定java 對象的大小泞坦,但是從數(shù)組的元數(shù)據(jù)中無法確定數(shù)組的大小窖贤。
對象大小必須是8 字節(jié)的整數(shù)倍。
Xms:堆初始大小。Xmx:堆最大大小赃梧。
內(nèi)存溢出:程序在申請內(nèi)存時滤蝠,沒有足夠的空間供其使用。內(nèi)存泄漏:分配出的內(nèi)存不再使用授嘀,但無法回收物咳。
Xss:設(shè)置棧的大小。
標記-清除:首先標記出all 需要回收的對象蹄皱,在標記完成后統(tǒng)一回收all 被標記的對象览闰。
Cms:并發(fā)收集、低停頓巷折。
Gc?進行時必須停頓all?java?執(zhí)行線程(stop-the-world)压鉴,即使在號稱幾乎不會發(fā)生停頓的cms?收集器中,枚舉根節(jié)點時必須停頓锻拘。(因為不可以發(fā)生在分析過程中對象引用關(guān)系還在不斷變化)油吭。
在大多數(shù)情況下,對象在新生代eden 區(qū)中分配署拟,當eden 區(qū)域沒有足夠的內(nèi)存空間時婉宰,虛擬機發(fā)起一次minor gc。
Xmn:分給新生代的大小推穷。
虛擬機給每一個對象定義一個對象年齡計數(shù)器心包,若對象在eden 出生并經(jīng)過第一次minor
gc 后仍然存活,并且能被survivor 容納的話馒铃,將被移到survivor 空間中谴咸,并且對象年齡設(shè)為1.對象在survivor 中每熬過一次minor gc,年齡就+1骗露,當他年齡達到一定程度(默認為15)岭佳,就會晉升到老年代。
虛擬機可以從方法表中的acc_synchronized 訪問標志得知一個方法是否為同步方法萧锉。當方法調(diào)用時珊随,調(diào)用指令將會檢查方法的acc_synchronized?訪問標志是否被設(shè)置了。若被設(shè)置了柿隙,執(zhí)行線程就要求先成功持有moniter叶洞,然后才能執(zhí)行方法,最后當方法完成(無論是正
常完成還是非正常完成)時釋放moniter禀崖。在方法執(zhí)行期間衩辟,執(zhí)行線程持有了moniter,其他任何線程都無法再獲取到同一個moniter波附,若一個同步方法執(zhí)行期間拋出了異常艺晴,并且在方法內(nèi)部無法處理此異常昼钻,那么這個方法所持有的moniter 將在異常執(zhí)行到同步方法之外時自動釋放。
加載封寞、驗證然评、準備、初始化卸載這5 個階段的順序是確定的狈究。
到初始化的階段才真正開始執(zhí)行類中定義的Java 程序代碼(或者說是字節(jié)碼)碗淌。初始化階段是執(zhí)行類的構(gòu)造器(())方法的過程。
()方法是由編譯器自動收集類的all 類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的抖锥。編譯器收集的順序是由語義在源文件中出現(xiàn)的順序決定的亿眠。
()方法與類的構(gòu)造函數(shù)不同,他不需要顯示的調(diào)用父類的構(gòu)造器磅废,虛擬機會保證在子類的()方法執(zhí)行之前缕探,父類的()方法已經(jīng)執(zhí)行完畢。因此在虛擬機中第一個被執(zhí)行的()方法的類肯定是java.lang.object.
由于父類的()方法先執(zhí)行还蹲,也就意味著父類中的靜態(tài)語句塊要優(yōu)于子類的變量賦值爹耗。
接口中,不能有靜態(tài)代碼塊谜喊。
虛擬機會保證一個類的()方法在多線程環(huán)境中被正確的加鎖同步潭兽。若多個線程同時去初始化一個類,那么只會有一個線程去執(zhí)行這個類的()方法斗遏,其他線程都要阻塞等待山卦,直到活動線程執(zhí)行()完畢。
若自己編寫一個類java.lang.Object 并放在classpath 下诵次,可以正常編譯账蓉,但永遠無法被加載。
若有一個類加載請求逾一,先檢查是否已經(jīng)被加載铸本,若沒有被加載,則調(diào)用父類加載器的
loadclass????方法遵堵,若父類加載器為空箱玷,則默認使用啟動類加載器作為父類加載器,若父類加載失敗陌宿,拋出classNotFoundException锡足,再調(diào)用自己的findclass 方法進行加載。
語法糖壳坪,在計算機語言中添加的某種語法舶得。這種語法對語言的功能并沒有影響,但是更方便程序員使用爽蝴。java 中最常用的語法糖有:泛型沐批、變長參數(shù)纫骑,自動拆箱/裝箱。虛擬機運行時不支持這些語法珠插,他們在編譯階段還原回簡單的基礎(chǔ)語法結(jié)構(gòu)惧磺,這個過程為解語法糖颖对。包裝類的“==”運算捻撑,在不遇到算術(shù)運算的情況下,不會自動拆箱缤底,遇到了就自動拆箱顾患。
java 中的運算并非原子操作,導(dǎo)致volatile 變量的運算在并發(fā)下并不一定安全个唧。
只有使用invokespecial?指令調(diào)用的私有方法江解,實例構(gòu)造器,父類方法徙歼,以及invokestatic 指令調(diào)用的靜態(tài)方法犁河,才是在編譯期進行解析的,除了上述四種方法魄梯,其他java?方法調(diào)用都需要在運行期進行方法接收者的多態(tài)選擇桨螺,并且很有可能存在多于一個版本的方法接收者
(最多再除去被final 修飾的方法,盡管它用invokevirtual 調(diào)用酿秸,但也是非虛方法)灭翔,java 中默認的實例方法是虛方法。
每次使用use volatile 變量之前辣苏,都必須先從主內(nèi)存刷新最新的值肝箱,用于保證能看見其他線程對該變量所做的修改后的值。在工作內(nèi)存中稀蟋,每次修改volatile 變量后煌张,都必須立刻同步回主內(nèi)存中,用于保證其他線程可以看到自己對該變量所做的修改退客。volatile?修飾的變
量不會被指令重排序優(yōu)化唱矛,保證代碼的執(zhí)行順序與程序順序相同。原子性井辜、可見性绎谦、有序性。
Happen-before:對于一個volatile 變量的寫操作粥脚,先行發(fā)生于后面對這個變量的讀操作窃肠。
各個線程可以共享進程資源(內(nèi)存地址、文件Io)刷允,也可以獨立調(diào)度冤留。線程是CPU 調(diào)度的基本單位碧囊。
synchronized 關(guān)鍵字經(jīng)過編譯后,會在同步塊的前后分別形成moniterenter 和mointerexit 這兩個字節(jié)碼指令纤怒,這兩個字節(jié)碼指令都需要一個reference 類型的參數(shù)來指明要鎖定和解鎖的對象糯而。若java 程序中的synchronized 明確指定了對象參數(shù),那就是這個對象的reference泊窘;若沒有明確指定熄驼,那就根據(jù)synchronized 修飾的是實例方法或類方法去取對應(yīng)的對象實例或
class 對象來作為搜索對象。
在執(zhí)行moniterenter 指令時烘豹,首先要嘗試獲取對象的鎖瓜贾,若這個對象沒被鎖定,或當前線程已經(jīng)擁有那個對象的鎖携悯,把鎖的計數(shù)器加一祭芦。相應(yīng)的,在執(zhí)行mointerexit 指令時憔鬼,會將計數(shù)器-1龟劲,當計數(shù)器為零時,鎖被釋放轴或。若獲取對象鎖失敗昌跌,則當前線程就要阻塞等待,直到對象鎖被另外一個線程釋放侮叮。
對于線程的阻塞或喚醒避矢,需要用戶態(tài)和核心態(tài)切換,狀態(tài)切換需要很多處理器時間囊榜。
類只可以是public 或包訪問權(quán)限的审胸。組合:只需要將對象引用置入新類中。
字符串與任意數(shù)據(jù)類型相連接都用“+”卸勺,最終都變?yōu)樽址芭妗.O.P(a);調(diào)用的是?a??的
toString?方法∈锴螅“source”+aa碍庵;由于只能將一個?String?對象與另一個?String?對象相加,所以編譯器會調(diào)用?aa?的?toString?方法悟狱,將?aa?轉(zhuǎn)為?string?對象静浴。對于空指針?Person?p=null;S.O.P(p);不會報null?指針異常挤渐,而是直接打印null苹享;
java 內(nèi)存模型:工作內(nèi)存,主內(nèi)存浴麻。
Concurrenthashmap 和hashtable 不允許空鍵空值得问。
-xx:newRadio:設(shè)置?young?和?old?的比例囤攀;-xx:survivorRadio:設(shè)置?eden?和?survivor?的比例:
survivor 大了,會浪費空間宫纬,空間利用率低焚挠。若survivor 太小,會使一些大對象在minor gc
時直接從eden 區(qū)到old 區(qū)漓骚,讓old 區(qū)的gc 頻繁蝌衔。
Xmx:堆到最大值;
Xms:堆到初始值认境;
Xmn:年輕代的大信呶挟鸠;
Xss:棧的大小叉信。我在測試OOM 時,Xmx 和Xms 都設(shè)為20M艘希。硼身。
JVM關(guān)閉:1)正常關(guān)閉:當最后一個非守護線程結(jié)束或調(diào)用了System.exit或通過其他特定于平臺的方式,比如ctrl+c。2)強制關(guān)閉:調(diào)用Runtime.halt方法覆享,或在操作系統(tǒng)中直接kill(發(fā)送single信號)掉JVM進程佳遂。3)異常關(guān)閉:運行中遇到RuntimeException 異常等。?
在某些情況下撒顿,我們需要在JVM關(guān)閉時做一些掃尾的工作丑罪,比如刪除臨時文件、停止日志服務(wù)凤壁。為此JVM提供了關(guān)閉鉤子(shutdown hocks)來做這些事件吩屹。
Runtime類封裝java應(yīng)用運行時的環(huán)境,每個java應(yīng)用程序都有一個Runtime類實例拧抖,使用程序能與其運行環(huán)境相連煤搜。?
關(guān)閉鉤子本質(zhì)上是一個線程(也稱為hock線程),可以通過Runtime的addshutdownhock
(Thread hock)向主jvm注冊一個關(guān)閉鉤子唧席。hock線程在jvm正常關(guān)閉時執(zhí)行擦盾,強制關(guān)閉不執(zhí)行。?
對于在jvm 中注冊的多個關(guān)閉鉤子淌哟,他們會并發(fā)執(zhí)行迹卢,jvm 并不能保證他們的執(zhí)行順序。
JDBC:橋接模式
局部變量沒有默認值徒仓。
-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年輕代大小
-XXSurvivorRatio:年輕代中eden 和survivor 區(qū)的大小比值腐碱。
默認空余堆內(nèi)存小于40%時,JVM 就會增大堆到-Xmx 的最大值蓬衡;空余堆內(nèi)存大于70%
時喻杈,JVM 會減小堆到-Xms 的最小值彤枢。
《深入理解java 虛擬機》68 頁:1)對分配內(nèi)存空間的動作進行同步處理——采用cas 配上失敗重試的方式保證更新操作的原子性。2)把內(nèi)存分配的動作按照線程劃分在不同的空間上進行筒饰,每個線程在堆中預(yù)先分配1 小塊內(nèi)存缴啡,稱為本地線程分配緩沖(TLAB),哪個線程要分配內(nèi)存瓷们,就在哪個線程的TLAB 上分配业栅,只有TLAB 用完,要分配新的TLAB 時谬晕,才需要加鎖碘裕。
分為新生代和老年代。Java 堆物理上可不連續(xù)攒钳,只要邏輯上連續(xù)帮孔。
堆歸整(用了的在一邊,沒用的在另一邊)不撑。
在使用serial文兢、parnew 等帶compat 過程的收集器時,系統(tǒng)采用的分配方法是指針碰撞焕檬。而使用cms 這種基于mark-sweep 算法的收集器時姆坚,通常采用空閑列表。
CMS?收集器——目的:獲取最短的回收停頓時間实愚;基于標記-清除兼呵。初始標記,并發(fā)標記腊敲,重新標記击喂,并發(fā)清除。初始標記和重新標記這兩步需要“Stop?the?world”兔仰,初始標記只是標記GC?root?能直接關(guān)聯(lián)到的對象茫负,速度快。并發(fā)標記就是進行GC?root?tracing?的過程乎赴,而重新標記階段是為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導(dǎo)致標記產(chǎn)生變動的那一部分對象的標記記錄忍法。
并發(fā)標記和并發(fā)清除中,垃圾收集線程可以和用戶線程同時工作榕吼。并發(fā)收集饿序,低停頓。
缺點:1)對CPU 資源敏感羹蚣,cms 默認啟動的回收線程數(shù)是(cpu 數(shù)量+3)/4;2)無法處
理浮動垃圾原探,由于cms 并發(fā)清除階段,用戶線程還在繼續(xù)執(zhí)行,伴隨程序進行,還有新的垃圾產(chǎn)生咽弦,這一部分垃圾發(fā)生在標記之后徒蟆,cms 無法在當次收集時處理他們,只能留到下一次
gc型型。3)它基于標記-清除段审,產(chǎn)生大量內(nèi)存碎片,大對象分配困難闹蒜。
Offer寺枉、peek、pool 不拋異常绷落。
Start()和run()——1)start?方法啟動線程姥闪,真正實現(xiàn)多線程運行,通過調(diào)用Thread?類的
start?方法來啟動一個線程砌烁,這時此線程是處于就緒狀態(tài)筐喳,并沒有運行,若cpu?調(diào)度該線程往弓,則該線程就執(zhí)行run?方法疏唾;2)run?方法當做普通方法的方式調(diào)用蓄氧,程序要順序執(zhí)行函似,要等
run 方法執(zhí)行完畢,才可以執(zhí)行下面的代碼喉童,程序中只有主線程這一個線程(除了gc 線程)撇寞。
常量池——byte、short堂氯、int蔑担、long、char咽白、boolean 包裝類實現(xiàn)了的常量池(float啤握、double 沒有);
Integer i1=123晶框;Integer i2=123排抬;I1==i2;是true 的授段;Boolean b1=true蹲蒲;Booleanb2=true;b1==b2侵贵;是true 的届搁;
常量池主要用于存放兩大類常量:1)字面量、符號引用。字面量相當于java 語言層面常量的概念卡睦,符號引用包括類和接口的全限定名宴胧,字段名稱和描述名稱,方法名稱和描述符表锻。運行時常量池有動態(tài)性牺汤,java 語言并不要常量一定只有在編譯時產(chǎn)生,也就是并非預(yù)置
入class?文件中常量池的內(nèi)容才能放入常量池浩嫌,運行期間有新的常量也可放入池中檐迟,比如
String 的intern 方法。優(yōu):對象共享码耐,節(jié)省內(nèi)存空間追迟,節(jié)省運行時間。
單調(diào)椛龋——單調(diào)棧的維護是O(n)的時間復(fù)雜度敦间。所有元素只會進入棧一次,并且出棧后再也不會入棧束铭。性質(zhì):1)單調(diào)棧里元素有單調(diào)性廓块;2)元素加入棧前,會在棧頂端把破壞棧單調(diào)性的元素都刪除契沫;3)使用單調(diào)棿铮可以找到元素向左遍歷第一個比它小的元素,也就可以找到元素向左邊遍歷第一個比它大的元素懈万。
CMS:由于在垃圾收集階段用戶線程還在運行拴清,也就是還需要預(yù)留有足夠的內(nèi)存空間給用戶線程使用,因此cms 收集器不能像其他收集器一樣会通,等到老年代幾乎完全被填滿了再進行收集口予,需要預(yù)留一部分空間,提供并發(fā)收集時的線程序使用涕侈。Jdk1.5 中沪停,默認老年使用
68%后就會被激活cms,jdk1.6?變?yōu)?2%裳涛,要是cms?運行期間預(yù)留的內(nèi)存不夠木张,就會出現(xiàn)一次“concurrent?mode?failure”,這時?JVM?會啟動后備預(yù)案调违,臨時啟用?serial?收集器來重新進行老年代的垃圾收集窟哺,這樣停頓的時間就長了。
垃圾收集器是自動運行的技肩,一般情況下且轨,無須顯示的請求垃圾收集器浮声,調(diào)用System 類的gc 方法可以運行垃圾收集器,但這樣并不能保證立即回收指定對象旋奢。
1)垃圾收集器并不是一個獨立的平臺泳挥,他具有平臺依賴
2)一段程序可以建議垃圾回收執(zhí)行,但是不能強迫他執(zhí)行
3)當一個對象的all 引用都被置為null至朗,這個對象就可以變?yōu)槟鼙焕厥?/p>
調(diào)用System.gc()屉符,這是一個不確定的方法。Java 中并不保證每次調(diào)用該方法就一定能夠啟動垃圾收集锹引,他不過是會向JVM 發(fā)送這樣一個申請矗钟,到底是否真正執(zhí)行垃圾收集,一切都是未知數(shù)嫌变。
Java 虛擬機棧吨艇,每個方法對應(yīng)一個棧幀,存放局部變量表腾啥、操作數(shù)棧东涡、方法出口等信息。堆分為新生代和老年代倘待。Java 堆物理上可不連續(xù)疮跑,只要邏輯上連續(xù)。
堆歸整(用了的在一邊凸舵,沒用的在另一邊)祖娘。
在使用serial、parnew 等帶compat 過程的收集器時贞间,系統(tǒng)采用的分配方法是指針碰撞贿条。而使用cms 這種基于mark-sweep 算法的收集器時,通常采用空閑列表增热。
《深入理解java 虛擬機》68 頁:1)對分配內(nèi)存空間的動作進行同步處理——采用cas 配上失敗重試的方式保證更新操作的原子性。2)把內(nèi)存分配的動作按照線程劃分在不同的空間上進行胧辽,每個線程在堆中預(yù)先分配1 小塊內(nèi)存峻仇,稱為本地線程分配緩沖(TLAB),哪個線程要分配內(nèi)存邑商,就在哪個線程的TLAB 上分配摄咆,只有TLAB 用完,要分配新的TLAB 時人断,才需要加鎖吭从。
強引用:類似Object o=new Object(),只要強引用還在,則垃圾收集器永遠不會收集被引用的對象恶迈。軟引用:有但并非必須的對象涩金。Softreference 類實現(xiàn)軟引用。弱引用:用
weakreference 類實現(xiàn)。虛引用:用phantomreference 類實現(xiàn)步做。
程序執(zhí)行時并非在all 地方都能停頓下來開始gc副渴,只能在到達安全點才可以。使用wpmap 來得知哪些地方存放著對象引用全度。
Jps:列出正在運行的虛擬機進程煮剧。
Jstat:虛擬機的運行狀態(tài)信息。類加載将鸵、垃圾收集勉盅、運行期編譯狀況。
Jinfo:虛擬機參數(shù)顶掉。
Jmap:堆轉(zhuǎn)儲快照菇篡。Heapdump/dump 文件。
Jhat:分析Heapdump 文件一喘。
Jstack:用于生成虛擬機當時時刻的線程快照驱还。
Jconsole Visual VM
Memory analysis tool Support assistant
源文件通過編譯變?yōu)樽止?jié)碼文件。
任何一個class 文件都對應(yīng)著唯一一個類or 接口的定義信息凸克。
Class?文件為二進制字節(jié)流议蟆,以8?位字節(jié)為基礎(chǔ)。1)每個class?文件的頭4?個字節(jié)成為魔數(shù)萎战,他的唯一作用是確定這個文件是否為一個能被虛擬機接受的class?文件咐容。2)緊接著魔數(shù)的4?個字節(jié)是class?文件的版本號(5、6?字節(jié)為次版本號蚂维,7戳粒、8?字節(jié)為主版本號)。3)緊接著主次版本號的是常量池入口虫啥。4)緊接著兩個字節(jié)為訪問標志蔚约。5)類索引、父類索引涂籽、接口索引集合苹祟。6)字段表。7)方法表评雌。8)屬性表(java?程序方法體中的代碼經(jīng)過java?編譯后树枫,最終變?yōu)樽止?jié)碼指令存在code?屬性中)。
Java 虛擬機的操作碼只有一個字節(jié)景东。
讀屏障用于保證讀操作有序砂轻,屏障之前的讀操作一定會優(yōu)先于屏障之后的讀操作完成,寫操作不受影響斤吐。
優(yōu)化屏障用于限制編譯器的指令重排搔涝。通用屏障則對讀寫操作都有用厨喂。
Animal a=new Dog();Animal 是靜態(tài)類型,dog 是實例類型体谒。虛擬機(準確的說是編譯器)杯聚,在重載時是通過參數(shù)的靜態(tài)類型,而不是實例類型作為判斷依據(jù)抒痒。
所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作稱為靜態(tài)分派幌绍,靜態(tài)分派的典型應(yīng)用是方法重載。靜態(tài)分配發(fā)生在編譯階段故响,因此確定靜態(tài)分派的動作傀广,實際上不是由虛擬機執(zhí)行。
多態(tài)彩届,重寫伪冰,動態(tài)分派。
java 語言是一門靜態(tài)多分派樟蠕,動態(tài)單分派贮聂。
Java、c++都是靜態(tài)類型語言寨辩,即在編譯期進行類型檢查吓懈。在運行期進行類型檢查的是
動態(tài)類型語言。
運行時異常靡狞,就是只要代碼不運行到這一行耻警,就不會有問題。
JIT 編譯甸怕,在虛擬機內(nèi)部甘穿,把字節(jié)碼轉(zhuǎn)換為機器碼。
Java 編譯器是由java 語言寫的程序梢杭。編譯的三個過程:1)解析與填充符號表過程温兼。2)插入或注解處理器的注解處理過程3)分析與字節(jié)碼生成過程。
熱
點代碼需要即時編譯式曲,JIT
編譯妨托,判斷一段代碼是否是熱點代碼叫熱點探測。1)基于采樣的熱點探測:采用這種方法的虛擬機會周期性的檢查各個線程的棧頂吝羞,若發(fā)現(xiàn)某個或某些方法經(jīng)常出
現(xiàn)在棧頂济丘,則該方法為熱點方法2)基于計數(shù)器的熱點探測弟疆,采用這種方法的虛擬機會為每個方法建立計數(shù)器,統(tǒng)計方法的執(zhí)行次數(shù)前计,若執(zhí)行次數(shù)超過一次閾值均澳,則
認為是熱點方法恨溜。
即時編譯優(yōu)化技術(shù)1)公共子表達式消除:如果一個表達式E 已經(jīng)計算過了符衔,且從先前的計算到現(xiàn)在,E
中所有變量的值都沒有發(fā)生變化糟袁,則E
的這次出現(xiàn)就成了公共子表達式判族。對于這種表達式,沒有必要花時間再對他進行計算项戴,只需要直接用前面計算過的表達式結(jié)果替代E
即可2)數(shù)組邊界檢查消除3)方法內(nèi)聯(lián)4)逃逸分析形帮。
處理器的指令:比較并交換,cas 指令周叮。cas 指令需要三個操作數(shù)辩撑,分別為內(nèi)存位置(在
java?中,可以簡單理解為內(nèi)存地址仿耽,用v?表示)合冀、舊的預(yù)期值A(chǔ)?和新值B。cas?指令執(zhí)行時项贺,當且僅當V?符合舊的預(yù)期值A(chǔ)?時君躺,處理器用新值B?更新V?值,否則他就不更新开缎。但無論是否更新了V?值棕叫,都會返回舊的V?值。上述過程是一個原子過程啥箭。
cas 操作定義在unsafe 類中谍珊,但用戶程序不能直接調(diào)用unsafe 類,要通過其他java api急侥。比如整型原子類compareandset()和Increamentandget()等方法都使用了unsafe 類的cas 操作砌滞。
Increamentandget():以原子的方式將當前值加一,該方法在一個無限循環(huán)中坏怪,不斷嘗試把一個比當前值大一的值賦給自己贝润。若失敗了,則表明在compareandset?操作時已經(jīng)有了修改铝宵,于是再次循環(huán)進行下一次操作打掘,直到設(shè)置成功為止。
Cas 的邏輯漏洞鹏秋,若一個變量V 初次讀取的時候是A 值尊蚁,并且準備賦值的時候,檢查到他仍然為A?值侣夷,那我們就能說它的值沒被其他線程改變過嗎横朋?若在這段期間他的值曾被改變?yōu)锽 值,后來又被改變回A百拓,則cas 操作會誤認為它從來沒有被改變過琴锭,這個漏洞被稱為
ABA 問題晰甚。解決辦法:控制變量值的版本。
輕量級鎖决帖,在沒有多線程競爭的條件下厕九,減少傳統(tǒng)的重量解鎖使用操作系統(tǒng)互斥量帶來的性能消耗(使用cas 操作)。如果說輕量級鎖是在無競爭的情況下地回,用cas 操作消除同步使用的互斥量扁远,那么偏向鎖就是在無競爭的情況下把整個同步去掉,連cas 操作都不做了落君。
用遙控板(句柄)控操縱電視(對象)穿香,即使沒有電視機,遙控板也可以單獨存在绎速,即擁有一個句柄皮获,并不表示必須有一個對象同他聯(lián)系。
{
Int x=100;
{
Int x=1; //不合法纹冤,因為編譯器認為變量已經(jīng)被定義
}
}
幾個類通過編譯后就產(chǎn)生幾個字節(jié)碼文件
持久化java 堆溢出:使用cglib 技術(shù)直接操作字節(jié)碼運行洒宝,產(chǎn)生大量的動態(tài)類;年老代溢出:上萬次字符串處理萌京,創(chuàng)建上萬個對象或在一段代碼內(nèi)申請上百M 甚至上G 的內(nèi)存雁歌。