Android性能優(yōu)化篇之(二)序言及JVM篇

前言

在內(nèi)存方面,相比于C/C++程序員拘荡,咱們java系程序員算是比較幸運的笨忌,因為對于內(nèi)存的分配和回收,都交給了JVM來處理了,而不需要手動在代碼中去完成官疲。有了虛擬機內(nèi)存管理機制,也就不那么容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出的問題了亮隙。不那么容易出現(xiàn)途凫,并不代表就不會出現(xiàn)。正是由于程序員將內(nèi)存的控制大權(quán)交了出去溢吻,那么一旦出現(xiàn)了內(nèi)存泄漏和內(nèi)存溢出的問題维费,如果虛擬機如何分配內(nèi)存的工作機制不了解,那這就成了一個難以處理的問題了促王。所以說犀盟,放權(quán)可以,但不能完全失去控制蝇狼,否則阅畴,就有被架空的危險,出了問題迅耘,你只能干

本文的主要內(nèi)容如下:
image

一贱枣、內(nèi)存的家庭住址

我們這么關(guān)心的內(nèi)存,到底是何方神圣呢颤专?看圖比看文字舒服纽哥,咱們先上圖:


image

似曾相識吧!這個就是第一節(jié)中JVM執(zhí)行java程序的流程栖秕。ClassLoader加載完畢.class文件后春塌,交由執(zhí)行引擎執(zhí)行。整個程序執(zhí)行過程中簇捍,JVM會用一段空間來存儲執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息只壳,這段空間一般被稱作Runtime Data Area (運行時數(shù)據(jù)區(qū)),這就是咱們常說的JVM內(nèi)存垦写,我們常說到的內(nèi)存管理就是針對這段空間進行管理吕世。這樣,我們就找到內(nèi)存的家庭住址了梯投。

二命辖、內(nèi)存大家庭中都有哪些成員呢?

咱們?nèi)匀幌壬蠄D:


image

Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存(運行時數(shù)據(jù)區(qū))劃分為若干個不同的數(shù)據(jù)區(qū)域分蓖。這些區(qū)域都有各自的用途尔艇,以及創(chuàng)建和銷毀的時間,有的區(qū)域隨著虛擬機進程的啟動而存在么鹤,有些區(qū)域則依賴用戶線程的啟動和結(jié)束而建立和銷毀终娃。根據(jù)《Java虛擬機規(guī)范(Java SE 7版)》的規(guī)定,Java虛擬機所管理的內(nèi)存包含了上圖中的5個區(qū)域:程序計數(shù)器蒸甜,虛擬機棧棠耕,本地方法棧余佛,GC堆,方法區(qū)窍荧。

三辉巡、內(nèi)存的家庭成員分別都是干嘛的呢?

這一部分比較理論蕊退,文字描述比較多郊楣,但是如果有一定的基礎(chǔ)而且認(rèn)真讀的話,其實很容易懂的瓤荔,同時要想更好地理解內(nèi)存這方面的知識净蚤,也需要耐著性子好好看。

1输硝、程序計數(shù)器

程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間今瀑,也有的稱為PC寄存器。

學(xué)過匯編語言或者計算機機構(gòu)與組成原理的童鞋腔丧,應(yīng)該對著個概念不陌生放椰,在匯編語言中,程序計數(shù)器是指CPU中的寄存器愉粤,它保存的是程序當(dāng)前執(zhí)行的指令的地址砾医,當(dāng)CPU需要執(zhí)行指令的時候,就從中取出這條地址衣厘,并根據(jù)這條地址獲取到指令如蚜。獲取到指令后,程序計數(shù)器會自動+1或者根據(jù)轉(zhuǎn)移指針得到下一條指令的地址影暴,如此循環(huán)错邦,直到執(zhí)行完所有的指令。字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令型宙,分支撬呢、循環(huán)、跳轉(zhuǎn)妆兑、異常處理魂拦、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。

