JVM深入理解
JVM深入理解
?
一.JVM介紹
JVM應用百度百科的原話是:
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。JAVA語言的一個非常重要的特點就是與平臺的無關性。
而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了
與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的能夠
"一次編譯,到處運行"的原因。
這句話能夠很清楚的明白一件事情:之所以JAVA能夠流行起來,JVM是一個非常關鍵的因數.所以學習JVM的原理和運轉流程是很有必要的,對優化JAVA性能也是非常有必要的一個基礎條件.
?
二.運行時區域簡單介紹
?
JAVA虛擬機所管理的內存將會包含了有以下幾個運行時數據區域,下圖可看:
?
1.程序計數器
是記錄當前線程所執行器的行號指示器.字節碼解釋器就是改變計數器的值來選取嚇一條字節碼指令來執行的.因為程序計數器是在私有區域,對于線程來說,每個線程都將擁有一個程序計數器.各個線程計數器都是互不影響,獨立存儲的.
程序計數器主要記錄的是虛擬機的字節碼地址,如果當前執行本地方法,那么程序計數器的值為空.
?
2.JAVA虛擬機棧
虛擬機棧也是線程私有的,是為了描述java方法執行的內存模型:但方法執行時,虛擬機棧會創建一個棧幀,是用來存儲局部變量表,操作數棧,動態鏈接,和方法的返回地址等信息的.方法在執行的過程,就是一個棧幀的入棧到出棧的過程.
1.局部變量表
是存儲8大基本類型(boolean,int,byte,long,double,short,float,char),和一個叫做對象引用的reference(只是一個存儲的對象指針,指向此對象的位置).
3.本地方法棧
?
類似于虛擬機棧的功能,但是本地方法棧是虛擬機是使用到了NATIVE方法.
?
4.JAVA堆
java堆是管理內存的最大區域,這一區域是線程共享的,幾乎所有的對象創建后都會保存在堆里面,并且堆也是垃圾回收的重點關注對象,因為垃圾回收對java對象回收的收益是非常高的,在后面我會詳細講解垃圾回收機制有關的知識.
java堆按照年齡段分為:新生代和老年代.
5.方法區
方法區也是線程共享的,它主要存放關于類的信息,常量,靜態變量和編譯后的代碼等.方法區也有人稱其為'永久代',因為垃圾回收雖然也會回收方法區的數據,但是回收的效益很低,幾乎不會被回收,所以稱為'永久代'.
運行時常量池:用于存放通過編譯期編譯過后的的字面量和符號引用,相當于存放數據結構的的地方.對于class文件,java虛擬機都對其每一個部分有著非常嚴格的說明與限制.不管是什么語言,在java虛擬機編譯后都會統一
編譯成class文件,并且在虛擬機上運行.
? 6.對象的創建與定位
對于java語言,大家都知道是一門對象語言,所以對java來說,對象的創建是非常平常的,讓我們來一探究竟.
對代碼而言,可能創建對象僅僅就是new這樣一個關鍵字而已,但是java底層一定是做了很多操作的:
1.當虛擬機檢查到new關鍵字時,會檢查常量池是否有這個類的符號引用.
2.如果有這個類的符號引用,會檢查這個類是否被加載,解析和初始化了,就是常常人們說的'類加載'.
3.如果沒有,就會去類加載,如果已經加載了,jvm就會為新對象分配內存空間.新對象的內存大小會在類加載的時候就確定.
4.分配完對象的內存大小,會對這個類進行一些初始化設置,比如:元數據信息,對象的hash碼,GC信息等,這些都會被分配到對象的對象頭里面.下圖為對象的對象頭信息圖:
?
三.垃圾收集
?
1.判斷對象是否已經死去
在垃圾收集器,在收集時,會對這個對象判斷是否已經不用了,不用了就代表已經死了,需要進行垃圾收集了.所以垃圾收集器時怎么判定對象是否已經死了呢?
有兩種方式:計數算法和可達性算法
計數算法:相當于給每一個對象的引用都加1,當引用失效的時候就減1,然后判斷此對象的計數器是否為0,如果是就說明此對象已經沒有引用了,需要垃圾收集了.
雖然這個方式簡單,但是有一個問題,如果對象之間存在相互調用的情況,那么它們的計數器都不為0,導致無法收集.
可達性算法:
基本思路時一個鏈路式的判定方法,把'GC ROOTS'作為根節點,從根節點往下尋找,搜索這個對象的引用鏈路,當這個對象沒法達到GC Roots的話,說明此
對象不可達了,需要垃圾收集.
2.垃圾收集算法
到目前為此,垃圾收集算法有非常多種,但是我也不能說哪一種時完美的,沒有完美的垃圾收集器,只有合適的垃圾收集器.所以,在不同場合,需要選擇不同的垃圾收集器
來完成運行中的垃圾收集任務.但是底層的算法大概能分為以下幾種:
1.標記-清除算法
看名字就應該知道,此算法有2個階段,'標記'和'清除'.當發現對象已經死了,就標記,到時候垃圾收集器就會統一'清除'對象.
2.復制算法
復制算法是為了解決效率問題產生的。大多數場景都可以定義為:Survivor和Eden,比例為2:8,而Survivor又可以分為:from?Survivor和to?Survivor,比例為:1:1.
對于新生代的對象來說,基本90%以上的對象都是‘朝生夕死’的,所以當回收時,就將from?Survivor和Eden區還存活的對象復制到to?Survivor,其余的空間全部
清除。這樣就可以提升性能。
?
3.標記-整理算法
? 使用復制算法效率高,但是會浪費一部分的空間,如果不想浪費空間,就可以采用標記-整理算法。它和‘標記-清除’算法很類似,但是,當標記完需要回收的對象時,
‘標記-整理’算法會把存活下來的對象整體向一個方向移動,就可以直接清除掉需要回收的對象。
?
?
3.垃圾收集器
?
垃圾收集器,就是根據各種垃圾回收算法,運用到不同的場合的一種實現。大致的垃圾收集器可以用如下圖表示:
每一種垃圾收集器都會運用在不同的場合,或者多個垃圾收集器共用,使得新生代和老年代都可以高效率的運行,目前最前沿的垃圾收集器是G1收集器。
號稱最低延遲的'stop the world'。關于每一種垃圾收集器詳細的意義,可以自行在網上查閱資料,在這里我就不過多說明了。
?
四.類文件結構
?
類文件就是通過編譯器編譯過后,生成的.class文件,之所以java語言是跨平臺的,其實關鍵就是在類文件class身上。
?
1.類文件結構
class文件其實就是一組由8位字節為基本單元的二進制流,文件的排序必須嚴格按照規定順序排列在class文件中,并且沒有任何空隙。class文件的結構大概就是:
2種:無符號數和表。
無符號數:是一種基本數據結構,以:u1,u2,u4,u8分別代表1字節,2字節,4字節和8字節的無符號數。無符號數可以用來描述數字,數量值,字符串等。
表:就是由多個無符號數組合而成的一種復合結構。其實整個class文件就是一張表罷了。
?
1.魔數
class文件的前4個字節被稱為“魔數”,它被當做唯一確定文件是否是class文件的標識。魔術的值為:0xCAFFEBABE.
2.版本號
第5,6和第7,8都是版本號,第5,6個字符是此版本號,第7,8個字符是主版本號。
3.常量池
它是整個class文件中和其他項目關聯最多的結構了。也是最大的一塊結構。它是存儲class文件中的資源倉庫。常量池的常量并不是固定的,所以在常量池的入口會放置一個u2的
數據類型,代表當前常量池的計量值。常量池主要存放2大類常量:字面量和符號引用。
字面量:文本符號,final的常量值等
符號引用:類,接口的全限定名,字段名,方法名
常量池的基本類型有如下幾十種:
所以說常量池是最繁瑣的結構,數據,每一種的結構數據都不一樣。
?
?
2.字節碼指令
字節碼指令都是由一個字節長度以及后面跟隨的多個操作參數組成的。字節碼的操作是通過在虛擬機棧的操作數棧實現的。
由于一開始就已經限制了字節碼指令長度為一個字節,所以所有的指令就不能超過256個。我這里就不羅列字節碼的指令了。
?
五.類加載機制
?
上一章我們講了class文件結構,但是jvm是怎么加載類文件的呢?下面我們來一起探討下這個問題
1.類加載時機
類加載的整個生命周期大致分為下列7個階段:
?
在什么時候會觸發第一個階段‘加載’呢?有如下5種情況:
1.遇到new,getstatic,putstatic,invokestatic字節碼時,如果沒有類加載,那么就需要進行類加載操作。
2.使用refect反射時,如果類沒有初始化。
3.當初始化一個類,發現其父類沒有初始化時。
4.當虛擬機啟動,執行的主類沒有初始化時。
5.當jdk為1.7以上,如果MethodHandle的解析結果為REF_getStatic,REF_putstatic,REF_invokestatic的類沒有初始化時。
?
2.類加載過程
1.加載
這個階段需要完成:
1.通過全限定名獲取類的二進制流。
2.將這個流所代表的靜態結構轉換為方法區的運行時數據結構。
3.在內存中生成一個代表這個類的java.lang.class對象。作為這個類的訪問入口。
2.驗證
驗證當前字節流中的信息是否符合虛擬機的要求,包含:
1.文件格式:
1.是否是以‘魔數’開頭。
2.主,副版本號是否符合當前虛擬機。
3.常量池中的常量是否有不被支持的類型。
4.其他等等要求。
?
2.元數據驗證:
1.驗證當前類的父類,類的字段等信息是否符合規范
?
3.字節碼驗證:
對每一個字節碼都進行驗證,檢查字節碼是否符合規范,邏輯。
?
4.符號引用:
對常量池中的符號引用做驗證。
?
?
3.準備
為類的變量分配初始值的階段。但是這個類的變量限于:被static修飾的變量,并且當前賦值和java代碼沒喲關系,僅僅是賦變量的初始值,比如int類型的初始值為0.
?
4.解析
就是把常量池的符號引用解析為直接引用的過程。
?
?
5.初始化
這一階段,才開始定義java代碼。在準備階段已經賦了一次初始值,這個階段是第二次賦值。它會把java代碼的初始值給賦上。
?
?
3.類加載器
?
類加載器顧名思義就是用來加載類的。比較兩個類是否相等,其實最底層是比較他們是否為同一個加載器加載的。類加載器有一個很重要的詞語:雙親委派。
類加載器分大類就2類:啟動類加載器(C語言實現),其他類加載器(java語言實現)。下圖為加載器的繼承圖:
這個模式就是雙親委派模型。意思是:當一個類加載器收到類加載的信息時,一般是先委派給父類完成,一般就會傳遞到頂層加載,當父類無法加載時,子加載器才會自己嘗試加載。
雙親委派對于java的穩定來說至關重要。
?
?
這次基本就說到這,強烈建議大家去看<<深入理解Java虛擬機:JVM高級特性與最佳實踐>>這本書。
?
?
轉載于:https://www.cnblogs.com/zhaowei520/p/10746186.html
總結
- 上一篇: excel导出文本格式设置为数值(eas
- 下一篇: 使用DSX-5000 对已安装的电缆进行