深入理解java虚拟机
深入理解java虛擬機
Java發展歷程
標志性事件
2012年JDK7倉促上線,很多規劃的功能被砍掉
2014年JDK8上線,陷入Jigsaw模塊化功能深坑
2017年經過艱苦的談判,JDK9加入了Jigsaw模塊化,增強了若干工具
此后,Oracle對JDK開發進入敏捷開發階段,每三個月和九個月發布一個新版本,由于迭代速度過快,決定每六個JDK大版本中一個才會被長期支持(LTS版)。JDK8、JDK11、JDK17為LTS版。
2018年9月JDK11開始維護兩個版本,OpenJDK和OracleJDK,兩個JDK共享大部分源碼,前者可以商用免費,但只有半年的更新支持,但是后者在生產環境中商用必須付費。
2019年2月RedHat公司接受JDK8和JDK11的維護工作
Java 內存區域與內存溢出異常
對于Java程序員來說,在虛擬機自動內存管理的機制下,不需要再為每一個new操作去寫配對的delete/free代碼,不容易出現內存溢出與內存泄露的問題,但是,一旦出現這種問題,如果不了解虛擬機怎樣運行的,修正問題將會異常艱巨。
數據區
可以籠統的將Java虛擬機內存分為堆(Heap)內存和棧(Stack)內存,
程序計數器
可以看做是當前線程執行的字節碼的行號指示器。由于Java虛擬機的多線程是通過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器只會執行一條線程的指令。因此,為了線程切換后能夠恢復到正確的執行位置,每條線程都需要一個獨立的程序計數器,每條線程互不影響,這類內存被稱為“線程私有”的內存。
如果線程正在執行一個Java方法,這個計數器記錄的是蒸菜執行的虛擬機字節碼指令的地址;如果執行的是native方法,這個計數器指針為空(Undefined)。
Java虛擬機棧
Java虛擬機棧也是線程私有的,它的生命周期與線程相同,虛擬機棧描述的是Java方法執行的線程內存模型:每個方法被執行的時候,Java虛擬機棧都會同步創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每個方法被調用直到執行完畢。每一個方法被調用直至執行完畢的過程,對應著一個棧幀在虛擬機棧從入棧到出棧的過程。
局部變量表存儲了編譯可知的基本數據類型(boolean、byte、char、short、float、long、double)、對象引用和returnAdrerss類型(指向一條字節碼指令的地址)。
對這個區域規定了兩種異常情況:如果線程請求的深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;如果Java虛擬機棧容量可以動態擴展,當棧擴展時無法申請到足夠的內存會拋出OutOfMemoryError異常。
主要異常:
NullPointerException - 空指針引用異常
ClassCastException - 類型強制轉換異
IllegalArgumentException - 傳遞非法參數異常
ArithmeticException - 算術運算異常
ArrayStoreException - 向數組中存放與聲明類型不兼容對象異常
IndexOutOfBoundsException - 下標越界異常
NegativeArraySizeException - 創建一個大小為負數的數組錯誤異常
NumberFormatException - 數字格式異常
SecurityException - 安全異常
UnsupportedOperationException - 不支持的操作異常
本地方法棧
本地方法棧與虛擬機棧的主要區別是,本地方法為虛擬機所用到的native方法服務,虛擬機棧為虛擬機執行Java方法。
堆
堆(Heap)是虛擬機所管理內存中最大的一塊,Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域唯一目的是存放對象實例,Java世界里“幾乎”所有實例都在這里分配內存。
Java堆是垃圾收集器管理的內存區域,因此也被成為不GC堆。
方法區
方法區(Method Area)和Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯后的代碼緩存數據。
運行時常量池
運行時常量池是方法區的一部分,用于存放編譯期生成的各種字面量與符號引用,具有動態特性,Java語言并不要求常量一定在編譯期間才能產生,也就是說,運行期間可以將新的常量放入池中,這種特性被開發人員利用比較多的是String類的intern()方法。
HotSpot虛擬機對象
對象的創建
當虛擬機遇到一條字節碼new指令時,首先檢查這個指令的參數是否能在常量池中定位一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須執行相應的類加載過程,在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需內存的大小在類加載完成后便可以完全確定,為對象分配內存的任務等同于把一塊確定大小的內存從Java堆中劃分出來。內存分配完成后,虛擬機必須將分配的內存空間都初始化為零值。
接下來,Java虛擬機還要對對象進行必要的設置,例如哪個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼(調用hashCode()方法計算出)、對象的分代年齡等信息。
然后對象創建完成了,對于Java程序員來說才剛剛開始-構造函數。
對象的內存布局
在HotSpot虛擬機里,對象的堆內存中存儲布局可以劃分為三個部分:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
對象頭分為兩部分:一部分是用于存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標志、線程持有鎖、偏向線程ID、偏向時間戳等,這部分數據可能為32bit或者64bit,被官方稱為“Mark Word”;另一部分是類型指針,即對象指向它的類型元數據的指針,Java虛擬機通過這個指針來確定對象是哪個類的實例。
實例數據是對象真正存儲的有效信息,無論是從父類繼承的還是在子類中定義的都必須記錄起來。
第三部分是對齊填充,僅僅起到占位符的作用。
對象的訪問
兩種訪問方式:
句柄訪問,Java堆會劃分處一塊內存作為句柄池,棧中的reference中存儲的就是對象的句柄池,而句柄中包含了對象實例數據與類型數據各自具體的地址信息結構如下所示
在這里插入圖片描述
直接指針訪問,reference存放的是直接對象的地址,內存布局需要考慮如何存放對象。好處是速度快,HotSpot主要使用這種方式。
虛擬機設置內存大小
堆
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
其中+HeapDumpOnOutOfMemoryError參數可以是內存溢出存儲內存快照,可用于分析錯誤。
虛擬機棧和本地方法棧
-Xss128k -XX:+HeapDumpOnOutOfMemoryError
虛擬機棧之前說過有兩種溢出,線程請求的棧深度大于虛擬機允許的深度,將拋出StackOverflowError異常。如果虛擬機棧可以動態擴展,當擴展的時候沒有申請到內存的時候拋出OutOfMemoryError。
一個機器的內存是有限的。建立過多線程導致的內存溢出。如果不能減少線程,那么可以通過減少堆和減少棧容量來換取更多線程。這部分可以深入討論,主要討論各個模塊的關系,和各自的限制。
方法區
jdk7:-XX:PermSize=10m -XX:MaxPermSize=10m
jdk8:-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
jdk8的方法區變成了元空間,使用的是直接內存。所以和jdk7使用的參數不一樣
直接內存
直接內存如果不設置和-Xmx(堆最大)一樣。可通過參數-XX:MaxDirectMemorySize來設置直接內存。直接內存一般和NIO有關,DirectByteBuffer中通過Unsafe去操作的直接內存,DirectByteBuffer是通過計算知道內存不足拋出的異常,unsafe.allocateMemory()才是去申請內存的方法。測試代碼如下圖:
測試參數:-Xms20m -XX:MaxDirectMemorySize=10m
垃圾收集器與內存分配策略
概述
研究路線:哪些內存需要回收,什么時候回收,如何回收
程序計數器,虛擬機棧,本地方法棧三個區域隨線程而生也隨線程而滅,每個棧幀中分配的內存基本上在類結構確定下來時都是已知的,因此這幾個區域的內存回收都具備確定性。
Java堆和方法區有著顯著的不確定性,一個接口的多個實現類所需的內存可能會不一樣,一個方法所執行的不同條件分支所需要的內存也可能不一樣,處于運行期間,我們才知道究竟創建哪些對象,創建多少個對象,這部分的回收是動態的。
判斷對象是否死去的方法
回收計數法
在對象添加一個計數器,引用一次就加一,引用失效就減一,當計數器為0時就判斷為死亡。
優點:原理簡單,引用效率高
缺點:當兩個對象互相引用的時候,計數器永遠都不會我0,即使兩個對象都已經不再使用了,這使得無法回收。
可達性分析法
從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。
在Java語言中,GC Roots包括:
虛擬機棧中引用的對象。
方法區中類靜態屬性實體引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI引用的對象。
引用
分為強引用、軟引用、弱引用、虛引用四種。
生存還是死亡
即使可達性分析算法中判定為不可達對象,這時候需要進一步去判斷,至少經過兩次標記過程:可達性分析進行第一次標記,第一次標記后進行篩選,篩選條件是此對象是否有必要執行finalize()方法。
垃圾收集算法
分代收集理論
分代收集理論建立在兩個假說之上:
絕大多數對象都是朝生夕滅的;
熬過多次垃圾收集對象的過程就越難以消亡。
這兩個分代假說奠定了多款常用的垃圾收集器的一致性設計原則:收集器將Java堆劃分成不同的區域,然后將回收對象依據其年齡分配到不同的區域中存儲。垃圾回收器每次只回收其中一個或者一部分的區域,因此有了“MinorGC” “MajorGC”“FullGC”這樣回收類型的劃分。也因此產生根據其特征相匹配的收集算法:“標記-復制算法”“標記-清除算法”“標記-整理算法”等針對垃圾收集器的算法。
把分代收集理論具體放到現在的商用Java虛擬機中,設計者一般把Java堆分為新生代(YoungGereration)和老年代(OldGeneration)兩個區域。
標記-清除算法
“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。之所以說它是最基礎的收集算法,是因為后續的收集算法都是基于這種思路并對其缺點進行改進而得到的。
它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
標記-復制算法
“復制”(Copying)的收集算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,持續復制長生存期的對象則導致效率降低。
在這里插入圖片描述
標記-整理算法
復制收集算法在對象存活率較高時就要執行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
垃圾收集器種類
內存分配
對象優先在Eden分配
大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠的空間進行分配時,虛擬機將發起異常Minor GC。
大對象直接進入老年代
大對象是指需要連續龐大連續內存空間的java對象,最典型的大對象就是那種很長的字符串,或者元素數量很龐大的數據。大對象對虛擬機的分配是一個不折不扣的壞消息,尤其是短命的壞消息,寫程序時應該注意。
長期存活的對象進入老年代
對象在Eden去誕生,如果第一次MinorGC能夠存活的話將進入survivor區(如果能容納的話),每過一次MinorGC,年齡就加一,年齡增長到一定程度(默認15歲),對象放入老年代。
動態對象年齡判定
如果Survivor空間中相同年齡的對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,不用等到設置的年齡。
空間分配擔保
在發生MinorGC之前虛擬機必須先檢查老年代的最大可用的連續空間是否大于新生代所有對象的總空間,如果條件成立,那么這次MinorGC可用確保是安全的,如果不成立,需要查看是否設置允許擔保失敗,如果不允許,則執行一次FullGC,如果允許,則繼續檢查老年代的連續空間是否大于每次晉升到老年代對象的平均大小,如果大于,則嘗試一次具有風險的MinirGC,如果小于,則進一步進行FullGC。
虛擬機分析工具
基礎故障處理工具
jps虛擬機進程狀況監視工具
羅列正在運行的虛擬機進程,并顯示虛擬機執行的主類名稱以及這些進程的本地虛擬機唯一id。無法定位進程名稱時可以依賴jsp來區分。
格式 jsp [options] [pid]
例如jps、 jps -l
選項:
-q:只輸出LVMID,省略主類名稱
-m:輸出虛擬機啟動時傳遞給主類main()函數的參數
-l: 輸出主類全名,如果進程執行的是jar包,則輸出jar路徑
-v:輸出虛擬機進程啟動時的JVM參數
jstat虛擬機統計信息監視工具
用于監視虛擬機各種運行狀態信息的命令行工具。它可以顯示進程中的類加載、內存、垃圾收集信息、即時編譯等運行的數據。
命令格式:
jstat [option vmid [interval [s|ms] [count] ] ]
interval和count代表查詢間隔和次數,如果省略這兩個參數,說明只會查詢一次。
例如:jstat -gc 24408 1000 100
1秒鐘查詢一次堆情況,總共查詢100次
選項option代表用戶查詢虛擬機信息,主要分為三類:類加載、垃圾收集、運行是編譯情況
-class 顯示有關類加載器行為的統計信息
-compiler 顯示有關 Java HotSpot 虛擬機即時編譯器行為的統計信息
-gc 顯示垃圾收集堆行為的統計信息
-gccapacity 顯示各代容量及其相應空間的統計信息
-gccause 顯示垃圾收集統計信息(與 -gcutil 相同)的摘要,以及上次和當前(如果適用)垃圾收集事件的原因
-gcnew 顯示新一代行為的統計數據
-gcnewcapacity 顯示關于新一代大小及其相應空間的統計信息
-gcold 顯示老一代行為的統計信息和元空間統計信息
-gcoldcapacity 顯示關于老一代大小的統計數據
-gcmetacapacity 顯示關于元空間大小的統計信息
-gcutil 顯示垃圾收集統計信息的摘要
jstat -gcutil 24408
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 13.01 4.91 97.62 91.53 2 0.035 1 0.051 0.086
S0,S1 代表Survivor區,E代表Eden區,O代表老年代,P代表永久代,程序運行以來總共發生YoungGC 2次,總耗時0.035s,FullGC 1次,總耗時0.086s。
查看10次GC信息
jstat -gcutil 39128 1000 10
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
74.02 0.00 93.88 25.82 71.07 61.60 1112 3.283 0 0.000 3.283
74.10 0.00 20.37 25.82 71.07 61.60 1116 3.289 0 0.000 3.289
74.19 0.00 36.18 26.39 71.07 61.60 1120 3.294 0 0.000 3.294
74.27 0.00 33.59 26.39 71.07 61.60 1124 3.300 0 0.000 3.300
6.25 0.00 12.52 26.95 71.07 61.60 1128 3.308 0 0.000 3.308
0.00 74.44 33.82 26.95 71.07 61.60 1131 3.343 0 0.000 3.343
0.00 74.52 35.58 26.95 71.07 61.60 1133 3.345 0 0.000 3.345
74.57 0.00 86.44 26.95 71.07 61.60 1134 3.346 0 0.000 3.346
74.67 0.00 46.28 26.95 71.07 61.60 1136 3.349 0 0.000 3.349
74.77 0.00 10.35 26.95 71.07 61.60 1138 3.352 0 0.000 3.352
通過檢查十次的gc發現 : 新生代的數據增長到數據滿時會觸發異常YGC,同時將數據放到老年代
jinfo:Java配置信息工具
查看JVM配置信息
命令格式:
jinfo [option] pid
例如
[10307006@zte.intra@LIN-CCE6AF6663D ~]$ jinfo -flag InitialHeapSize 35487
-XX:InitialHeapSize=197132288
jmap:Java內存映像工具
jmap 用于生成堆轉儲快照,還可以查詢finalize執行隊列、Java堆和方法區的詳細信息,如空間使用率、當前用的哪種平臺收集器。
命令格式:
jmap [options] vmid
其中options的選線可以為:
-dump 生成堆轉快照的文件。格式為-dump:[live,]format=b,file=filename,其中live子參數說明是否只dump出存活對象
-finalizerinfo 顯示在F-Queue中等待FinaLizer線程執行finalize方法的對象。
-heap 顯示堆詳細信息。 例如:jmap -heap 35487 簡單羅列部分數據如下
-histo 顯示堆中對象統計信息,包括類、實例對象、合計容量。
-permastat 以ClassLoader為統計口徑顯示永久代內存信息。
jhat:虛擬機堆轉儲快照分析工具
JDK提供jhat命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。
例如:
jhat ommpjmap.bin
Reading from ommpjmap.bin…
Dump file created Sat Feb 19 10:48:30 CST 2022
Snapshot read, resolving…
Resolving 110871 objects…
Chasing references, expect 22 dots…
Eliminating duplicate references…
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
在瀏覽器輸入localhost:7000即可看到分析的堆信息
jstack:Java堆棧跟蹤工具
jstack命令用于生成虛擬機當前時刻的線程快照。線程快照就是當前虛擬機每一條線程正在執行的方法堆棧的集合。
命令格式:
jstack [options] vmid
options選項合法值和具體含義如表所示
-F 當正常輸出的請求不被響應時,強制輸出線程堆棧。
-l 除堆棧外,顯示關于鎖的附加信息
-m 如果調用本地方法的話,可以輸出C/C++的堆棧
可視化處理工具
JVM集成的可視化工具包括JConsole、JHSDB、VisualVM和JMC四個。
調優案例
虛擬機執行子系統
代碼編譯從本地機器碼轉變為字節碼
類文件結構
Java虛擬機不包括Java語言在內的任何程序語言綁定,它只與“Class文件”這種特定的二進制文件格式所關聯。Java技術能夠一直保持良好的向后兼容性,Class文件結構的穩定功不可沒。主要將如何將Class文件存儲格式的具體細節。
常量池
常量池主要存放兩大類常量:字面量和符號引用。字面量比較接近Java語言層面的常量概念,如文本字符串、被聲明的final常量值等。而符號引用則屬于編譯原理方面的概念。
操作碼
虛擬機類加載機制
虛擬機加載機制實際為Java虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗、轉換解析和初始化,并最終形成可以被虛擬機直接使用的Java類型的過程。
類加載的過程
一個類型從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期將會經歷加載、驗證、準備、解析、初始化、使用、卸載七個階段,其中驗證、準備、解析被統稱為連接。
加載
在加載階段,虛擬機會完成三件事:
通過一個類的全限定性類名來獲取此類的二進制字節流。
通過這個字節流所代表的靜態存儲結構轉化為運行時數據結構。
在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據訪問入口。
驗證
目的是確保Class文件的字節流包含的信息不會危害虛擬機自身的安全。
準備
準備階段是正式為類中定義的變量(即靜態變量,被static修飾的變量)分配內存并設置變量的初始值階段,從概念上講,這些變量所使用的內存都應當在方法區中進行分配, 方法區實際是是一個邏輯區域,JDK8之后類變量會隨著Class對象一起放到Java堆中。
需要注意的是類變量會在準備階段進行內存分配,但是實例變量會在對對象進行實例化的時候隨對象一起放到Java堆中。
在準備階段會對類變量進行初始化,如果這個類變量用final修飾,則會初始化為設置的值,如果不是final類型的則設置為0,false或者null。
解析
解析階段是Java虛擬機將常量池內的符號引用替換為直接引用的過程。
解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符這七類符號引用今行,分別對應常量池的八種常量類型的表格。
初始化
之前的階段除了用戶應用程序可以通過自定義加載器的方式局部參與外,其余動作都完全由Java虛擬機來主導控制,直到初始化階段,Java虛擬機才真正開始執行類中編寫的程序代碼,將主導權交給應用程序。
類加載器
Java虛擬機設計團隊有意把“通過一個全限定名來獲取描述改類的二進制字節流”這個動作放到Java虛擬機外部,以便讓應用程序自己決定如何獲取所需要的類。實現這個動作的代碼被稱為“類加載器”。
站在Java虛擬機的角度看,只存在兩種類加載器,一種是啟動類加載器,在JVM內部,另一種是其它所有類加載器,獨立于虛擬機外面,并且全部繼承自抽象類java.lang.ClassLoader
雙親委派模型
雙親委派模型的工作流程是:
如果一個類加載器受到了類加載的請求,它不會自己嘗試加載這個類,而是委托它的父類加載器去完成,每一個層次的類加載器都是這樣,因此都傳輸到頂層的啟動類加載器中,只有當父類加載器反饋無法完成這個加載請求的時候(搜索范圍內沒有需要的類),子類加載器才會去完成加載。
1、啟動類加載器(Bootstrap ClassLoader),它是屬于虛擬機自身的一部分,用C++實現的,主要負責加載<JAVA_HOME>\lib目錄中或被-Xbootclasspath指定的路徑中的并且文件名是被虛擬機識別的文件。它等于是所有類加載器的爸爸。
2、擴展類加載器(Extension ClassLoader),它是Java實現的,獨立于虛擬機,主要負責加載<JAVA_HOME>\lib\ext目錄中或被java.ext.dirs系統變量所指定的路徑的類庫。
3、應用程序類加載器(Application ClassLoader),它是Java實現的,獨立于虛擬機。主要負責加載用戶類路徑(classPath)上的類庫,如果我們沒有實現自定義的類加載器那這玩意就是我們程序中的默認加載器。
為什么需要破壞雙親委派?
因為在某些情況下父類加載器需要委托子類加載器去加載class文件。受到加載范圍的限制,父類加載器無法加載到需要的文件,以Driver接口為例,由于Driver接口定義在jdk當中的,而其實現由各個數據庫的服務商來提供,比如mysql的就寫了MySQL Connector,那么問題就來了,DriverManager(也由jdk提供)要加載各個實現了Driver接口的實現類,然后進行管理,但是DriverManager由啟動類加載器加載,只能記載JAVA_HOME的lib下文件,而其實現是由服務商提供的,由系統類加載器加載,這個時候就需要啟動類加載器來委托子類來加載Driver實現,從而破壞了雙親委派,這里僅僅是舉了破壞雙親委派的其中一個情況。
高效并發
Java內存模型與線程
計算機的運算速度和它的存儲和通信子系統的速度差距太大,大量的時間都花在磁盤I/O、通信網絡或者數據庫訪問上。如果不希望處理器在大部分時間都在等待其它資源的空閑狀態,就必須使用一些時間把處理器的運算能力壓榨出來。
衡量一個服務器性能的高低好壞,每秒事務處理數(TPS)是重要指標之一。而TPS與程序的并發能力密切相關
volatile型變量的規則
問題:
局部變量表中什么是對象引用和returnAdress
native方法與java方法的區別
String類的intern()方法有什么用?
String::intern()是一個本地方法,它的作用是如果字符串常量池已經包含一個等于此String對象的字符串,則返回代表池中這個字符串的String對象引用;否則,會將此String對象包含的字符串添加到常量池中,并且返回此String對象引用。
finalize()方法作用
由上,Java中的對象并不一定會被全部垃圾回收,當你不想要該對象的時候,你需要手動去處理那些“特殊內存”。
Java 允許定義這樣的方法,它在對象被垃圾收集器析構(回收)之前調用,這個方法叫做 finalize( ),它用來清除回收對象。
不建議用finalize()方法完成“非內存資源”的清理工作,但建議用于:
① 清理本地對象(通過JNI創建的對象);
② 確保某些非內存資源(如Socket、文件等)的釋放:在finalize()方法中顯式調用其他資源釋放方法。
finalize()的隱藏問題
System.gc()與System.runFinalization()方法增加了finalize()方法執行的機會,但不可盲目依賴它們
Java語言規范并不保證finalize方法會被及時地執行、而且根本不會保證它們會被執行
finalize()方法可能會帶來性能問題。因為JVM通常在單獨的低優先級線程中完成finalize()的執行
對象再生問題:finalize()方法中,可將待回收對象賦值給GC Roots可達的對象引用,從而達到對象再生的目的
finalize()方法至多由GC執行一次(用戶當然可以手動調用對象的finalize()方法,但并不影響GC對finalize()的行為)
finalize()一般不用!被執行的不確定性太大。不要指望使用finalize()來回收你的對象,它只會在系統進行GC的時候清理特殊內存,不受你的控制!
finalize()的執行過程(生命周期)
當對象變成(GC Roots)不可達時,GC會判斷該對象是否覆蓋了finalize方法,若未覆蓋,則直接將其回收。否則,若對象未執行過finalize方法,將其放入F-Queue隊列,由一低優先級線程執行該隊列中對象的finalize方法。執行finalize方法完畢后,GC會再次判斷該對象是否可達,若不可達,則進行回收,否則,對象“復活”
常量池存儲的數據
final修飾的變量
。。。。
Class文件的全路徑類名
類變量和實例變量的區別
類變量:
類變量也稱為靜態變量,在類中以static關鍵字聲明,但必須在方法構造方法和語句塊之外。
無論一個類創建了多少個對象,類只擁有類變量的一份拷貝。
靜態變量除了被聲明為常量外很少使用。常量是指聲明為public/private,final和static類型的變量。常量初始化后不可改變。
靜態變量儲存在靜態存儲區。經常被聲明為常量,很少單獨使用static聲明變量。
靜態變量在程序開始時創建,在程序結束時銷毀。
與實例變量具有相似的可見性。但為了對類的使用者可見,大多數靜態變量聲明為public類型。
默認值和實例變量相似。數值型變量默認值是0,布爾型默認值是false,引用類型默認值是null。變量的值可以在聲明的時候指定,也可以在構造方法中指定。此外,靜態變量還可以在靜態語句塊中初始化。
靜態變量可以通過:ClassName.VariableName的方式訪問。
類變量被聲明為public static final類型時,類變量名稱必須使用大寫字母。如果靜態變量不是public和final類型,其命名方式與實例變量以及局部變量的命名方式一致。
實例變量:
實例變量聲明在一個類中,但在方法、構造方法和語句塊之外;
當一個對象被實例化之后,每個實例變量的值就跟著確定;
實例變量在對象創建的時候創建,在對象被銷毀的時候銷毀;
實例變量的值應該至少被一個方法、構造方法或者語句塊引用,使得外部能夠通過這些方式獲取實例變量信息;
實例變量對于類中的方法、構造方法或者語句塊是可見的。一般情況下應該把實例變量設為私有。通過使用訪問修飾符可以使實例變量對子類可見;
實例變量具有默認值。數值型變量的默認值是0,布爾型變量的默認值是false,引用類型變量的默認值是null。變量的值可以在聲明時指定,也可以在構造方法中指定;
實例變量可以直接通過變量名訪問。但在靜態方法以及其他類中,就應該使用完全限定名:ObejectReference.VariableName。
總結
以上是生活随笔為你收集整理的深入理解java虚拟机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++题解:百钱买百鸡数量
- 下一篇: 全文搜索引擎Solr原理和实战教程