雖然JVM中的程序計數(shù)器并不像匯編語言中的程序計數(shù)器一樣是物理概念上的CPU寄存器搁嗓,但是JVM中的程序計數(shù)器的功能跟匯編語言中的程序計數(shù)器的功能在邏輯上是等同的芯勘,也就是說是用來指示執(zhí)行哪條指令的。

由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的腺逛,在任何一個確定的時刻荷愕,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置安疗,每條線程都需要有一個獨立的程序計數(shù)器抛杨,各條線程之間計數(shù)器互不影響,獨立存儲茂契,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存蝶桶。

如果線程執(zhí)行的是非native方法,則程序計數(shù)器中保存的是當(dāng)前需要執(zhí)行的指令的地址掉冶;如果線程執(zhí)行的是native方法,則程序計數(shù)器中的值為空(undefined)脐雪。這塊內(nèi)存中存儲的數(shù)據(jù)所占空間的大小不會隨程序的執(zhí)行而發(fā)生改變厌小,所以,此內(nèi)存區(qū)域不會發(fā)生內(nèi)存溢出(OutOfMemory)問題战秋,該內(nèi)存區(qū)域也是唯一一個在JVM規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域璧亚。

2、Java虛擬機棧

Java虛擬機棧(Java Vitual Machine Stack)簡稱為Java棧脂信,也就是我們常常說的棧內(nèi)存癣蟋。它是Java方法執(zhí)行的內(nèi)存模型。

image

如上圖所示狰闪,Java棧中存放的是一個個的棧幀疯搅,每個棧幀對應(yīng)的是一個被調(diào)用的方法。每一個棧幀中包括了如下部分:局部變量表(Local Variables)埋泵、操作數(shù)棧(Operand Stack)幔欧、指向當(dāng)前方法所屬的類的運行時常量池(運行時常量池的概念在方法區(qū)部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息丽声。每一個方法從調(diào)用直至執(zhí)行完成的過程礁蔗,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。因此可以知道雁社,線程當(dāng)前執(zhí)行的方法所對應(yīng)的棧必定位于Java虛擬機棧的頂部浴井。在Java虛擬機規(guī)范中,對Java棧區(qū)域規(guī)定了兩種異常狀況:1)如果線程請求的棧深度大于虛擬機所允許的深度霉撵,將拋出棧內(nèi)存溢出(StackOverflowError)異常磺浙;2) 如果虛擬機棧可以動態(tài)擴展喊巍,而且擴展時無法申請到足夠的內(nèi)存屠缭,就會拋出OutOfMemoryError異常。到這里崭参,我們就容易理解呵曹,在使用遞歸方法的時候,如果這個方法的層次太深,就會導(dǎo)致Java棧中的棧幀過多奄喂,從而導(dǎo)致棧內(nèi)存溢出铐殃。這部分空間的分配和釋放都是由系統(tǒng)自動實施的,而不需要程序員去管理了跨新。

經(jīng)常有人把Java的內(nèi)存區(qū)分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack)富腊,這種分法比較粗糙,實際劃分遠(yuǎn)比這復(fù)雜域帐。之所以這種分法能夠流行赘被,說明大多數(shù)程序員最關(guān)注的、與對象內(nèi)存分配關(guān)系最密切的內(nèi)存區(qū)域主要是這兩塊肖揣。下面咱們對棧幀再做細(xì)致的描述民假。

