摘要:虛擬機(jī)棧線程私有,生命周期跟線程相同。堆用于存放對(duì)象實(shí)例,是虛擬機(jī)所管理的內(nèi)存中最大的一塊,同時(shí)也是所有線程共享的一塊內(nèi)存區(qū)域。統(tǒng)計(jì)監(jiān)測(cè)工具語法格式如下是虛擬機(jī),在系統(tǒng)上一般就是進(jìn)程。
JDK、JRE、JVM三者的關(guān)系
JDK(Java Development Kit)是針對(duì)Java開發(fā)的產(chǎn)品、是整個(gè)Java的核心,包括Java運(yùn)行環(huán)境JRE、Java工具包和Java基礎(chǔ)類庫。
JRE(Java Runtime Environment)是運(yùn)行Java程序所必須的環(huán)境的集合,包含JVM標(biāo)準(zhǔn)實(shí)現(xiàn)及Java核心類庫。
JVM(Java Virtual Machine)是整個(gè)Java跨平臺(tái)的最核心的部分,能夠運(yùn)行以Java語言寫作的軟件程序。所有的Java程序都會(huì)首先被編譯為.class文件,這種類文件可以在虛擬機(jī)上運(yùn)行,class文件并不直接與機(jī)器的操作系統(tǒng)相對(duì)應(yīng),而是經(jīng)過虛擬機(jī)間接與操作系統(tǒng)交互,由虛擬機(jī)將程序解釋給本地系統(tǒng)執(zhí)行。
Java運(yùn)行時(shí)區(qū)域 程序計(jì)數(shù)器內(nèi)存中較小的內(nèi)存空間,通過計(jì)數(shù)器的值可以選取下一條執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
線程私有,生命周期跟線程相同。
如果正在執(zhí)行一個(gè)Native方法,那么這個(gè)計(jì)數(shù)器值將為空。
虛擬機(jī)棧線程私有,生命周期跟線程相同。
每個(gè)方法在執(zhí)行同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常情況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;
如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展,如果擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
跟虛擬機(jī)棧所發(fā)揮的作用相似,區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。
Java堆用于存放對(duì)象實(shí)例,是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,同時(shí)也是所有線程共享的一塊內(nèi)存區(qū)域。
因?yàn)镴ava堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱為“GC"堆。由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆還可以細(xì)分為
新生代
老年代
永久代(永久代是Hotspot虛擬機(jī)特有的概念,是方法區(qū)的一種實(shí)現(xiàn),別的JVM都沒有這個(gè)東西。在Java 8中,永久代被徹底移除,取而代之的是另一塊與堆不相連的本地內(nèi)存——元空間。)
當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),它首先進(jìn)入新生代,之后有可能被轉(zhuǎn)移到老年代中。
新生代存放著大量的生命很短的對(duì)象,因此新生代在三個(gè)區(qū)域中垃圾回收的頻率最高。為了更高效地進(jìn)行垃圾回收,把新生代繼續(xù)劃分成以下三個(gè)空間:
Eden
From Survivor
To Survivor
方法區(qū)與Java堆一樣,各個(gè)線程共享的內(nèi)存區(qū)域,存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即使編譯器編譯后的代碼等數(shù)據(jù)。
運(yùn)行時(shí)常量池方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號(hào)引用。
運(yùn)行時(shí)常量池相對(duì)于class文件常量池的另外一個(gè)重要特征是具備動(dòng)態(tài)性,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。
直接內(nèi)存在JDK1.4中新加入了NIO類,引入了一種基于通道與緩沖區(qū)的I/O方法,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。
堆外內(nèi)存之 DirectByteBuffer 詳解
在語言層上,創(chuàng)建對(duì)象通常僅僅是一個(gè)new關(guān)鍵字而已,而當(dāng)虛擬機(jī)遇到一條new執(zhí)行時(shí),將由一下步驟:
檢查類是否加載、解析、初始化過,沒有則先執(zhí)行相應(yīng)的類加載過程。
在堆中分配內(nèi)存
劃分可用空間:
指針碰撞:堆內(nèi)存規(guī)整
空閑列表:堆內(nèi)存不規(guī)整
并發(fā)問題
同步:采用CAS配上失敗重試的方式保證更新操作的原子性
把內(nèi)存分配動(dòng)作按照線程劃分在不同的空間之中進(jìn)行
將分配到的內(nèi)存空間都初始化零值
設(shè)置對(duì)象的類實(shí)例、元數(shù)據(jù)、哈希碼、GC分代年齡等信息。
執(zhí)行
對(duì)象在內(nèi)存中儲(chǔ)存的布局可以分為3塊區(qū)域:
對(duì)象頭
對(duì)象運(yùn)行時(shí)數(shù)據(jù)、哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)記、線程持有的鎖、偏向線程ID等
類型執(zhí)行:即對(duì)象執(zhí)向它的類元數(shù)據(jù)的指針,指明對(duì)象數(shù)據(jù)哪個(gè)類的實(shí)例。
實(shí)例數(shù)據(jù)
對(duì)象真正存儲(chǔ)的有效信息
對(duì)齊填充
占位符作用
對(duì)象的訪問定位句柄定位
直接指針
內(nèi)存溢出內(nèi)存溢出out of memory,是指程序在申請(qǐng)空間時(shí),沒有足夠的內(nèi)存空間供其使用,出現(xiàn)了Out of memory error。
堆內(nèi)存溢出當(dāng)new一個(gè)對(duì)象或者數(shù)組時(shí),如果超出了Jvm的head內(nèi)存最大限制就會(huì)爆出異常。
偽代碼:
while(ture){ new Object(); }棧內(nèi)存溢出
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)棧規(guī)定了兩種異常情況,如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOutFlowError異常,如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分Java虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),當(dāng)擴(kuò)展時(shí)無法申請(qǐng)得到足夠的內(nèi)存時(shí)將會(huì)拋出OutOfMemory。
StackOutFlowError線程中的stack是線程私有的,默認(rèn)大小通常為1M,可以通過-Xss來設(shè)置,-Xss越大,則線程獲取的內(nèi)存越大。
常見問題在線程內(nèi)過度的調(diào)用函數(shù),函數(shù)調(diào)用會(huì)消耗??臻g。
偽代碼:
public void SOFETest(){ SOFETest(); }OutOfMemoryError
Java的棧空間被所有線程分配成一塊一塊的,每個(gè)線程只占一塊。而Jvm的??臻g的最小分配單位有-Xss來決定。-Xss有兩個(gè)語義,即定義每個(gè)線程的棧大小,也定義了虛擬機(jī)的最小棧內(nèi)存的分配單位。
如果申請(qǐng)的線程沒有獲得棧空間可以分配了就會(huì)拋出OutOfMemoryError。表示??臻g不足,溢出異常。
代碼:該代碼可能導(dǎo)致JVM無法申請(qǐng)得到太多的棧內(nèi)存而導(dǎo)致操作系統(tǒng)因?yàn)闂?臻g不足假死。
public class Main { public static void main(String[] args) throws ClassNotFoundException { CountDownLatch countDownLatch = new CountDownLatch(1); for(int i =0;i<1020000000;i++){ new Thread(new Runnable(){ @Override public void run() { int a = 1000; try { countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } countDownLatch.countDown(); } }內(nèi)存泄漏
內(nèi)存泄漏memory leak,指程序在申請(qǐng)內(nèi)存之后,無法釋放已申請(qǐng)的內(nèi)存空間,一次內(nèi)存泄漏危害可以忽略,多次memory leak將導(dǎo)致oom。
內(nèi)存泄漏是指你向系統(tǒng)申請(qǐng)分配內(nèi)存進(jìn)行使用(new),可是使用完了以后卻不歸還(delete),結(jié)果你申請(qǐng)到的那塊內(nèi)存你自己也不能再訪問(也許你把它的地址給弄丟了),而系統(tǒng)也不能再次將它分配給需要的程序。
jvm性能調(diào)優(yōu)監(jiān)控工具使用詳解該部分內(nèi)容轉(zhuǎn)自:JVM性能調(diào)優(yōu)監(jiān)控工具jps、jstack、jmap、jhat、jstat、hprof使用詳解
jps(Java Virture Machine Process Status Tool)jps主要用來輸出JVM中運(yùn)行的進(jìn)程狀態(tài)信息。語法格式如下:
jps [options] [hostid]
如果不指定hostid就默認(rèn)為當(dāng)前主機(jī)或服務(wù)器。
命令行參數(shù)選項(xiàng)說明如下:
-q 不輸出類名、Jar名和傳入main方法的參數(shù) -m 輸出傳入main方法的參數(shù) -l 輸出main類或Jar的全限名 -v 輸出傳入JVM的參數(shù)
比如下面:
root@ubuntu:/# jps -m -l 2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml 29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat 3149 org.apache.catalina.startup.Bootstrap start 30972 sun.tools.jps.Jps -m -l 8247 org.apache.catalina.startup.Bootstrap start 25687 com.sun.tools.hat.Main -port 9999 dump.dat 21711 mrf-center.jarjstack
jstack主要用來查看某個(gè)Java進(jìn)程內(nèi)的線程堆棧信息。語法格式如下:
jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip
命令行參數(shù)選項(xiàng)說明如下:
-l long listings,會(huì)打印出額外的鎖信息,在發(fā)生死鎖時(shí)可以用jstack -l pid來觀察鎖持有情況 -m mixed mode,不僅會(huì)輸出Java堆棧信息,還會(huì)輸出C/C++堆棧信息(比如Native方法)
jstack可以定位到線程堆棧,根據(jù)堆棧信息我們可以定位到具體代碼,所以它在JVM性能調(diào)優(yōu)中使用得非常多。下面我們來一個(gè)實(shí)例找出某個(gè)Java進(jìn)程中最耗費(fèi)CPU的Java線程并定位堆棧信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java進(jìn)程ID,我部署在服務(wù)器上的Java應(yīng)用名稱為mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到進(jìn)程ID為21711,第二步找出該進(jìn)程內(nèi)最耗費(fèi)CPU的線程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我這里用第三個(gè),輸出如下:
TIME列就是各個(gè)Java線程耗費(fèi)的CPU時(shí)間,CPU時(shí)間最長的是線程ID為21742的線程,用
printf "%x " 21742
得到21742的十六進(jìn)制值為54ee,下面會(huì)用到。
OK,下一步終于輪到j(luò)stack上場(chǎng)了,它用來輸出進(jìn)程21711的堆棧信息,然后根據(jù)線程ID的十六進(jìn)制值grep,如下:
root@ubuntu:/# jstack 21711 | grep 54ee "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread這個(gè)類的Object.wait(),我找了下我的代碼,定位到下面的代碼:
// Idle wait getLog().info("Thread [" + getName() + "] is idle waiting..."); schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting; long now = System.currentTimeMillis(); long waitTime = now + getIdleWaitTime(); long timeUntilContinue = waitTime - now; synchronized(sigLock) { try { if(!halted.get()) { sigLock.wait(timeUntilContinue); } } catch (InterruptedException ignore) { } }
它是輪詢?nèi)蝿?wù)的空閑等待代碼,上面的sigLock.wait(timeUntilContinue)就對(duì)應(yīng)了前面的Object.wait()。
jmap(Memory Map)和jhat(Java Heap Analysis Tool)jmap用來查看堆內(nèi)存使用狀況,一般結(jié)合jhat使用。
jmap語法格式如下:
jmap [option] pid jmap [option] executable core jmap [option] [server-id@]remote-hostname-or-ip
如果運(yùn)行在64位JVM上,可能需要指定-J-d64命令選項(xiàng)參數(shù)。
jmap -permstat pid
打印進(jìn)程的類加載器和類加載器加載的持久代對(duì)象信息,輸出:類加載器名稱、對(duì)象是否存活(不可靠)、對(duì)象地址、父類加載器、已加載的類大小等信息,如下圖:
使用jmap -heap pid查看進(jìn)程堆內(nèi)存使用情況,包括使用的GC算法、堆配置參數(shù)和各代中堆內(nèi)存使用情況。比如下面的例子:
root@ubuntu:/# jmap -heap 21711 Attaching to process ID 21711, please wait... Debugger attached successfully. Server compiler detected. JVM version is 20.10-b01 using thread-local object allocation. Parallel GC with 4 thread(s) Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 2067791872 (1972.0MB) NewSize = 1310720 (1.25MB) MaxNewSize = 17592186044415 MB OldSize = 5439488 (5.1875MB) NewRatio = 2 SurvivorRatio = 8 PermSize = 21757952 (20.75MB) MaxPermSize = 85983232 (82.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 6422528 (6.125MB) used = 5445552 (5.1932830810546875MB) free = 976976 (0.9317169189453125MB) 84.78829520089286% used From Space: capacity = 131072 (0.125MB) used = 98304 (0.09375MB) free = 32768 (0.03125MB) 75.0% used To Space: capacity = 131072 (0.125MB) used = 0 (0.0MB) free = 131072 (0.125MB) 0.0% used PS Old Generation capacity = 35258368 (33.625MB) used = 4119544 (3.9287033081054688MB) free = 31138824 (29.69629669189453MB) 11.683876009235595% used PS Perm Generation capacity = 52428800 (50.0MB) used = 26075168 (24.867218017578125MB) free = 26353632 (25.132781982421875MB) 49.73443603515625% used ....
使用jmap -histo[:live] pid查看堆內(nèi)存中的對(duì)象數(shù)目、大小統(tǒng)計(jì)直方圖,如果帶上live則只統(tǒng)計(jì)活對(duì)象,如下:
root@ubuntu:/# jmap -histo:live 21711 | more num #instances #bytes class name ---------------------------------------------- 1: 38445 55977362: 38445 5237288 3: 3500 3749504 4: 60858 3242600 5: 3500 2715264 6: 2796 2131424 7: 5543 1317400 [I 8: 13714 1010768 [C 9: 4752 1003344 [B 10: 1225 639656 11: 14194 454208 java.lang.String 12: 3809 396136 java.lang.Class 13: 4979 311952 [S 14: 5598 287064 [[I 15: 3028 266464 java.lang.reflect.Method 16: 280 163520 17: 4355 139360 java.util.HashMap$Entry 18: 1869 138568 [Ljava.util.HashMap$Entry; 19: 2443 97720 java.util.LinkedHashMap$Entry 20: 2072 82880 java.lang.ref.SoftReference 21: 1807 71528 [Ljava.lang.Object; 22: 2206 70592 java.lang.ref.WeakReference 23: 934 52304 java.util.LinkedHashMap 24: 871 48776 java.beans.MethodDescriptor 25: 1442 46144 java.util.concurrent.ConcurrentHashMap$HashEntry 26: 804 38592 java.util.HashMap 27: 948 37920 java.util.concurrent.ConcurrentHashMap$Segment 28: 1621 35696 [Ljava.lang.Class; 29: 1313 34880 [Ljava.lang.String; 30: 1396 33504 java.util.LinkedList$Entry 31: 462 33264 java.lang.reflect.Field 32: 1024 32768 java.util.Hashtable$Entry 33: 948 31440 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
class name是對(duì)象類型,說明如下:
B byte C char D double F float I int J long Z boolean [ 數(shù)組,如[I表示int[] [L+類名 其他對(duì)象
還有一個(gè)很常用的情況是:用jmap把進(jìn)程內(nèi)存使用情況dump到文件中,再用jhat分析查看。jmap進(jìn)行dump命令格式如下:
jmap -dump:format=b,file=dumpFileName pid
我一樣地對(duì)上面進(jìn)程ID為21711進(jìn)行Dump:
root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711 Dumping heap to /tmp/dump.dat ... Heap dump file created
dump出來的文件可以用MAT、VisualVM等工具查看,這里用jhat查看:
root@ubuntu:/# jhat -port 9998 /tmp/dump.dat Reading from /tmp/dump.dat... Dump file created Tue Jan 28 17:46:14 CST 2014 Snapshot read, resolving... Resolving 132207 objects... Chasing references, expect 26 dots.......................... Eliminating duplicate references.......................... Snapshot resolved. Started HTTP server on port 9998 Server is ready.
注意如果Dump文件太大,可能需要加上-J-Xmx512m這種參數(shù)指定最大堆內(nèi)存,即jhat -J-Xmx512m -port 9998 /tmp/dump.dat。然后就可以在瀏覽器中輸入主機(jī)地址:9998查看了:
上面紅線框出來的部分大家可以自己去摸索下,最后一項(xiàng)支持OQL(對(duì)象查詢語言)。
jstat(JVM統(tǒng)計(jì)監(jiān)測(cè)工具)語法格式如下:
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmid是Java虛擬機(jī)ID,在Linux/Unix系統(tǒng)上一般就是進(jìn)程ID。interval是采樣時(shí)間間隔。count是采樣數(shù)目。比如下面輸出的是GC信息,采樣時(shí)間間隔為250ms,采樣數(shù)為4:
root@ubuntu:/# jstat -gc 21711 250 4 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 192.0 192.0 64.0 0.0 6144.0 1854.9 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649 192.0 192.0 64.0 0.0 6144.0 2109.7 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
要明白上面各列的意義,先看JVM堆內(nèi)存布局:
可以看出:
堆內(nèi)存 = 年輕代 + 年老代 + 永久代 年輕代 = Eden區(qū) + 兩個(gè)Survivor區(qū)(From和To)
現(xiàn)在來解釋各列含義:
S0C、S1C、S0U、S1U:Survivor 0/1區(qū)容量(Capacity)和使用量(Used) EC、EU:Eden區(qū)容量和使用量 OC、OU:年老代容量和使用量 PC、PU:永久代容量和使用量 YGC、YGT:年輕代GC次數(shù)和GC耗時(shí) FGC、FGCT:Full GC次數(shù)和Full GC耗時(shí) GCT:GC總耗時(shí)hprof(Heap/CPU Profiling Tool)
hprof能夠展現(xiàn)CPU使用率,統(tǒng)計(jì)堆內(nèi)存使用情況。
語法格式如下:
java -agentlib:hprof[=options] ToBeProfiledClass java -Xrunprof[:options] ToBeProfiledClass javac -J-agentlib:hprof[=options] ToBeProfiledClass
完整的命令選項(xiàng)如下:
Option Name and Value Description Default --------------------- ----------- ------- heap=dump|sites|all heap profiling all cpu=samples|times|old CPU usage off monitor=y|n monitor contention n format=a|b text(txt) or binary output a file=write data to file java.hprof[.txt] net= : send data over a socket off depth= stack trace depth 4 interval= sample interval in ms 10 cutoff= output cutoff point 0.0001 lineno=y|n line number in traces? y thread=y|n thread in traces? n doe=y|n dump on exit? y msa=y|n Solaris micro state accounting n force=y|n force output to y verbose=y|n print messages about dumps y
來幾個(gè)官方指南上的實(shí)例。
CPU Usage Sampling Profiling(cpu=samples)的例子:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello
上面每隔20毫秒采樣CPU消耗信息,堆棧深度為3,生成的profile文件名稱是java.hprof.txt,在當(dāng)前目錄。
CPU Usage Times Profiling(cpu=times)的例子,它相對(duì)于CPU Usage Sampling Profile能夠獲得更加細(xì)粒度的CPU消耗信息,能夠細(xì)到每個(gè)方法調(diào)用的開始和結(jié)束,它的實(shí)現(xiàn)使用了字節(jié)碼注入技術(shù)(BCI):
javac -J-agentlib:hprof=cpu=times Hello.java
Heap Allocation Profiling(heap=sites)的例子:
javac -J-agentlib:hprof=heap=sites Hello.java
Heap Dump(heap=dump)的例子,它比上面的Heap Allocation Profiling能生成更詳細(xì)的Heap Dump信息:
javac -J-agentlib:hprof=heap=dump Hello.java
雖然在JVM啟動(dòng)參數(shù)中加入-Xrunprof:heap=sites參數(shù)可以生成CPU/Heap Profile文件,但對(duì)JVM性能影響非常大,不建議在線上服務(wù)器環(huán)境使用。
垃圾收集器程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧這三個(gè)區(qū)域?qū)儆诰€程私有的,只存在于線程的生命周期內(nèi),線程結(jié)束之后也會(huì)消失,因此不需要對(duì)這三個(gè)區(qū)域進(jìn)行垃圾回收。垃圾回收主要是針對(duì) Java 堆和方法區(qū)進(jìn)行。
判斷對(duì)象是否死亡 引用計(jì)數(shù)算法給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器值就加1;引用時(shí)效時(shí),計(jì)算器值就減1;當(dāng)計(jì)數(shù)器值為0的對(duì)象就是不可能再被使用的。
當(dāng)兩個(gè)對(duì)象相互引用時(shí),此時(shí)引用計(jì)數(shù)器的值永遠(yuǎn)不為0,導(dǎo)致無法對(duì)它們進(jìn)行垃圾回收。
public class ReferenceCountingGC { public Object instance = null; public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA .instance = objB ; objB .instance = objA ; objA = null; objB = null; System.gc(); } }可達(dá)性分析算法
以GC Roots為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,能夠搜索到的對(duì)象都是存活的,不可達(dá)的對(duì)象則為不可用。
在Java語言中,可作為GC Roots的對(duì)象包括下面幾種:
虛擬機(jī)棧中引用的對(duì)象
方法區(qū)中靜態(tài)屬性引用的對(duì)象
方法區(qū)中常量引用的對(duì)象
本地方法棧中Native方法引用的對(duì)象
引用類型無論是引用計(jì)數(shù)算法還是可達(dá)性分析算法判斷對(duì)象是否存活都與引用有關(guān)。在JDK1.2之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,劃分為強(qiáng)度不同的四個(gè)的引用類型。
強(qiáng)引用通過new來創(chuàng)建對(duì)象的引用類型,被強(qiáng)引用的對(duì)象永遠(yuǎn)不會(huì)被垃圾收集器回收。
Object obj = new Object();軟引用
通過SortReference類來實(shí)現(xiàn),只有在內(nèi)存不足的時(shí)候才會(huì)被回收。
Object obj = new Object(); SoftReference弱引用
通過WeakReference類來實(shí)現(xiàn),只能存活到下一次垃圾收集發(fā)生之前。
Object obj = new Object(); WeakReferencewr = new WeakReference (obj); obj = null;
WeakHashMap 的 Entry 繼承自 WeakReference,主要用來實(shí)現(xiàn)緩存。
private static class Entry
Tomcat 中的 ConcurrentCache 就使用了 WeakHashMap 來實(shí)現(xiàn)緩存功能。ConcurrentCache 采取的是分代緩存,經(jīng)常使用的對(duì)象放入 eden 中,而不常用的對(duì)象放入 longterm。eden 使用 ConcurrentHashMap 實(shí)現(xiàn),longterm 使用 WeakHashMap,保證了不常使用的對(duì)象容易被回收。
public final class ConcurrentCache虛引用{ private final int size; private final Map eden; private final Map longterm; public ConcurrentCache(int size) { this.size = size; this.eden = new ConcurrentHashMap<>(size); this.longterm = new WeakHashMap<>(size); } public V get(K k) { V v = this.eden.get(k); if (v == null) { v = this.longterm.get(k); if (v != null) this.eden.put(k, v); } return v; } public void put(K k, V v) { if (this.eden.size() >= size) { this.longterm.putAll(this.eden); this.eden.clear(); } this.eden.put(k, v); } }
也稱為幽靈引用或者幻影引用,是最弱的一種引用關(guān)系。
通過PhantomReference類來實(shí)現(xiàn),為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。
Object obj = new Object(); PhantomReference垃圾收集算法 標(biāo)記清除wr = new PhantomReference (obj, null); obj = null;
算法分為“標(biāo)記”跟“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成之后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
不足:
效率問題,標(biāo)記跟清除兩個(gè)過程的效率都不高
空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片。
復(fù)制算法將內(nèi)存分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這塊內(nèi)存用完了,就將還存活的對(duì)象負(fù)責(zé)到另一塊上面,然后再把一是要難過過得內(nèi)存空間一次清理掉。
不足:
代價(jià)太高,只使用一半內(nèi)存。
標(biāo)記整理算法首先標(biāo)記出所有需要回收的對(duì)象,然后將所有存活的對(duì)象都向一端移動(dòng),最后清理掉端邊界以外的內(nèi)存。
分代收集算法根據(jù)對(duì)象的存活周期將內(nèi)存劃分為幾塊。一般將Java堆分為新生代跟老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?/p>
新生代:復(fù)制算法
老年代:標(biāo)記整理或標(biāo)記清除算法。
垃圾收集器如果說手機(jī)算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。
上圖展示了7種不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說明它們可以搭配使用。
知道目前為止還沒有最好的收集器出現(xiàn),更加沒有萬能的收集器,所以我們只能選擇對(duì)具體應(yīng)用最合適的收集器。
Serial收集器最基本、最悠久的收集器,單線程收集器,復(fù)制算法
在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,直到它收集結(jié)束。
相比較與其他收集器,它具有:簡單而高效的特點(diǎn),對(duì)于限定CPU環(huán)境來說,Serial收集器沒有線程交互的開銷。
依然是虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器。
ParNew收集器Serial的多線程版本、并行,復(fù)制算法。
是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,因?yàn)槟壳俺薙erial收集器外,只有它能與CMS收集器配合使用。
默認(rèn)開啟的收集線程數(shù)與CPU的數(shù)量相同,在CPU非常多的環(huán)境下,可以使用-XX:ParallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。
Parallel Scavenge收集器新生代、并行的多線程收集器,復(fù)制算法。
Parallel Scavenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量:CPU用戶運(yùn)行用戶代碼的時(shí)間與CPU的執(zhí)行時(shí)間,即吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)。
停頓時(shí)間越短越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn),而高吞吐量可以高效率地利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不需要太多交互的任務(wù)。
Parallel Scavenge收集器提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間-XX:MaxGCPauseMillis參數(shù)以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。
Serial Old收集器Serial的老年代版本,單線程,標(biāo)記-整理算法。
這個(gè)收集器的主要意義在于給Client模式下的虛擬機(jī)使用,如果在Server默認(rèn)下,它還有兩大用途:
在JDK1.5以及之前版本中與Parallel Scavenge收集器搭配使用。
作為CMS收集器的后備方案。
Parallel Old收集器parallel Scavenge的老年代版本,多線程,標(biāo)記-整理算法,JDK1.6之后提供。
在注重吐吞量以及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。
CMS收集器CMS(Concurrent Mark Sweep),從名字來就可以看出,基于標(biāo)記-清除算法。
并發(fā)收集、低停頓。
運(yùn)算過程分為4個(gè)步驟:
初始標(biāo)記:標(biāo)記GC Roots能直接關(guān)聯(lián)得到的對(duì)象,速度很快
并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing過程,時(shí)間較長,不停頓
重新標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,停頓時(shí)間一般比初始標(biāo)記稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記短。
并發(fā)清理:耗時(shí)較長,不停頓。
整個(gè)過程耗時(shí)最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶安城一起工作。
CMS還遠(yuǎn)達(dá)不到完美的程度,還有以下3個(gè)缺點(diǎn):
對(duì)CPU資源敏感,并發(fā)階段占用CPU資源而導(dǎo)致用戶線程變慢;低停頓時(shí)間是以犧牲吞吐量為代價(jià)的,導(dǎo)致CPU利用率不高。
無法處理浮動(dòng)垃圾,由于CMS并發(fā)清理階段用戶線程還在運(yùn)行著,伴隨著程序運(yùn)行自然還會(huì)有新的垃圾產(chǎn)生,這部分垃圾CMS無法在檔次收集中處理掉它們,只有留待下一次GC時(shí)再清理。也是由于垃圾收集階段用戶還需要運(yùn)行,故還需要預(yù)留足夠的內(nèi)存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了在進(jìn)行收集,需要預(yù)留一部分空間提供并發(fā)收集時(shí)的程序運(yùn)作使用。
標(biāo)記-清除算法會(huì)導(dǎo)致大量的空間碎片,空間碎片過多時(shí),將會(huì)給大對(duì)象分配帶來很大麻煩,往往會(huì)出現(xiàn)老年代還有很大空間剩余卻無法找到足夠大的連續(xù)空間來分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC。
G1收集器一款面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是未來可以替代掉JDK1.5中發(fā)布的CMS收集器。
與其他收集器相比,G1具有以下特點(diǎn):
并發(fā)與并行:使用多個(gè)CPU來縮短Stop-The-World停頓的時(shí)間。
分代收集:與其他收集器一樣,分代概念在G1中依然得以保存,雖然G1可以不需要其他收集器配合就能獨(dú)立管理GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對(duì)象和一存活了一段時(shí)間、熬過多次GC的舊對(duì)象。
空間整合:從整體來看是基于標(biāo)記-整理算法實(shí)現(xiàn)的收集器,從局部(兩個(gè)Region之間)來看是基于復(fù)制算法來實(shí)現(xiàn)的,意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片。
可預(yù)測(cè)停頓:能夠讓使用者明確指定一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒。
G1將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離級(jí)別,它們都是一部分Region的集合。
G1收集器之所以能夠建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃低避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾回收。G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region。
虛擬機(jī)使用Remembered Set來避免全棧掃描,G1中每個(gè)Region都有一個(gè)與之對(duì)應(yīng)的Remembered Set,用來記錄該Region對(duì)象的引用對(duì)象所在的Region。
如果不計(jì)算Remembered Set的操作,G1收集器的運(yùn)作大致可分為:
初始標(biāo)記
并發(fā)標(biāo)記
最終標(biāo)記:為了修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變化的那一部分標(biāo)記記錄,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程Remembered Set Logs里面,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這階段需要停頓線程,但是可以并行執(zhí)行。
篩選回收:首先對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來制定回收計(jì)劃。此階段其實(shí)可以做到與用戶程序一起并發(fā)執(zhí)行,但是因?yàn)橹换厥找徊糠諶egion,時(shí)間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率。
回收策略新生代GC(Minor GC):發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)镴ava對(duì)象大多都具備朝生息滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。
老年代GC(major GC/full GC):發(fā)生在老年代的GC,出現(xiàn)了Major GC經(jīng)常會(huì)伴隨一次的Minor GC(但非絕對(duì),在paraller Scavenge收集器的手機(jī)策略里就有直接進(jìn)行Major GC的策略選擇過程)。Major GC的速度一般會(huì)比Minor GC慢10倍以上。
Full GC的觸發(fā)條件
調(diào)用System.gc():只是建議虛擬機(jī)執(zhí)行Full GC,但是虛擬機(jī)不一定真正地執(zhí)行,不建議使用這種方式,而是讓虛擬機(jī)管理內(nèi)存。
老年代空間不足:老年代空間不足的最常見場(chǎng)景為前文所講的大對(duì)象直接進(jìn)入老年代,長期存活的對(duì)象進(jìn)入老年代等。為了避免以上原因引起的 Full GC,應(yīng)當(dāng)盡量不要?jiǎng)?chuàng)建過大的對(duì)象以及數(shù)組。除此之外,可以通過 -Xmn 虛擬機(jī)參數(shù)調(diào)大新生代的大小,讓對(duì)象盡量在新生代被回收掉,不進(jìn)入老年代。還可以通過 -XX:MaxTenuringThreshold 調(diào)大對(duì)象進(jìn)入老年代的年齡,讓對(duì)象在新生代多存活一段時(shí)間。
空間分配擔(dān)保失?。?/strong>使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作擔(dān)保,如果擔(dān)保失敗會(huì)執(zhí)行一次 Full GC。具體內(nèi)容請(qǐng)參考上面的第五小節(jié)。
JDK 1.7 及以前的永久代空間不足:在 JDK 1.7 及以前,HotSpot 虛擬機(jī)中的方法區(qū)是用永久代實(shí)現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù),當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時(shí),永久代可能會(huì)被占滿,在未配置為采用 CMS GC 的情況下也會(huì)執(zhí)行 Full GC。如果經(jīng)過 Full GC 仍然回收不了,那么虛擬機(jī)會(huì)拋出 java.lang.OutOfMemoryError。為避免以上原因引起的 Full GC,可采用的方法為增大永久代空間或轉(zhuǎn)為使用 CMS GC。
Concurrent Mode Failure:執(zhí)行 CMS GC 的過程中同時(shí)有對(duì)象要放入老年代,而此時(shí)老年代空間不足(有時(shí)候“空間不足”是指 CMS GC 當(dāng)前的浮動(dòng)垃圾過多導(dǎo)致暫時(shí)性的空間不足),便會(huì)報(bào) Concurrent Mode Failure 錯(cuò)誤,并觸發(fā) Full GC。
內(nèi)存分配策略對(duì)象的內(nèi)存分配規(guī)則并不是百分百固定的,其細(xì)節(jié)取決于當(dāng)前使用的是哪一種垃圾收集器組合,還有虛擬機(jī)中與內(nèi)存有關(guān)的參數(shù)設(shè)置。
對(duì)象優(yōu)先在eden分配
大對(duì)象直接進(jìn)入老年代
長期存活對(duì)象進(jìn)入老年代:Survivor區(qū)對(duì)象每經(jīng)歷一次Minor GC,年齡就增加1,當(dāng)年齡達(dá)到MaxTenuringThreshold設(shè)置的值(默認(rèn)15),就將會(huì)被晉升到老年代。
動(dòng)態(tài)對(duì)象年齡判定:如果Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代。
空間分配擔(dān)保:在發(fā)生Minor GC之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC就是安全的。如果不成立,則虛擬機(jī)會(huì)先查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將會(huì)嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)也要改為進(jìn)行一次Full GC。
類加載機(jī)制 類加載的時(shí)機(jī)虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有下面5種情況必須立即對(duì)類進(jìn)行初始化:
遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果累沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的java代碼場(chǎng)景:使用new關(guān)鍵字實(shí)例化對(duì)象,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、調(diào)用一個(gè)類的靜態(tài)方法。
使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用,如果類沒有初始化則需要先觸發(fā)其初始化。
當(dāng)虛擬機(jī)加載一個(gè)類的時(shí)候,父類還沒有進(jìn)行初始化,則需先觸發(fā)父類初始化。
但虛擬機(jī)啟東市,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先執(zhí)行這個(gè)主類。
當(dāng)使用JDK1.7的動(dòng)態(tài)語言規(guī)則時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法所對(duì)應(yīng)的類還沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。
以下情況不會(huì)初始化:
通過子類引用父類靜態(tài)變量,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過其子類來引用父類中定義的靜態(tài)字段只會(huì)初始化父類。
通過數(shù)組定義來引用類。SuperClass[] sca = new SuperClass[];。
引用常量,常量在編輯階段會(huì)存入調(diào)用類的常用池中,本質(zhì)上并沒有直接引用到定義常量的類。
類加載的過程 加載在加載階段,虛擬機(jī)需要完成下面三件事:
通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法去對(duì)這個(gè)類的各種數(shù)據(jù)的訪問入口。
驗(yàn)證驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前的虛擬機(jī)要求,并且不會(huì)危害虛擬機(jī)自身的安全。
從整體看,驗(yàn)證階段大致上會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:
文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。(魔數(shù)、主、次版本號(hào)等)
元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語義分析,驗(yàn)證點(diǎn):是否有父類、是否繼承了不被允許繼承的類、如果不是抽象類,是否實(shí)現(xiàn)了其父類或接口之中要求實(shí)現(xiàn)的所有方法。
字節(jié)碼驗(yàn)證:通過數(shù)據(jù)流和控制分析確定程序的語義是否合法、符合邏輯。
符號(hào)引用驗(yàn)證:發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接飲用的時(shí)候,這個(gè)轉(zhuǎn)化動(dòng)作在解析階段發(fā)生。符號(hào)引用可以看做對(duì)類自身以外(常量池中各種符號(hào)引用)的信息進(jìn)行匹配性檢驗(yàn):能夠通過字符串描述的全限定名找到對(duì)應(yīng)的類、方法、字段以及訪問性能否被當(dāng)前類訪問。
準(zhǔn)備正式為類變量分配內(nèi)存并設(shè)置初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。同時(shí)設(shè)置變量的初始值(零值)。
解析將常量池中的符號(hào)引用替換成直接飲用的過程。
符號(hào)引用:以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。
直接引用:直接指向目標(biāo)的執(zhí)行、相對(duì)偏移量或能間接定位到目標(biāo)的句柄。
初始化開始執(zhí)行類中定義的java程序代碼,根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者說執(zhí)行類構(gòu)造器
接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成
虛擬機(jī)保證一個(gè)類的
虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中“通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到j(luò)ava虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。
對(duì)于任意一個(gè)類,都需要由加載它的加載器和這個(gè)類本身確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器都擁有一個(gè)獨(dú)立的類名稱空間。兩個(gè)類“相等”包括代表類的Class對(duì)象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,也包括使用instanceof關(guān)鍵字作對(duì)象所屬關(guān)系判定等情況。
從Java虛擬機(jī)的角度來講,只存在兩種不同的類加載器:
啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類加載器使用C++語言實(shí)現(xiàn),是虛擬機(jī)自身的一部分。
所有其他的類加載器:由Java語言實(shí)現(xiàn)獨(dú)立于虛擬機(jī)外部,并且全部繼承自抽象類java.lang.ClassLoader。
從Java開發(fā)人員的角度來看,類加載器劃分為更細(xì)致一些:
啟動(dòng)類加載器(Bootstrp ClassLoader):負(fù)責(zé)將存在
擴(kuò)展類加載器(Extemsion ClassLoader):由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載
應(yīng)用程序類加載器(Application ClassLoader):這個(gè)類加載器由sun.misc.Launcher$App-ClassLoader實(shí)現(xiàn)。由于這個(gè)類加載器是Classloader中的getSystemClassLoader()的返回值,所以一般也稱為系統(tǒng)類加載器。負(fù)責(zé)加載用戶路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個(gè)類加載器。如果應(yīng)用程序沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
雙親委派模型應(yīng)用程序都是由三種類加載器互相配合進(jìn)行加載的,如果有必要還可以加入自己定義的類加載器,這些類加載器之間的關(guān)系一般如下:
圖中展示的類加載器之間的這種層次關(guān)系,稱為類加載器的雙親委派模型,雙親委派模型除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器,這里加載器之間的父子關(guān)系一般不會(huì)以繼承(Inheritance)的關(guān)系來實(shí)現(xiàn),而是都是用組合(Composition)關(guān)系來服用父加載器的代碼。
如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先會(huì)把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有父加載器反饋?zhàn)约簾o法嘗試完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。
Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。
例如類java.lang.Object存放在rt.jar中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器來進(jìn)行加載,因此Object類在程序的各種加載器環(huán)境中都是一個(gè)類。相反如果沒有雙親委派模型,如果用戶自己編寫了一個(gè)稱為java.lang.Object的類,并放在程序的ClassPath中,那么系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類。
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71621.html
摘要:由虛擬機(jī)加載的類,被加載到虛擬機(jī)內(nèi)存中之后,虛擬機(jī)會(huì)讀取并執(zhí)行它里面存在的字節(jié)碼指令。虛擬機(jī)中執(zhí)行字節(jié)碼指令的部分叫做執(zhí)行引擎。 什么是Java虛擬機(jī)? 作為一個(gè)Java程序員,我們每天都在寫Java代碼,我們寫的代碼都是在一個(gè)叫做Java虛擬機(jī)的東西上執(zhí)行的。但是如果要問什么是虛擬機(jī),恐怕很多人就會(huì)模棱兩可了。在本文中,我會(huì)寫下我對(duì)虛擬機(jī)的理解。因?yàn)槟芰λ?,可能有些地方描述的不夠?..
摘要:虛擬機(jī)發(fā)展史注本文大部分摘自深入理解虛擬機(jī)第二版作為一名開發(fā)人員,不能局限于語言規(guī)范,更需要對(duì)虛擬機(jī)規(guī)范有所了解。虛擬機(jī)規(guī)范有多種實(shí)現(xiàn),其中是和中所帶的虛擬機(jī),也是目前使用范圍最廣的虛擬機(jī)。世界第一款商用虛擬機(jī)。號(hào)稱世界上最快的虛擬機(jī)。 Java虛擬機(jī)發(fā)展史 注:本文大部分摘自《深入理解Java虛擬機(jī)(第二版)》 作為一名Java開發(fā)人員,不能局限于Java語言規(guī)范,更需要對(duì)Java虛...
摘要:虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)分為以下幾個(gè)部分。程序計(jì)數(shù)器也是在虛擬機(jī)規(guī)范中唯一沒有規(guī)定任何異常情況的區(qū)域。在方法運(yùn)行期間不會(huì)改變局部變量表的大小。長度在位和位的虛擬機(jī)中,分別為官方稱它為。 Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū) 詳解 2.1 概述 本文參考的是周志明的 《深入理解Java虛擬機(jī)》第二章 ,為了整理思路,簡單記錄一下,方便后期查閱。 2.2 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)在Java程序運(yùn)行時(shí)...
摘要:此處指定的虛擬機(jī)與平臺(tái)兼容,并支持語言規(guī)范中指定的編程語言。第章說明了虛擬機(jī)的指令集,按字母順序顯示操作碼助記符。 介紹 一點(diǎn)歷史 Java?編程語言是一種通用的、并發(fā)的、面向?qū)ο蟮恼Z言,它的語法類似于C和C++,但它省略了許多使C和C++復(fù)雜、混亂和不安全的特性。最初開發(fā)Java平臺(tái)是為了解決為聯(lián)網(wǎng)的消費(fèi)者設(shè)備構(gòu)建軟件的問題,它旨在支持多種主機(jī)架構(gòu),并允許安全交付軟件組件,為了滿足這...
摘要:虛擬機(jī)在執(zhí)行程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。棧幀棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素。棧幀的概念結(jié)構(gòu)如下運(yùn)行時(shí)數(shù)據(jù)區(qū)腦圖高 這里我們先說句題外話,相信大家在面試中經(jīng)常被問到介紹Java內(nèi)存模型,我在面試別人時(shí)也會(huì)經(jīng)常問這個(gè)問題。但是,往往都會(huì)令我比較尷尬,我還話音未落,面試者就會(huì)背誦一段(Java虛擬...
摘要:原始類型和值虛擬機(jī)支持的原始數(shù)據(jù)類型是數(shù)字類型布爾類型和類型。,其值為位帶符號(hào)的二進(jìn)制補(bǔ)碼整數(shù),其默認(rèn)值為零。 Java虛擬機(jī)的結(jié)構(gòu) 本文檔指定了一個(gè)抽象機(jī)器,它沒有描述Java虛擬機(jī)的任何特定實(shí)現(xiàn)。 要正確實(shí)現(xiàn)Java虛擬機(jī),你只需要能夠讀取類文件格式并正確執(zhí)行其中指定的操作,不屬于Java虛擬機(jī)規(guī)范的實(shí)現(xiàn)細(xì)節(jié)會(huì)不必要地限制實(shí)現(xiàn)者的創(chuàng)造力。例如,運(yùn)行時(shí)數(shù)據(jù)區(qū)的內(nèi)存布局、使用的垃圾收集...
閱讀 2500·2021-11-23 09:51
閱讀 549·2019-08-30 13:59
閱讀 1855·2019-08-29 11:20
閱讀 2555·2019-08-26 13:41
閱讀 3268·2019-08-26 12:16
閱讀 756·2019-08-26 10:59
閱讀 3351·2019-08-26 10:14
閱讀 627·2019-08-23 17:21