最近在閱讀《深入理解JAVA虛擬機》彪杉,因為本人記性較差灭抑,而且這類書本身就比較難懂,更需要反復閱讀,所以采用的是慢讀+筆記的學習方式模闲,一處看不懂會反復地再閱讀建瘫。
本書是基于jdk7,目前的最新版本是jdk1.9(2017.9)
jdk1.x一般指的是開發(fā)版本尸折,jdkX是真正改動后的名字啰脚。
筆記是按照閱讀順序(目錄順序?)
第一部分:走進JAVA
概念:
1实夹、JVM(JAVA virtual machine):Java虛擬機橄浓,提供了字節(jié)碼文件(.class)的運行環(huán)境支持。JVM是一種用于計算設備的規(guī)范收擦,它是一個虛構出來的計算機贮配,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。引入Java語言虛擬機后塞赂,Java語言在不同平臺上運行時不需要重新編譯泪勒。
Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼)宴猾,就可以在多種平臺上不加修改地運行圆存。Java虛擬機在執(zhí)行字節(jié)碼時,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行仇哆。這就是Java的能夠“一次編譯沦辙,到處運行”的原因。
2讹剔、JRE(JAVA Runtime Environment):是支持JAVA程序運行的標準環(huán)境油讯,包含JAVA SE API子架,JAVA虛擬機延欠。運行JAVA程序所必須的環(huán)境的集合陌兑,包含JVM標準實現(xiàn)及Java核心類庫。
.jre判斷程序是否執(zhí)行結束的標準時:所有的前臺線程都執(zhí)行完畢由捎。
3兔综、JDK(Java Development Kit):是支持JAVA程序開發(fā)的最小環(huán)境,是Java 語言的軟件開發(fā)工具包(SDK)狞玛。包含JAVA語言软驰、JAVA虛擬機、JAVA API類庫心肪。
即 jdk(java開發(fā)工具)> jre(java運行時環(huán)境)> jvm(java虛擬機)
4锭亏、java一開始是sun公司,后來的oracle公司收購蒙畴。
5贰镣、現(xiàn)在主流的虛擬機有:HotSpot VM
第二部分:自動內(nèi)存管理機制
一呜象、運行時java虛擬機管理的數(shù)據(jù)區(qū)域:
1膳凝、程序計數(shù)器:該內(nèi)存區(qū)域是唯一一個在java虛擬機規(guī)范中沒規(guī)定任何OutOfMemoryError情況的區(qū)域碑隆。線程隔離。
每條線程有一個獨立的PC蹬音,且互不影響上煤,獨立存儲。是線程私有的內(nèi)存著淆。
用于選取下一條需要執(zhí)行的字節(jié)碼指令劫狠。
2、JAVA虛擬機棧:為虛擬機執(zhí)行java方法(也就是字節(jié)碼)服務
也是線程私有的內(nèi)存永部,生命周期同線程独泞。是線程隔離的。
描述JAVA方法執(zhí)行的內(nèi)存模型苔埋,每個方法調(diào)用時都創(chuàng)建一個棧幀懦砂,用于存儲信息。方法從調(diào)用到執(zhí)行完成组橄,意味一個棧幀在虛擬機棧中入棧到出棧荞膘。
一般JAVA內(nèi)存區(qū)的棧就是指虛擬機棧。
在java虛擬機規(guī)范中:對該區(qū)域規(guī)定2種異常狀況:
(1)線程請求的棧深度大于虛擬機所允許的深度玉工,拋出StackOverflowError異常羽资;
(2)虛擬機棧擴展時無法申請足夠內(nèi)存,拋出OutOfMemoryError異常
3遵班、本地方法棧(為虛擬機使用到的native方法服務)
可以自由實現(xiàn)屠升,或者有的JVM將虛擬機棧和方法棧合二為一。
在java虛擬機規(guī)范中:同樣拋出兩種異常狭郑。
棧區(qū):存方法局部變量
4腹暖、JAVA堆:所有線程共享的內(nèi)存區(qū)域
一般時JAVA虛擬機所管理的內(nèi)存中最大的一塊,被所有線程共享愿阐,在虛擬機啟動時創(chuàng)建微服。
目的:存放對象實例(所有對象實例都在這分配內(nèi)存)類對象!缨历!
JAVA堆時垃圾收集器管理的主要區(qū)域(常叫為GC堆以蕴,Garbage Collected Heap)
在java虛擬機規(guī)范中:可處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上是連續(xù)的即可辛孵。若在堆中沒有內(nèi)存完成實例分配丛肮,且堆也無法再擴展,拋出OutOfMemoryError異常魄缚。
5宝与、方法區(qū):所有線程共享的內(nèi)存區(qū)域焚廊,與堆一樣
用于存儲已被虛擬機加載的類信息、常量习劫、靜態(tài)變量咆瘟、即時編譯器變異后的代碼等數(shù)據(jù)。
在java虛擬機規(guī)范中:
不需要連續(xù)的內(nèi)存和可選擇固定大小或者可擴展
可選擇不是先垃圾收集(也常被成為永久代)诽里,但一般收集是必要的袒餐,不然會內(nèi)存泄漏...bug
當方法區(qū)無法滿足內(nèi)存分配需求時,拋出OutOfMemoryError異常
(1)運行時常量池:是方法區(qū)一部分
存放編譯期生成的各種字面量和符號引用谤狡,將在類加載后進入方法區(qū)的運行時常量池中存放灸眼。
沒有什么規(guī)定。
但具備動態(tài)性墓懂,常量不一定只有編譯期產(chǎn)生焰宣。同樣,當方法區(qū)無法滿足內(nèi)存分配需求時捕仔,拋出OutOfMemoryError異常
PS:直接內(nèi)存:不是虛擬機運行時數(shù)據(jù)區(qū)的一部分匕积,也不是虛擬機規(guī)范中定義的內(nèi)存區(qū)
但常使用,也可能導致OutOfMemoryError異常
總結B甙摹U⑻臁!
方法區(qū):線程共享斜做,類信息苞氮、常量、靜態(tài)變量瓤逼、即時編譯器編譯后的代碼等數(shù)據(jù)
(運行時常量池:方法區(qū)一部分笼吟,存類版本、字段霸旗、方法贷帮、接口等信息)
堆:線程共享,類對象诱告,new出來的對象撵枢。
棧:線程隔離宅倒,存放方法局部變量倔撞,對象引用荆几。
程序計數(shù)器:線程隔離愁铺,指示當前所執(zhí)行的字節(jié)碼行號,唯一不拋異常携丁。
```
String s="abc"可款;? //兩個對象拣挪,引用s:棧中對象佛吓;“abc”:常量池對象
String? s=new String("abc")宵晚; //三個對象:引用s:棧中對象垂攘;“abc":常量池對象;new String:堆中對象
```
二淤刃、 HotSpot虛擬機對象
1晒他、對象的創(chuàng)建:
通常是(1)new創(chuàng)建
(2)虛擬機先檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否被加載钝凶、解析和初始化過仪芒,沒有則先執(zhí)行相應的類加載過程.
(3)為新生對象分配內(nèi)存(即把一塊確定大小的內(nèi)存從java堆中劃分出來)(對象創(chuàng)建時并發(fā)情況可能線程不安全唁影,解決方案:①對分配內(nèi)存空間的動作進行同步處理;? ②把內(nèi)存分配的動作按照線程劃分在不同的空間中進行耕陷,即每個線程在java堆中預先分配一小塊內(nèi)存,稱為本地線程分配緩沖 TLAB)?
(4)內(nèi)存分配完成后据沈,虛擬機將分配到的內(nèi)存空間都初始化為零值哟沫。
(5)虛擬機對對象進行必要的設置
2、對象的內(nèi)存布局:
HotSpot虛擬機中锌介,對象在內(nèi)存中存儲的布局分為3塊區(qū)域:對象頭(Header)嗜诀、實例數(shù)據(jù)(Instance Data)、對齊填充(Padding)
(1)虛擬機對象頭包括兩部分信息:
第一部分用于存儲對象自身的運行時數(shù)據(jù)(通常長度是32位或64位孔祸,對32位的隆敢,存儲對象哈希碼25bit,對象分代年齡4bit崔慧,鎖標志位2bit拂蝎,1bit固定為0);是8字節(jié)整數(shù)倍
第二部分是類型指針惶室,即對象指向它的類元數(shù)據(jù)的指針温自,虛擬機通過這個指針確定這個對象是哪個類的實例。但并不是所有虛擬機都保存類型指針皇钞。
(2)實例數(shù)據(jù):對象真正存儲的有效信息悼泌,代碼中所定義的各種類型的字段內(nèi)容,分配策略是相同狂賭字段分配一起(ints夹界、longs/doubles馆里、shorts/chars、bytes/booleans可柿、oops)
(3)對齊填充:不是必須鸠踪,用于補全8字節(jié)的整數(shù)倍
3、對象的訪問定位:
java程序通過棧上的reference數(shù)據(jù)操作堆上的具體對象(reference類型在java虛擬機規(guī)范中只規(guī)定了一個指向?qū)ο蟮囊茫?/p>
訪問方式:
1趾痘、使用句柄訪問:劃分一塊內(nèi)存做句柄池慢哈,reference存對象的句柄地址(穩(wěn)定的句柄地址)
2、直接指針訪問:reference存對象地址(較快)
三永票、OutOfMemoryError異常:
1卵贱、堆溢出:OutOfMemoryError + "java heap space"
處理方式:
首先確認是內(nèi)存中的對象是否是必要的滥沫?
內(nèi)存泄漏(Memory Leak):準確定位泄漏代碼位置
內(nèi)存溢出(Memory Overflow):內(nèi)存中的對象確實還必須存活,應檢查虛擬機的堆參數(shù)(-Xmx键俱,-Xms)兰绣,減少內(nèi)存消耗、調(diào)大堆參數(shù)
2编振、虛擬機棧和本地方法棧溢出:
java虛擬機規(guī)范中兩種異常:
(1)線程請求的棧深度? > 虛擬機所允許的最大深度:StackOverflowError異常
(2)虛擬機在擴展棧時無法申請到足夠內(nèi)存空間:OutOfMemoryError異常
兩者可能重疊
3缀辩、方法區(qū)和運行時常量池溢出:
運行時常量池是方法區(qū)的一部分。
String.intern():Native方法踪央,若字符串常量池已經(jīng)包含一個等于此String對象的字符串臀玄,則返回代表池中這個字符串的String對象,否則畅蹂,將此String對象包含的字符串添加到常量池中健无,并返回此String對象的引用。
4液斜、本機直接內(nèi)存溢出
第三部分:垃圾收集器與內(nèi)存分配策略
GC:Garbage Collection
一累贤、判定對象是否存活的算法:垃圾收集器對堆進行回收前,要先確定這些對象中哪些還存活少漆,哪些已經(jīng)死去(不可能再被任何途徑使用的對象)
【JVM中共劃分為三個代:年輕代臼膏、年老代和持久代,
年輕代:存放所有新生成的對象示损;
年老代:在年輕代中經(jīng)歷了N次垃圾回收仍然存活的對象渗磅,將被放到年老代中,故都是一些生命周期較長的對象屎媳;
持久代:用于存放靜態(tài)文件夺溢,如Java類、方法等烛谊。
新生代的垃圾收集器命名為“minor gc”风响,老生代的GC命名為”Full Gc 或者Major GC”.其中用System.gc()強制執(zhí)行的是Full Gc.】
判斷對象是否需要回收的辦法?
【1丹禀、引用計數(shù)算法:
給對象添加一個引用計數(shù)器状勤,每當一個地方引用它,計數(shù)器值加一双泪;當引用失效時持搜,計數(shù)器值減一;任何時刻計數(shù)器為0的對象就是不可能再被使用的焙矛。(python用了葫盼,但java虛擬機沒用引用計數(shù)算法來管理內(nèi)存,因為很難解決對象間相互循環(huán)引用的問題)
2村斟、可達性分析算法:
通過一系列的被稱為“GC Roots”的對象作為七十點贫导,從這些節(jié)點開始向下搜索抛猫,所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈(從GC Roots到該對象不可達)孩灯,則對象不可用闺金,可回收》宓担】
GC Roots包括:
虛擬機棧(棧幀中的本地變量表)中引用的對象败匹、方法區(qū)中類靜態(tài)屬性引用的對象、方法區(qū)常量引用的對象讥巡、本地方法棧中JNI(即Native方法)引用的對象
3掀亩、引用:
強引用:Object obj=new Object(),new的對象尚卫,強引用存在归榕,其被引用的對象就不會被回收。
軟引用:描述還有用但非必需的對象吱涉。系統(tǒng)將發(fā)生內(nèi)存溢出異常之前回收,如果還沒有足夠內(nèi)存外里,才拋出內(nèi)存溢出異常怎爵。
弱引用:描述非必需對象。其關聯(lián)對象只能生存到下一次垃圾收集發(fā)生之前盅蝗。
虛引用(幽靈引用/幻影引用):一定會被回收鳖链,回收前會通知
二、判定不存活的對象墩莫,至少進行兩次標記芙委,才確定真正死亡。
1狂秦、可達性分析發(fā)現(xiàn)不可達灌侣,則進行第一次標記+一次篩選
2、篩選:對象是否有必要執(zhí)行finalize()方法(只能執(zhí)行一次)
“沒有必要執(zhí)行”——對象沒有覆蓋finalize()方法? 或? finalize()方法已被虛擬機調(diào)用過
“有必要執(zhí)行”——F-Queue隊列裂问,若對象在finalize()方法中重新與引用鏈上的任何一個對象建立關聯(lián)侧啼,則第二次標記時會被移除出"即將回收"集合,成功拯救堪簿。否則——被回收痊乾。
但finalize()方法不鼓勵用于拯救對象。
3椭更、回收方法區(qū):
java不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集哪审,且收集效率也較低
永久代垃圾收集回收:廢棄常量、無用的類虑瀑。
(1)廢棄常量:
一個進入常量池的常量湿滓,不再被引用畏腕,則廢棄,被清理出常量池
(2)無用的類:
同時滿足:該類所有的實例都已被回收(java堆中不存在該類任何實例)茉稠、加載該類的ClassLoader已被回收描馅、該類對應的java.lang,Class對象沒在任何地方被引用(無法在任何地方通過反射訪問該類的方法)
則該類可回收,但不是必然回收而线。
三铭污、垃圾收集算法:
1、標記-清除算法:標記所有需要回收的對象膀篮,標記完成后統(tǒng)一回收嘹狞。(效率不高;清除后產(chǎn)生大量不連續(xù)的內(nèi)存碎片)
2誓竿、復制算法(很多用):把可用內(nèi)存分兩塊磅网,每次只使用其中一塊;當這塊內(nèi)存用完了筷屡,將還存活的對象復制到另一塊上涧偷,將已使用的一次清除(實現(xiàn)簡單,運行高效毙死;但每次將內(nèi)存縮小到一半)
3燎潮、標記-整理算法(常用于老年代):標記所有需要回收的對象,將還存活的對象向一端移動扼倘,直接清理掉端邊界以外的內(nèi)存确封。
4、分代收集算法:新生代:復制算法再菊;老年代:標記-清除/標記-整理
五爪喘、各類垃圾收集器(基于HotSpot虛擬機)
垃圾收集器是內(nèi)存回收的具體體現(xiàn)。
1纠拔、Serial收集器:Client模式下的新生代收集器秉剑,單線程
2、Serial Old收集器:Client模式下的老年代收集器
(1绿语、2是串行收集器)
3秃症、ParNew收集器:server模式下的首選新生代收集器(Serial收集器的多線程版本)
4、Parall Scavenge收集器:更加關注吞吐量
5吕粹、Parall? Old收集器:parall scavenge收集器的老年代版本
(3种柑、4、5是并行收集器)
6匹耕、G1收集器:整體上看聚请,標記-整理,部分看是復制;新生代老年代都有驶赏,更加關注停頓時間炸卑。面向服務端應用。初始標記煤傍;并發(fā)標記盖文;最終標記;篩選回收蚯姆。
7五续、CMS收集器:并發(fā)標記清除,獲取最短回收停頓時間為目的龄恋,老年代收集器(大量空間碎片)疙驾,分為4個步驟:初始標記、并發(fā)標記郭毕、重新標記它碎、并發(fā)清除。
【總結:
串行收集器:單線程显押,暫停所有應用線程來工作
并行收集器:默認的垃圾收集器扳肛。多線程。
G1收集器:新生代煮落,老年代都有敞峭;整體(主要)是標記-整理,部分是復制算法蝉仇。面向服務端應用。初始標記殖蚕、并發(fā)標記轿衔、最終標記、篩選回收睦疫。
CMS收集器:以獲取最短回收停頓時間為目標害驹,老年代收集器;并發(fā)標記-清除蛤育。分為4個步驟:初始標記宛官、并發(fā)標記、重新標記瓦糕、并發(fā)清除底洗。】
第七章? 虛擬機類加載機制
一、類加載機制:虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存咕娄,并對數(shù)據(jù)進行校驗亥揖、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型
類型的加載、連接和初始化都是在程序運行期間完成的费变。(此處的Class文件更多表示一個類或接口摧扇,是一串二進制字節(jié)流。)
二挚歧、類的生命周期:加載扛稽、驗證、準備滑负、解析在张、初始化、使用橙困、卸載? 7階段(連接:驗證-->準備-->初始化)
類加載的五個過程:加載瞧掺、驗證、準備凡傅、解析辟狈、初始化。
(a)加載:類的全限定名--->定義此類的二進制字節(jié)流--->由字節(jié)流的靜態(tài)存儲結構--->轉化為方法區(qū)的運行時數(shù)據(jù)結構--->內(nèi)存中生成代表該類的java.lang.Class對象(方法區(qū)這個類的各種數(shù)據(jù)的訪問入口)
(1)通過類的全限定名來獲取定義此類的二進制字節(jié)流(從zip包獲取——jar,war,ear夏跷;從網(wǎng)絡中獲取——Applet哼转;運行時計算生成——動態(tài)代理;由其他文件生成——JSP應用槽华;從數(shù)據(jù)庫中獲纫悸)
(2)由字節(jié)流的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構
(3)在內(nèi)存中生成代表該類的java.lang.Class對象,作為方法去這個類的各種數(shù)據(jù)的訪問入口
加載有兩種情況猫态,①當遇到new關鍵字或static關鍵字的時候就會發(fā)生
②動態(tài)加載佣蓉,當用反射方法(如class.forName(“類名”)),如果發(fā)現(xiàn)沒有初始化亲雪,則要進行初始化勇凭。(注:加載的時候發(fā)現(xiàn)父類沒有被加載,則要先加載父類)
加載階段可使用系統(tǒng)的引導類加載器完成义辕,或用戶自定義的類加載器完成(自定義控制字節(jié)流的獲取方式)虾标,加載和連接階段交錯進行。
(b)驗證:連接階段第一步
這一階段的目的是確保class文件的字節(jié)流中包含的信息符合當前虛擬機的要求灌砖,并不會危害虛擬機自身的安全璧函。
驗證階段4個階段檢驗動作:【文件格式檢驗、元數(shù)據(jù)驗證(語義校驗)基显、字節(jié)碼驗證蘸吓、符號引用驗證(解析時發(fā)生)】
(c)準備:
正式為類變量分配內(nèi)存并設置類變量初始值,內(nèi)存分配在方法區(qū)续镇。
注意:C腊摹!此時只對類變量(static修飾的)進行內(nèi)存分配,不包括實例變量(在對象實例化時隨對象一起分配在java堆)制跟。>俗!此時進行的是默認初始化雨膨,賦零值擂涛,即=0,=false聊记;
(d)解析:虛擬機將常量池中的符號引用替換成直接引用的過程撒妈。
符號引用:以一組符號來描述所引用的目標(不一定已加載到內(nèi)存中)
直接引用:直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄排监。(目標必定加載到了內(nèi)存)
(e) 初始化:真正執(zhí)行類中的代碼
是執(zhí)行類構造器<clinit>()方法的過程狰右。
用于:創(chuàng)建類的實例、訪問類或接口的靜態(tài)變量舆床、調(diào)用類的靜態(tài)方法棋蚌、反射(Class.forName())、初始化類的子類
【<clinit>方法:編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生挨队;JVM會保證父類的<clinit>()先執(zhí)行谷暮,所以第一個執(zhí)行的一定是Object類,所以父類中定義的靜態(tài)語句塊優(yōu)先于子類的變量賦值操作盛垦;該方法對類/接口不是必須的湿弦,如果類無靜態(tài)語句塊/對變量的賦值動作,可無該方法腾夯〖瞻#】
(三)類加載器:完成加載階段的【通過一個類的全限定名來獲取描述此類的二進制字節(jié)流】的操作。在JVM外部實現(xiàn)蝶俱。
1竟秫、分類:
(1)從JVM來看,
a.啟動類加載器(C++跷乐,是虛擬機自身的一部分)
b.所有其他類加載器(Java,獨立于虛擬機外部趾浅,全都繼承自抽象類java.lang.ClassLoader)
(2)從開發(fā)來看愕提,
a.啟動類加載器:Bootstrap? ClassLoader:
負責加載 存放在<JAVA_HOME>\lib目錄中,或被-Xbootclasspath參數(shù)所指定的路徑中皿哨,被虛擬機識別的類庫? 到 虛擬機內(nèi)存中浅侨。
b.擴展類加載器:Extension ClassLoader,可直接使用证膨,負責加載? ?<JAVA_HOME>\lib\ext目錄中如输,或被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫? ?到? ?虛擬機內(nèi)存中。
c.應用程序類加載器:可直接使用,負責加載? 用戶類路徑(ClassPath)上所指定的類庫不见。是程序默認的類加載器澳化。
d.自定義類加載器:通過繼承ClassLoader實現(xiàn),一般是加載我們的自定義類
2稳吮、類加載器間層次關系:
a.雙親委派模型:組合關系缎谷。
請求委派給父類,最終都傳到啟動類加載器灶似,只有父類加載器反饋自己無法加載列林,子加載器才嘗試加載。
其代碼都集中在java.lang.ClassLoader的loadClass()方法中酪惭∠3眨【先檢查是否被加載過,無則調(diào)用父類的loadClass()春感,父加載器為空則使用啟動類加載器作為父加載器砌创,若父類加載失敗,拋出ClassNotFoundException甥厦,調(diào)用自己的findClass()加載】
【阿里的面試官問我纺铭,可以不可以自己寫個String類
答案:不可以,因為 根據(jù)類加載的雙親委派機制刀疙,會去加載父類舶赔,父類發(fā)現(xiàn)沖突了String就不再加載了;
可以類比到其他已經(jīng)有的類。我試過谦秧,然后其他同個包中的所有類有運用到原先的String類的全都報錯竟纳,因為引用到了這個類【卫穑】
說一說你對環(huán)境變量classpath的理解锥累?如果一個類不在classpath下,為什么會拋出ClassNotFoundException異常集歇,如果在不改變這個類路徑的前期下桶略,怎樣才能正確加載這個類?
classpath是javac編譯器的一個環(huán)境變量诲宇。它的作用與import际歼、package關鍵字有關。package的所在位置姑蓝,就是設置CLASSPATH當編譯器面對import packag這個語句時鹅心,它先會查找CLASSPATH所指定的目錄,并檢視子目錄java/util是否存在纺荧,然后找出名稱吻合的已編譯文件(.class文件)旭愧。如果沒有找到就會報錯颅筋!
動態(tài)加載包
-