(1)局部變量表。顧名思義龙优,它是一組變量值存儲空間羊异,用于存放對應(yīng)方法的形參和方法內(nèi)部定義的非static局部變量。其中存放的數(shù)據(jù)的類型有如下幾種:a)基本數(shù)據(jù)類型彤断。boolean野舶,char,byte宰衙,short平道,int,long菩浙,float巢掺,double,java中定義的8種基本數(shù)據(jù)類型劲蜻。b)對象引用(reference)陆淀。不是對象本身,而是指向?qū)ο髮嵗囊粋€引用先嬉,這個就是Java中的指針轧苫,他的值為一個地址,在堆中該實例的首地址疫蔓。例如含懊,Date date = new Date(...);new Date(...)表示在堆內(nèi)存中開辟了一個空間來存儲該實例對象衅胀,而date就是對象的引用岔乔,局部變量表中存儲的就是指向堆中該對象的首地址。c) retunAddress類型滚躯。它指向了一條字節(jié)碼指令的地址雏门。這一點沒有查得很明白嘿歌,筆者估計應(yīng)該是方法執(zhí)行完畢后,返回給程序計數(shù)器的當(dāng)前指令的地址吧茁影。局部變量表所需的內(nèi)存空間在編譯期間完成分配宙帝,即在Java程序被編譯成.class文件時,就確定了所需要分配的最大局部變量表的容量募闲。當(dāng)進入一個方法時步脓,這個方法需要在棧中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變其大小浩螺。

(2)操作數(shù)棧靴患。其又常被稱為操作棧,它的最大深度也是在編譯的時候就確定了要出。當(dāng)一個方法開始執(zhí)行時蚁廓,它的操作棧是空的,在方法執(zhí)行過程中厨幻,會有各種字節(jié)碼指令(比如:加操作、賦值運算等)向操作棧中寫入內(nèi)容腿时,也就是入棧况脆,計算完畢后提取內(nèi)容,即出棧操作批糟。學(xué)過數(shù)據(jù)結(jié)構(gòu)的童鞋格了,一定對表達式求職問題不會陌生,棧最典型的一個應(yīng)用就是用來對表達式求值徽鼎。一個線程執(zhí)行方法的過程中盛末,實際上就是不斷執(zhí)行語句的過程,歸根到底就是進行計算的過程否淤,可以說悄但,程序中的所有計算過程都是在借助于操作數(shù)棧來完成的。Java虛擬機的解釋執(zhí)行引擎也被稱為“基于棧的執(zhí)行引擎”石抡,這里“楅芟”就是操作數(shù)棧。因此我們也稱Java虛擬機是基于棧的啰扛,這點不同于Android虛擬機嚎京,Android虛擬機是基于寄存器的∫猓基于棧的指令集最主要的優(yōu)點是可移植性強鞍帝,主要缺點是執(zhí)行速度相對較慢;而由于寄存器由硬件直接提供煞茫,所以基于寄存器指令集最主要的優(yōu)點是執(zhí)行速度快帕涌,主要缺點是可移植性差摄凡。

(3)指向當(dāng)前方法所屬的類的運行時常量池的引用。很多地方也稱這個部分為動態(tài)連接宵膨。每個棧幀都包含一個指向運行時常量池(在方法區(qū)中詳細(xì)介紹)的引用架谎,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接。Class文件的常量池中存在有大量的符號引用辟躏,字節(jié)碼中的方法調(diào)用指令就是以常量池中指向方法的符號引用為參數(shù)谷扣。這些符號引用,一部分會在類加載階段或一第一次使用的時候轉(zhuǎn)化為直接引用(如final捎琐,static域等)会涎,稱為靜態(tài)解析,另一部分將在每一次的運行期間轉(zhuǎn)化為直接引用瑞凑,這部分稱為動態(tài)連接末秃。簡單點說,就是因為在方法執(zhí)行的過程中有可能需要用到類中的常量籽御,所以需要有一個引用指向運行時常量练慕。

