內存中較小的內存空間,經過計數器的值能夠選取下一條執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。html
線程私有,生命週期跟線程相同。java
若是正在執行一個Native方法,那麼這個計數器值將爲空。程序員
線程私有,生命週期跟線程相同。算法
每一個方法在執行同時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。apache
在Java虛擬機規範中,對這個區域規定了兩種異常狀況:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常;
若是虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常。ubuntu
跟虛擬機棧所發揮的做用類似,區別在於虛擬機棧爲虛擬機執行Java(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。數組
用於存放對象實例,是Java虛擬機所管理的內存中最大的一塊,同時也是全部線程共享的一塊內存區域。瀏覽器
由於Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱爲「GC"堆。因爲如今收集器基本都採用分代收集算法,因此Java堆還能夠細分爲緩存
當一個對象被建立時,它首先進入新生代,以後有可能被轉移到老年代中。安全
新生代存放着大量的生命很短的對象,所以新生代在三個區域中垃圾回收的頻率最高。爲了更高效地進行垃圾回收,把新生代繼續劃分紅如下三個空間:
與Java堆同樣,各個線程共享的內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即便編譯器編譯後的代碼等數據。
方法區的一部分,用於存放編譯器生成的各類字面量和符號引用。
運行時常量池相對於class文件常量池的另一個重要特徵是具有動態性,Java語言並不要求常量必定只有編譯期才能產生,也就是並不是預置入class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的即是String類的intern()方法。
在JDK1.4中新加入了NIO類,引入了一種基於通道與緩衝區的I/O方法,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。
堆外內存之 DirectByteBuffer 詳解
在語言層上,建立對象一般僅僅是一個new關鍵字而已,而當虛擬機遇到一條new執行時,將由一下步驟:
在堆中分配內存
劃分可用空間:
併發問題
對象在內存中儲存的佈局能夠分爲3塊區域:
對象頭
實例數據
對齊填充
內存溢出out of memory,是指程序在申請空間時,沒有足夠的內存空間供其使用,出現了Out of memory error。
當new一個對象或者數組時,若是超出了Jvm的head內存最大限制就會爆出異常。
僞代碼:
while(ture){ new Object(); }
在Java虛擬機規範中,對這個棧規定了兩種異常狀況,若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOutFlowError異常,若是虛擬機能夠動態擴展(當前大部分Java虛擬機均可動態擴展,只不過Java虛擬機規範中也容許固定長度的虛擬機棧),當擴展時沒法申請獲得足夠的內存時將會拋出OutOfMemory。
線程中的stack是線程私有的,默認大小一般爲1M,能夠經過-Xss來設置,-Xss越大,則線程獲取的內存越大。
常見問題在線程內過分的調用函數,函數調用會消耗棧空間。
僞代碼:
public void SOFETest(){ SOFETest(); }
Java的棧空間被全部線程分配成一塊一塊的,每一個線程只佔一塊。而Jvm的棧空間的最小分配單位有-Xss來決定。-Xss有兩個語義,即定義每一個線程的棧大小,也定義了虛擬機的最小棧內存的分配單位。
若是申請的線程沒有得到棧空間能夠分配了就會拋出OutOfMemoryError。表示棧空間不足,溢出異常。
代碼:該代碼可能致使JVM沒法申請獲得太多的棧內存而致使操做系統由於棧空間不足假死。
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(); } }
內存泄漏memory leak,指程序在申請內存以後,沒法釋放已申請的內存空間,一次內存泄漏危害能夠忽略,屢次memory leak將致使oom。
內存泄漏是指你向系統申請分配內存進行使用(new),但是使用完了之後卻不歸還(delete),結果你申請到的那塊內存你本身也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給須要的程序。
該部份內容轉自:JVM性能調優監控工具jps、jstack、jmap、jhat、jstat、hprof使用詳解
jps主要用來輸出JVM中運行的進程狀態信息。語法格式以下:
jps [options] [hostid]
若是不指定hostid就默認爲當前主機或服務器。
命令行參數選項說明以下:
-q 不輸出類名、Jar名和傳入main方法的參數 -m 輸出傳入main方法的參數 -l 輸出main類或Jar的全限名 -v 輸出傳入JVM的參數
好比下面:
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.jar
jstack主要用來查看某個Java進程內的線程堆棧信息。語法格式以下:
jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-ip
命令行參數選項說明以下:
-l long listings,會打印出額外的鎖信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況 -m mixed mode,不只會輸出Java堆棧信息,還會輸出C/C++堆棧信息(好比Native方法)
jstack能夠定位到線程堆棧,根據堆棧信息咱們能夠定位到具體代碼,因此它在JVM性能調優中使用得很是多。下面咱們來一個實例找出某個Java進程中最耗費CPU的Java線程並定位堆棧信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java進程ID,我部署在服務器上的Java應用名稱爲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
獲得進程ID爲21711,第二步找出該進程內最耗費CPU的線程,可使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我這裏用第三個,輸出以下:
TIME列就是各個Java線程耗費的CPU時間,CPU時間最長的是線程ID爲21742的線程,用
printf "%x\n" 21742
獲得21742的十六進制值爲54ee,下面會用到。
OK,下一步終於輪到jstack上場了,它用來輸出進程21711的堆棧信息,而後根據線程ID的十六進制值grep,以下:
root@ubuntu:/# jstack 21711 | grep 54ee "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
能夠看到CPU消耗在PollIntervalRetrySchedulerThread這個類的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) { } }
它是輪詢任務的空閒等待代碼,上面的sigLock.wait(timeUntilContinue)就對應了前面的Object.wait()。
jmap用來查看堆內存使用情況,通常結合jhat使用。
jmap語法格式以下:
jmap [option] pid jmap [option] executable core jmap [option] [server-id@]remote-hostname-or-ip
若是運行在64位JVM上,可能須要指定-J-d64命令選項參數。
jmap -permstat pid
打印進程的類加載器和類加載器加載的持久代對象信息,輸出:類加載器名稱、對象是否存活(不可靠)、對象地址、父類加載器、已加載的類大小等信息,以下圖:
使用jmap -heap pid查看進程堆內存使用狀況,包括使用的GC算法、堆配置參數和各代中堆內存使用狀況。好比下面的例子:
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查看堆內存中的對象數目、大小統計直方圖,若是帶上live則只統計活對象,以下:
root@ubuntu:/# jmap -histo:live 21711 | more num #instances #bytes class name ---------------------------------------------- 1: 38445 5597736 <constMethodKlass> 2: 38445 5237288 <methodKlass> 3: 3500 3749504 <constantPoolKlass> 4: 60858 3242600 <symbolKlass> 5: 3500 2715264 <instanceKlassKlass> 6: 2796 2131424 <constantPoolCacheKlass> 7: 5543 1317400 [I 8: 13714 1010768 [C 9: 4752 1003344 [B 10: 1225 639656 <methodDataKlass> 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 <objArrayKlassKlass> 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是對象類型,說明以下:
B byte C char D double F float I int J long Z boolean [ 數組,如[I表示int[] [L+類名 其餘對象
還有一個很經常使用的狀況是:用jmap把進程內存使用狀況dump到文件中,再用jhat分析查看。jmap進行dump命令格式以下:
jmap -dump:format=b,file=dumpFileName pid
我同樣地對上面進程ID爲21711進行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這種參數指定最大堆內存,即jhat -J-Xmx512m -port 9998 /tmp/dump.dat。而後就能夠在瀏覽器中輸入主機地址:9998查看了:
上面紅線框出來的部分你們能夠本身去摸索下,最後一項支持OQL(對象查詢語言)。
語法格式以下:
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmid是Java虛擬機ID,在Linux/Unix系統上通常就是進程ID。interval是採樣時間間隔。count是採樣數目。好比下面輸出的是GC信息,採樣時間間隔爲250ms,採樣數爲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堆內存佈局:
能夠看出:
堆內存 = 年輕代 + 年老代 + 永久代 年輕代 = Eden區 + 兩個Survivor區(From和To)
如今來解釋各列含義:
S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used) EC、EU:Eden區容量和使用量 OC、OU:年老代容量和使用量 PC、PU:永久代容量和使用量 YGC、YGT:年輕代GC次數和GC耗時 FGC、FGCT:Full GC次數和Full GC耗時 GCT:GC總耗時
hprof可以展示CPU使用率,統計堆內存使用狀況。
語法格式以下:
java -agentlib:hprof[=options] ToBeProfiledClass java -Xrunprof[:options] ToBeProfiledClass javac -J-agentlib:hprof[=options] ToBeProfiledClass
完整的命令選項以下:
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=<file> write data to file java.hprof[.txt] net=<host>:<port> send data over a socket off depth=<size> stack trace depth 4 interval=<ms> sample interval in ms 10 cutoff=<value> 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 <file> y verbose=y|n print messages about dumps y
來幾個官方指南上的實例。
CPU Usage Sampling Profiling(cpu=samples)的例子:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 Hello
上面每隔20毫秒採樣CPU消耗信息,堆棧深度爲3,生成的profile文件名稱是java.hprof.txt,在當前目錄。
CPU Usage Times Profiling(cpu=times)的例子,它相對於CPU Usage Sampling Profile可以得到更加細粒度的CPU消耗信息,可以細到每一個方法調用的開始和結束,它的實現使用了字節碼注入技術(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能生成更詳細的Heap Dump信息:
javac -J-agentlib:hprof=heap=dump Hello.java
雖然在JVM啓動參數中加入-Xrunprof:heap=sites參數能夠生成CPU/Heap Profile文件,但對JVM性能影響很是大,不建議在線上服務器環境使用。
程序計數器、虛擬機棧和本地方法棧這三個區域屬於線程私有的,只存在於線程的生命週期內,線程結束以後也會消失,所以不須要對這三個區域進行垃圾回收。垃圾回收主要是針對 Java 堆和方法區進行。
給對象添加一個引用計數器,每當有一個地方引用它,計數器值就加1;引用時效時,計算器值就減1;當計數器值爲0的對象就是不可能再被使用的。
當兩個對象相互引用時,此時引用計數器的值永遠不爲0,致使沒法對它們進行垃圾回收。
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(); } }
以GC Roots爲起始點,從這些節點開始向下搜索,可以搜索到的對象都是存活的,不可達的對象則爲不可用。
在Java語言中,可做爲GC Roots的對象包括下面幾種:
不管是引用計數算法仍是可達性分析算法判斷對象是否存活都與引用有關。在JDK1.2以後,Java對引用的概念進行了擴充,劃分爲強度不一樣的四個的引用類型。
經過new來建立對象的引用類型,被強引用的對象永遠不會被垃圾收集器回收。
Object obj = new Object();
經過SortReference類來實現,只有在內存不足的時候纔會被回收。
Object obj = new Object(); SoftReference<Object> sr = new SoftReference<Object>(obj); obj = null;
經過WeakReference類來實現,只能存活到下一次垃圾收集發生以前。
Object obj = new Object(); WeakReference<Object> wr = new WeakReference<Object>(obj); obj = null;
WeakHashMap 的 Entry 繼承自 WeakReference,主要用來實現緩存。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
Tomcat 中的 ConcurrentCache 就使用了 WeakHashMap 來實現緩存功能。ConcurrentCache 採起的是分代緩存,常用的對象放入 eden 中,而不經常使用的對象放入 longterm。eden 使用 ConcurrentHashMap 實現,longterm 使用 WeakHashMap,保證了不常使用的對象容易被回收。
public final class ConcurrentCache<K, V> { private final int size; private final Map<K, V> eden; private final Map<K, V> 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); } }
也稱爲幽靈引用或者幻影引用,是最弱的一種引用關係。
經過PhantomReference類來實現,爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。
Object obj = new Object(); PhantomReference<Object> wr = new PhantomReference<Object>(obj, null); obj = null;
算法分爲「標記」跟「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成以後統一回收全部被標記的對象。
不足:
將內存分爲大小相等的兩塊,每次只使用其中的一塊,當這塊內存用完了,就將還存活的對象負責到另外一塊上面,而後再把一是要難過過得內存空間一次清理掉。
不足:
首先標記出全部須要回收的對象,而後將全部存活的對象都向一端移動,最後清理掉端邊界之外的內存。
根據對象的存活週期將內存劃分爲幾塊。通常將Java堆分爲新生代跟老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。
若是說手機算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。
上圖展現了7種不一樣分代的收集器,若是兩個收集器之間存在連線,就說明它們能夠搭配使用。
知道目前爲止尚未最好的收集器出現,更加沒有萬能的收集器,因此咱們只能選擇對具體應用最合適的收集器。
最基本、最悠久的收集器,單線程收集器,複製算法
在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。
相比較與其餘收集器,它具備:簡單而高效的特色,對於限定CPU環境來講,Serial收集器沒有線程交互的開銷。
依然是虛擬機運行在Client模式下的默認新生代收集器。
Serial的多線程版本、並行,複製算法。
是許多運行在Server模式下的虛擬機中首選的新生代收集器,由於目前除了Serial收集器外,只有它能與CMS收集器配合使用。
默認開啓的收集線程數與CPU的數量相同,在CPU很是多的環境下,可使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。
新生代、並行的多線程收集器,複製算法。
Parallel Scavenge收集器的目標是達到一個可控制的吞吐量:CPU用戶運行用戶代碼的時間與CPU的執行時間,即吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。
停頓時間越短越適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗,而高吞吐量能夠高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。
Parallel Scavenge收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。
Serial的老年代版本,單線程,標記-整理算法。
這個收集器的主要意義在於給Client模式下的虛擬機使用,若是在Server默認下,它還有兩大用途:
parallel Scavenge的老年代版本,多線程,標記-整理算法,JDK1.6以後提供。
在注重吐吞量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。
CMS(Concurrent Mark Sweep),從名字來就能夠看出,基於標記-清除算法。
併發收集、低停頓。
運算過程分爲4個步驟:
整個過程耗時最長的併發標記和併發清除過程收集器線程均可以與用戶安城一塊兒工做。
CMS還遠達不到完美的程度,還有如下3個缺點:
一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予它的使命是將來能夠替代掉JDK1.5中發佈的CMS收集器。
與其餘收集器相比,G1具備如下特色:
G1將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留新生代和老年代的概念,可是新生代和老年代再也不是物理隔離級別,它們都是一部分Region的集合。
G1收集器之因此可以創建可預測的停頓時間模型,是由於它能夠有計劃低避免在整個Java堆中進行全區域的垃圾回收。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region。
虛擬機使用Remembered Set來避免全棧掃描,G1中每一個Region都有一個與之對應的Remembered Set,用來記錄該Region對象的引用對象所在的Region。
若是不計算Remembered Set的操做,G1收集器的運做大體可分爲:
Full GC的觸發條件
對象的內存分配規則並非百分百固定的,其細節取決於當前使用的是哪種垃圾收集器組合,還有虛擬機中與內存有關的參數設置。
虛擬機規範嚴格規定了有且只有下面5種狀況必須當即對類進行初始化:
如下狀況不會初始化:
SuperClass[] sca = new SuperClass[];
。
在加載階段,虛擬機須要完成下面三件事:
驗證是鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前的虛擬機要求,而且不會危害虛擬機自身的安全。
從總體看,驗證階段大體上會完成4個階段的檢驗動做:
正式爲類變量分配內存並設置初始值的階段,這些變量所使用的內存都將在方法區中進行分配。同時設置變量的初始值(零值)。
將常量池中的符號引用替換成直接飲用的過程。
開始執行類中定義的java程序代碼,根據程序員經過程序制定的主觀計劃去初始化類變量和其餘資源,或者說執行類構造器<clinit>()方法的過程。
虛擬機設計團隊把類加載階段中「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到java虛擬機外部去實現,以便讓應用程序本身決定如何獲取所須要的類。實現這個動做的代碼模塊稱爲「類加載器」。
對於任意一個類,都須要由加載它的加載器和這個類自己確立其在Java虛擬機中的惟一性,每個類加載器都擁有一個獨立的類名稱空間。兩個類「相等」包括表明類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關係斷定等狀況。
從Java虛擬機的角度來說,只存在兩種不一樣的類加載器:
從Java開發人員的角度來看,類加載器劃分爲更細緻一些:
應用程序都是由三種類加載器互相配合進行加載的,若是有必要還能夠加入本身定義的類加載器,這些類加載器之間的關係通常以下:
圖中展現的類加載器之間的這種層次關係,稱爲類加載器的雙親委派模型,雙親委派模型除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父類加載器,這裏加載器之間的父子關係通常不會以繼承(Inheritance)的關係來實現,而是都是用組合(Composition)關係來服用父加載器的代碼。
若是一個類加載器收到了類加載的請求,它首先會把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有父加載器反饋本身沒法嘗試完成這個加載請求時,子加載器纔會嘗試本身去加載。
Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。
例如類java.lang.Object存放在rt.jar中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器來進行加載,所以Object類在程序的各類加載器環境中都是一個類。相反若是沒有雙親委派模型,若是用戶本身編寫了一個稱爲java.lang.Object的類,並放在程序的ClassPath中,那麼系統中將會出現多個不一樣的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; } }