成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

java虛擬機(jī)

趙春朋 / 2384人閱讀

摘要:虛擬機(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 詳解

HotSpot虛擬機(jī)對(duì)象 對(duì)象的創(chuàng)建

在語言層上,創(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)存布局

對(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.jar
jstack

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        5597736  
   2:         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 sr = new SoftReference(obj);
    obj = null;
弱引用

通過WeakReference類來實(shí)現(xiàn),只能存活到下一次垃圾收集發(fā)生之前。

    Object obj = new Object();
    WeakReference wr = new WeakReference(obj);
    obj = null;

WeakHashMap 的 Entry 繼承自 WeakReference,主要用來實(shí)現(xiàn)緩存。

private static class Entry extends WeakReference implements Map.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 wr = new PhantomReference(obj, null);
    obj = null;
垃圾收集算法
標(biāo)記清除

算法分為“標(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)造器()方法的過程。

()方法和構(gòu)造函數(shù)不同,虛擬機(jī)保證子類的()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢。

()方法并不是必需的,如果累沒有靜態(tài)語句塊,也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成()方法。

接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成()方法,但是與類不同的是,執(zhí)行借口的()方法先執(zhí)行父接口的,只有當(dāng)父接口定義的變量被使用時(shí),父接口才會(huì)初始化。

虛擬機(jī)保證一個(gè)類的()方法在多線程環(huán)境中被正確的加鎖、同步。

類加載器

虛擬機(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é)將存在lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別)類庫加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無法被Java程序直接飲用,用戶在編寫自定義類加載器時(shí),如果需要把加載請(qǐng)求委派給引導(dǎo)類加載器,那直接使用null替代即可。

擴(kuò)展類加載器(Extemsion ClassLoader):由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載libext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用擴(kuò)展類加載器。

應(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類。

實(shí)現(xiàn)
    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

相關(guān)文章

  • 深入理解Java虛擬機(jī)到底是什么

    摘要:由虛擬機(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)槟芰λ?,可能有些地方描述的不夠?..

    宋華 評(píng)論0 收藏0
  • 《深入理解Java虛擬機(jī)》(一)Java虛擬機(jī)發(fā)展史

    摘要:虛擬機(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虛...

    張春雷 評(píng)論0 收藏0
  • 《深入理解Java虛擬機(jī)》(二)Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

    摘要:虛擬機(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í)...

    draveness 評(píng)論0 收藏0
  • Java虛擬機(jī)規(guī)范(介紹)

    摘要:此處指定的虛擬機(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),并允許安全交付軟件組件,為了滿足這...

    chnmagnus 評(píng)論0 收藏0
  • 【JVM從小白學(xué)成大佬】2.Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

    摘要:虛擬機(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虛擬...

    shuibo 評(píng)論0 收藏0
  • Java虛擬機(jī)規(guī)范(Java虛擬機(jī)的結(jié)構(gòu))

    摘要:原始類型和值虛擬機(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)存布局、使用的垃圾收集...

    bang590 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<