(4)方法返回地址。當(dāng)一個方法執(zhí)行完畢之后技掏,要返回之前調(diào)用它的地方铃将,因此在棧幀中必須保存一個方法返回地址。方法被執(zhí)行后哑梳,有兩種方式退出該方法:一種是執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令劲阎,也就是遇到了return,或者void函數(shù)執(zhí)行完畢鸠真;另外一種是遇到了異常悯仙,并且該異常沒有在方法體內(nèi)得到處理,即沒有用try-catch進行捕獲吠卷。無論是哪種退出方式锡垄,在退出后都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行撤嫩。方法返回時可能需要在幀中保存一些信息偎捎,用來幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)。一般來說序攘,方法正常退出時茴她,調(diào)用者的PC計數(shù)器的值就可以作為返回地址,棧幀中很可能保存了這個計數(shù)器值程奠。而方法異常退出時丈牢,返回地址是要通過異常處理來確定的,棧幀一般不會保存這部分信息瞄沙。方法退出的過程實際上等同于把當(dāng)前棧幀出棧己沛,因此退出時可能執(zhí)行的操作有:恢復(fù)上層局部變量表和操作數(shù)棧慌核,如果有返回值,則把它壓入調(diào)用者棧幀的操作數(shù)棧中申尼,調(diào)整程序計數(shù)器的值以指向方法方法調(diào)用指令后面的一條指令垮卓。

每個線程擁有自己的Java棧,調(diào)用自己的方法师幕,互不干擾粟按,屬于“私有內(nèi)存”。

3霹粥、本地方法棧(Native Method Stack)

本地方法棧與Java虛擬機棧的作用和原理非常相似灭将,區(qū)別在與前者為執(zhí)行Nativit方法服務(wù)的咧最,而后者是為執(zhí)行Java方法服務(wù)的驾锰。在JVM規(guī)范中對本地方法棧中方法使用的語言,使用方式和數(shù)據(jù)結(jié)構(gòu)并沒有強制規(guī)定粹庞,因此具體的虛擬機可以自由實現(xiàn)它浩淘。在HotSpot虛擬機中捌朴,直接把本地方法棧和Java棧合二而一了,而我們平時Java開發(fā)中张抄,最常用到的就是HotSpot虛擬機男旗。與Java虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常欣鳖。

4、GC堆

對于大多數(shù)應(yīng)用來說茴厉,Java堆(Java Heap)是Java虛擬機所管理的內(nèi)存中最大的一塊泽台。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建矾缓。此內(nèi)存區(qū)域的唯一目的就是存放對象實例怀酷,幾乎所有的對象實例都在這里分配。堆是被所有線程共享的嗜闻,在JVM中只有一個堆蜕依。這一點在Java虛擬機規(guī)范中的描述為:所有的對象實例以及數(shù)組都要在對上分配,但是隨著JIT編譯器(即時編譯器:是一種提高程序運行效率的方法琉雳,通常由兩種運行方式样眠,靜態(tài)編譯與動態(tài)編譯。靜態(tài)編譯是指執(zhí)行前全部翻譯為機器碼翠肘,動態(tài)編譯時指檐束,一句一句地邊翻譯邊運行)的發(fā)展與逃逸技術(shù)逐漸成熟,棧上分配束倍、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生被丧,所有的對象都分配在對上也漸漸變得不那么絕對了盟戏。

Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱作“GC堆”(Garbage Collected Heap)甥桂。如果還細(xì)分柿究,有新生代和老年代等的劃分,此處不詳細(xì)展開黄选,有興趣和需要深入的可以自行研究蝇摸。根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中糕簿,只要邏輯上是連續(xù)的即可探入,就像我們的磁盤空間一樣。在實現(xiàn)時懂诗,既可以實現(xiàn)成固定大小的蜂嗽,也可以是可擴展的,不過當(dāng)前主流還是可以擴展的(通過-Xmx和-Xms控制)殃恒。如果在堆中沒有內(nèi)存完成實例分配植旧,并且也無法再擴展時,將會拋出OutOfMemoryError異常离唐。

5病附、方法區(qū)

方法區(qū)(Method Area)在JVM中也是一個非常重要的區(qū)域,它與堆一樣亥鬓,是被線程共享的區(qū)域完沪,一般用來存儲不容易改變的數(shù)據(jù),所以一般也被稱為“永久代”嵌戈。在方法區(qū)中覆积,存儲了每個類的信息(包括類名,方法信息熟呛,字段信息)宽档、靜態(tài)變量、常量以及編譯器編譯后的代碼等庵朝。在Class文件中除了類的字段吗冤、方法、接口等描述信息外九府,還有運行時常量池椎瘟,用來儲存編譯期間生成的字面量和符號引用。在方法區(qū)中有一個非常重要的部分就是運行時常量池侄旬,它是每一個類或者接口的常量池的運行時表示形式降传,在類和接口被加載到JVM后,對應(yīng)的運行時常量池就被創(chuàng)建出來勾怒。當(dāng)然并非Class文件常量池中的內(nèi)容才能進入運行時常量池婆排,在運行期間也可將新的常量放入運行時常量池中声旺,比如String的intern方法(這一段是不是看得比較蒙?這里先整體提一下段只,后面還會對該段內(nèi)容做詳細(xì)整理腮猖,畢竟這一段全是知識點)。

JVM垃圾收集器可以像管理堆區(qū)一樣管理這部分區(qū)域赞枕,從而不需要專門為這部分設(shè)計垃圾回收機制澈缺。不過,從JDK7之后炕婶,HotSpot虛擬機便將運行時常量池從永久代中移除了姐赡。

Java虛擬機規(guī)范把方法區(qū)描述為Java堆的一個邏輯部分,而且它和Java Heap一樣不需要連續(xù)的內(nèi)存柠掂,可以選擇固定大小或可擴展项滑,可以允許該區(qū)域選擇不實現(xiàn)垃圾回收。相對而言涯贞,垃圾收集行為在這個區(qū)域出現(xiàn)比較少枪狂,該區(qū)域的內(nèi)存回收目標(biāo)主要是針對廢棄常量和無用類的回收。為了區(qū)別于Java-Heap宋渔,方法區(qū)也被稱為Non-Heap區(qū)州疾。根據(jù)規(guī)范,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時皇拣,將拋出OutOfMemoryError異常严蓖。

四、方法區(qū)到底存儲了哪些信息氧急?

上一節(jié)中的第5點中谈飒,概括性地講到了方法區(qū)存儲的信息,說得比較籠統(tǒng)态蒂,那樣是遠(yuǎn)遠(yuǎn)不夠的,筆者仍然需要再更細(xì)致地探究一下费什,才能更深入地理解钾恢。

image

1、類信息

(1)類型的全限定名:即類的完整有效名鸳址。在Java源代碼中瘩蚪,完整有效名由類的所屬包名稱加一個".",再加上類名組成稿黍。如疹瘦,Object類所屬的包為java.lang,那它的完整名稱為java.lang.Object巡球,但是在類的文件里言沐,所有的“.”都被斜杠 “/” 代替邓嘹,就成為java/lang/Object。完整有效名在方法區(qū)中的表示根據(jù)不同的實現(xiàn)而不同险胰。

(2)超類的全限定名:即直接父類的完整有效名汹押。

(3)直接超接口的全限定名:即實現(xiàn)的接口的完整有效名。

(4)類型標(biāo)志:即該類是普通類類型還是接口類型起便。

(5)類的訪問描述符:如publlic棚贾,private,default榆综,protected妙痹,abstract,final鼻疮,static等

2怯伊、類的常量池

JVM為每個已加載的類型都維護一個常量池,是這個類用到的常量的一個有序集合陋守,包括實際的常量(String震贵,Integer,F(xiàn)loating Point常量)和對類水评、域(屬性)和方法的符號引用(符號引用在后面會講到)猩系。池中的數(shù)據(jù)項像數(shù)組項一樣,是通過索引訪問的中燥。因為常量池存儲了一個類型所使用到的所有類寇甸、域和方法的符號引用,所以它在Java程序的動態(tài)鏈接中起了核心的作用疗涉。(這一部分下一節(jié)會和運行時常量一起詳細(xì)講到)

3拿霉、字段信息(該類聲明的所有字段,也稱Field咱扣,屬性绽淘,域)

(1)字段修飾符:如public、protected闹伪,private沪铭,default

(2)字段的類型:比如int,float等8種基本類型和引用類型

(3)字段的名稱:這個好理解

4偏瓤、方法信息(方法信息中包含類中的所以方法杀怠,每個方法又包含了如下信息)

(1)方法修飾符:public、protected厅克,private赔退,default,static,final硕旗,synchronized窗骑,native,abstract等

(2)方法返回類型:比如public String getName(String id)中的String即為返回類型卵渴,包括void

(3)方法名:如上述中的getName

(4)方法參數(shù)個數(shù)慧域、類型、順序等

(5)方法字節(jié)碼

(6)操作數(shù)棧和方法棧幀的局部變量區(qū)的大小

5浪读、類變量

即靜態(tài)成員變量昔榴,被static修飾的變量,為該類所有對象共享的變量碘橘,即使沒有任何實例對象時互订,也可以訪問的類變量,它們與類進行綁定痘拆,成為類數(shù)據(jù)在邏輯上的一部分仰禽。這個和第3)點區(qū)分開來,第3)點為實例變量纺蛆。在JVM使用一個類之前吐葵,它必須在方法區(qū)中為每一個non-final的類變量提前分配空間。對于被final修飾的類變量(常量)桥氏,會在常量池中有一個拷貝温峭,而non-final 類變量則被存儲在聲明它的類信息中,這里要注意final和no-final修飾的區(qū)別字支。

注意:Java類中的成員變量有靜態(tài)和非靜態(tài)之分凤藏。靜態(tài)成員變量在方法區(qū),為共享數(shù)據(jù)堕伪;非靜態(tài)成員變量揖庄,在new 一個對象的時候被分配在堆內(nèi)存中。局部變量則是方法內(nèi)定義的變量欠雌,前面已經(jīng)講過蹄梢,它會被分配在Java虛擬機棧內(nèi)存中。虛擬機棧內(nèi)存中會為當(dāng)前方法非配一個棧幀富俄,棧幀中有一個局部變量表禁炒,該表存儲了該變量的值(基礎(chǔ)類型)或?qū)ο笤诙阎械牡刂罚ㄒ妙愋停?/p>

舉個栗子:

image
  • int i; 在類中定義(不是在方法中定義)蛙酪,為第3)點中講到的,為實例變量翘盖,需要類的實例才能調(diào)用桂塞,保存在堆中對應(yīng)的對象實例中。
  • static int i ;non-final修飾的類變量馍驯,保存方法區(qū)中的類信息中阁危。
  • final static int I=0; final修飾的類變量玛痊,此時I就成為了一個常量了,必須賦值狂打,否則報錯擂煞。它會在常量池中有一個拷貝。

6趴乡、指向類加載器的引用

每一個被JVM加載的類对省,都保存這個類加載器的引用,類加載器動態(tài)鏈接時會用到晾捏。當(dāng)解析一個類到另一個類的引用時蒿涎,JVM需要保證這兩個類的加載器是相同的,這對JVM區(qū)分名字空間的方式是至關(guān)重要的惦辛。

7劳秋、指向Class實例的引用

類加載的過程中,虛擬機會為每個加載的類(包括類和接口)都創(chuàng)建一個java.lang.Class的實例胖齐,JVM必須以某種方式把這個Class實例和存儲在方法區(qū)中的類數(shù)據(jù)聯(lián)系起來玻淑。在Class類中有個靜態(tài)方法可以得帶這個實例的引用,public static Class forName(String className)呀伙,通過Class.forName(String className)(反射)來查找獲得該實例的引用补履,然后創(chuàng)建該類的對象(這里和直接new一個對象區(qū)分開來)。例如区匠,通過調(diào)用 Class.forName(“java.lang.Object”)干像,可以得到與java.lang.Object對應(yīng)的類對象(這里用到了工廠模式),甚至可以通過這個函數(shù)得到任何包中任何已經(jīng)加載的類引用驰弄,只要這個類能夠被加載到當(dāng)前的名字空間麻汰。如果不能把類加載到當(dāng)前名字空間,forName就會拋出ClassNotFoundException戚篙。

Class類還提供了如下方法五鲫,獲取到類的對象后,可以用這些方法得到對應(yīng)的類存儲在方法區(qū)中的類信息:

  • public String getName(); //獲取類名
  • public Class getSuperClass(); //獲取父類對象
  • public boolean isInterface(); // 判斷是否為接口
  • public Class[] getInterfaces(); //返回一組接口對象岔擂,對應(yīng)該類實現(xiàn)的接口對象位喂。
  • public ClassLoader getClassLoader(); //返回類加載器的引用。

8乱灵、方法表

為了提高訪問效率塑崖,JVM可能會對每個裝載的非抽象類和非接口,都創(chuàng)建一個數(shù)組痛倚,數(shù)組的每個元素都是實例可能調(diào)用的方法的直接引用(注意规婆,這里說的是引用,不是方法本身,方法本身是在Java虛擬機棧的棧幀中)抒蚜,包括父類中繼承過來的方法掘鄙。JVM可以通過方法表快速激活實例方法。

9嗡髓、運行時常量

JDK7后已經(jīng)移除了方法區(qū)操漠。結(jié)合第2點類的常量池,后面會有個小節(jié)再繼續(xù)擴展分析饿这。

10浊伙、即時編譯(JIT)后的代碼

Java的字節(jié)碼文件.class文件,被JVM加載后蛹稍,會一句一句翻譯程機器碼執(zhí)行吧黄。這個區(qū)域就存儲了這些機器碼。(這個是筆者自己的理解唆姐,沒有查到權(quán)威的結(jié)論)

五拗慨、常量池

在上一節(jié)中,我們提到了“類的常量池”和“運行時常量池”奉芦,這里我們接著來講赵抢。

常量池分為靜態(tài)常量池和運行時常量池,它們的區(qū)別在于動態(tài)性声功。

1烦却、靜態(tài)常量池

靜態(tài)常量就是我上面提到的“類常量池”,即*.class文件中的常量池先巴。當(dāng)java文件被編譯為.class文件的時候其爵,會專門有一部分區(qū)域用于保存類中的常量,這個區(qū)域就是類常量池伸蚯。.class文件中的常量池不僅僅包含字符串(數(shù)字)字面量摩渺,還包含類、方法的信息剂邮,他們占據(jù)了class文件的絕大部分空間摇幻。總體來說挥萌,它主要存儲了兩大類常量:字面量和符號引用绰姻。在這里,我們解釋幾個名詞:

  • 常量:有兩種情況引瀑,第一種是一個值狂芋,如1024(整型常量)、‘a(chǎn)’'b''c'(字符常量)憨栽、“abc”(字符串常量)帜矾、true/false(boolean型常量)等辆影。第二種就是被final修飾的變量,因為它的值不能再改變黍特,也被稱作常量,比如final int I = 0锯蛀; 這里灭衷,I 就成為了一個常量。
  • 字面量:相當(dāng)于Java語言層面常量的概念旁涤。比如 String s = “abc”翔曲,這里"abc"就是一個字面量。
  • 符號引用:屬于編譯原理方面的概念劈愚,包含了如下三種類型的的常量:

I)類和接口的全限定名:即前面第4節(jié)第1)點類信息中提到過的瞳遍,比如Object類的全限定名就是java.lang.Object

II)字段名稱和描述符:即前面第4節(jié)第3)點中對應(yīng)的名稱和修飾符。

III)方法名稱和描述符:即前面第4節(jié)第4)點中對應(yīng)的名稱和修飾符菌羽。

image

2掠械、運行時常量

上述中的靜態(tài)常量池(類常量池),是在編譯的時候注祖,存在于.class文件中的猾蒂,而JVM在完成.class文件的裝載后,靜態(tài)常量池就被載入到內(nèi)存中用于程序的運行是晨,此時肚菠,靜態(tài)常量池?fù)u身一變,成為了運行時常量池罩缴。JDK7之前的版本中蚊逢,運行時常量是方法區(qū)中的一部分,可能由于方法區(qū)的空間有限箫章,JDK7及以后的版本就把它移除了方法區(qū)烙荷,這一點在前面也多次提到過。有些資料說是移到了Java堆中炉抒,沒有看到權(quán)威的資料奢讨,筆者也不敢去確定。

一點疑惑:從上面的描述來看焰薄,類/接口拿诸、方法、字段的相關(guān)信息塞茅,在上訴第4節(jié)中方法區(qū)中的類信息亩码、字段信息、方法信息存儲了一份野瘦,在類常量池中又存儲一次描沟,這樣是不是冗余了飒泻?方法區(qū)是內(nèi)存中的一部分,在運行期出現(xiàn)吏廉,而類常量池是.class文件中的一部分泞遗,在運行前就出現(xiàn)了,為什么方法區(qū)中會存在類變量席覆? 是筆者參考的資料中描述有誤史辙?還是筆者理解有誤?這里如果有幸被讀者讀到佩伤,可以自己研究一下聊倔,順便告知于我,3Q生巡!

3耙蔑、常量池的好處

常量池是為了避免頻繁地創(chuàng)建和銷毀對象而影響到系統(tǒng)性能,而實現(xiàn)的對對象的共享孤荣。例如甸陌,字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中盐股,這樣做有兩個好處:I)節(jié)省內(nèi)存空間:常量池中所有相同的字符串常量合并邀层,只占用一個空間。II)節(jié)省運行時間:比較字符時遂庄,==比equals()快寥院。對于兩個引用變量,只用==判斷引用是否相等涛目,也就可以判斷實際值是否相等秸谢。

六、總結(jié)

本章中理論性的東西太多了霹肝,下圖對這一章節(jié)的內(nèi)容做個簡單的梳理和歸納估蹄。

image

大家關(guān)注下,后面還有三個專欄哦~

想學(xué)習(xí)更多Android知識沫换,或者獲取相關(guān)資料請加入Android技術(shù)開發(fā)交流2群:935654177臭蚁。本群可免費獲取Gradle,RxJava讯赏,小程序垮兑,Hybrid,移動架構(gòu)漱挎,NDK系枪,React Native,性能優(yōu)化等技術(shù)教程磕谅!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末私爷,一起剝皮案震驚了整個濱河市雾棺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衬浑,老刑警劉巖捌浩,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異工秩,居然都是意外死亡嘉栓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門拓诸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人麻昼,你說我怎么就攤上這事奠支。” “怎么了抚芦?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵倍谜,是天一觀的道長。 經(jīng)常有香客問我叉抡,道長尔崔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任褥民,我火速辦了婚禮季春,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘消返。我一直安慰自己载弄,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布撵颊。 她就那樣靜靜地躺著宇攻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倡勇。 梳的紋絲不亂的頭發(fā)上逞刷,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音妻熊,去河邊找鬼夸浅。 笑死,一個胖子當(dāng)著我的面吹牛扔役,可吹牛的內(nèi)容都是我干的题篷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厅目,長吁一口氣:“原來是場噩夢啊……” “哼番枚!你這毒婦竟也來了法严?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤葫笼,失蹤者是張志新(化名)和其女友劉穎深啤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路星,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡溯街,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了洋丐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呈昔。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖友绝,靈堂內(nèi)的尸體忽然破棺而出堤尾,到底是詐尸還是另有隱情,我是刑警寧澤迁客,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布郭宝,位于F島的核電站,受9級特大地震影響掷漱,放射性物質(zhì)發(fā)生泄漏粘室。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一卜范、第九天 我趴在偏房一處隱蔽的房頂上張望衔统。 院中可真熱鬧,春花似錦海雪、人聲如沸缰冤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棉浸。三九已至,卻和暖如春刺彩,著一層夾襖步出監(jiān)牢的瞬間迷郑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工创倔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗡害,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓畦攘,卻偏偏與公主長得像霸妹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子知押,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容