關(guān)于java方法區(qū)詳解
方法區(qū)
保存在著被加載過的每一個(gè)類的信息;這些信息由類加載器在加載類的時(shí)候,從類的源文件中抽取出來;static變量信息也保存在方法區(qū)中;
可以看做是將類(Class)的元數(shù)據(jù),保存在方法區(qū)里;
方法區(qū)是線程共享的;當(dāng)有多個(gè)線程都用到一個(gè)類的時(shí)候,而這個(gè)類還未被加載,則應(yīng)該只有一個(gè)線程去加載類,讓其他線程等待;
方法區(qū)的大小不必是固定的,jvm可以根據(jù)應(yīng)用的需要?jiǎng)討B(tài)調(diào)整。jvm也可以允許用戶和程序指定方法區(qū)的初始大小,最小和最大限制;
方法區(qū)同樣存在垃圾收集,因?yàn)橥ㄟ^用戶定義的類加載器可以動(dòng)態(tài)擴(kuò)展Java程序,這樣可能會(huì)導(dǎo)致一些類,不再被使用,變?yōu)槔?。這時(shí)候需要進(jìn)行垃圾清理。
圖例(方法區(qū)中都保存什么)
類型信息
包括以下幾點(diǎn):
類的完整名稱(比如,java.long.String)類的直接父類的完整名稱類的直接實(shí)現(xiàn)接口的有序列表(因?yàn)橐粋€(gè)類直接實(shí)現(xiàn)的接口可能不止一個(gè),因此放到一個(gè)有序表中)類的修飾符可以看做是,對(duì)一個(gè)類進(jìn)行登記,這個(gè)類的名字叫啥,他粑粑是誰、有沒有實(shí)現(xiàn)接口, 權(quán)限是啥;
類型的常量池 (即運(yùn)行時(shí)常量池)
每一個(gè)Class文件中,都維護(hù)著一個(gè)常量池(這個(gè)保存在類文件里面,不要與方法區(qū)的運(yùn)行時(shí)常量池搞混),里面存放著編譯時(shí)期生成的各種字面值和符號(hào)引用;這個(gè)常量池的內(nèi)容,在類加載的時(shí)候,被復(fù)制到方法區(qū)的運(yùn)行時(shí)常量池 ;
字面值:就是像string, 基本數(shù)據(jù)類型,以及它們的包裝類的值,以及final修飾的變量,簡(jiǎn)單說就是在編譯期間,就可以確定下來的值;
符號(hào)引用:不同于我們常說的引用,它們是對(duì)類型,域和方法的引用,類似于面向過程語言使用的前期綁定,對(duì)方法調(diào)用產(chǎn)生的引用;
存在這里面的數(shù)據(jù),類似于保存在數(shù)組中,外部根據(jù)索引來獲得它們 ;
字段信息
- 聲明的順序
- 修飾符
- 類型
- 名字
方法信息
- 聲明的順序
- 修飾符
- 返回值類型
- 名字
- 參數(shù)列表(有序保存)
- 異常表(方法拋出的異常)
- 方法字節(jié)碼(native、abstract方法除外,)
- 操作數(shù)棧和局部變量表大小
類變量(即static變量)
非final類變量
- 在java虛擬機(jī)使用一個(gè)類之前,它必須在方法區(qū)中為每個(gè)非final類變量分配空間。非final類變量存儲(chǔ)在定義它的類中;
final類變量(不存儲(chǔ)在這里)
- 由于final的不可改變性,因此,final類變量的值在編譯期間,就被確定了,因此被保存在類的常量池里面,然后在加載類的時(shí)候,復(fù)制進(jìn)方法區(qū)的運(yùn)行時(shí)常量池里面 ;final類變量存儲(chǔ)在運(yùn)行時(shí)常量池里面,每一個(gè)使用它的類保存著一個(gè)對(duì)其的引用;
對(duì)類加載器的引用
jvm必須知道一個(gè)類型是由啟動(dòng)加載器加載的還是由用戶類加載器加載的。
如果一個(gè)類型是由用戶類加載器加載的,那么jvm會(huì)將這個(gè)類加載器的一個(gè)引用作為類型信息的一部分保存在方法區(qū)中。
對(duì)Class類的引用
jvm為每個(gè)加載的類都創(chuàng)建一個(gè)java.lang.Class的實(shí)例(存儲(chǔ)在堆上)。
而jvm必須以某種方式把Class的這個(gè)實(shí)例和存儲(chǔ)在方法區(qū)中的類型數(shù)據(jù)(類的元數(shù)據(jù))聯(lián)系起來, 因此,類的元數(shù)據(jù)里面保存了一個(gè)Class對(duì)象的引用;
方法表
為了提高訪問效率,必須仔細(xì)的設(shè)計(jì)存儲(chǔ)在方法區(qū)中的數(shù)據(jù)信息結(jié)構(gòu)。除了以上討論的結(jié)構(gòu),jvm的實(shí)現(xiàn)者還可以添加一些其他的數(shù)據(jù)結(jié)構(gòu),如方法表。
jvm對(duì)每個(gè)加載的非虛擬類的類型信息中都添加了一個(gè)方法表,方法表是一組對(duì)類實(shí)例方法的直接引用(包括從父類繼承的方法。jvm可以通過方法表快速激活實(shí)例方法。(譯者:這里的方法表與C++中的虛擬函數(shù)表一樣,但java方法全都 是virtual的,自然也不用虛擬二字了。
正像java宣稱沒有 指針了,其實(shí)java里全是指針。更安全只是加了更完備的檢查機(jī)制,但這都是以犧牲效率為代價(jià)的,個(gè)人認(rèn)為java的設(shè)計(jì)者 始終是把安全放在效率之上的,所有java才更適合于網(wǎng)絡(luò)開發(fā))
JVM如何使用方法區(qū)里面的數(shù)據(jù)
一個(gè)例子
為了顯示jvm如何使用方法區(qū)中的信息,我們據(jù)一個(gè)例子,我們 看下面這個(gè)類:
class Lava { private int speed = 5; // 5 kilometers per hour void flow() { } } class Volcano { public static void main(String[] args) { Lava lava = new Lava(); lava.flow(); } }
下面我們描述一下main()方法的第一條指令的字節(jié)碼是如何被執(zhí)行的。不同的jvm實(shí)現(xiàn)的差別很大,這里只是其中之一。
為了運(yùn)行這個(gè)程序,你以某種方式把“Volcano”傳給了jvm。有了這個(gè)名字,jvm找到了這個(gè)類文件(Volcano.class)并讀入,它從 類文件提取了類型信息并放在了方法區(qū)中,通過解析存在方法區(qū)中的字節(jié)碼,jvm激活了main()方法,在執(zhí)行時(shí),jvm保持了一個(gè)指向當(dāng)前類(Volcano)常量池的指針。
注意jvm在還沒有加載Lava類的時(shí)候就已經(jīng)開始執(zhí)行了。正像大多數(shù)的jvm一樣,不會(huì)等所有類都加載了以后才開始執(zhí)行,它只會(huì)在需要的時(shí)候才加載。
main()的第一條指令告知jvm為列在常量池第一項(xiàng)的類分配足夠的內(nèi)存。jvm使用指向Volcano常量池的指針找到第一項(xiàng),發(fā)現(xiàn)是一個(gè)對(duì)Lava類的符號(hào)引用,然后它就檢查方法區(qū)看lava是否已經(jīng)被加載了。
這個(gè)符號(hào)引用僅僅是類lava的完整有效名”lava“。這里我們看到為了jvm能盡快從一個(gè)名稱找到一個(gè)類,一個(gè)良好的數(shù)據(jù)結(jié)構(gòu)是多么重要。這里jvm的實(shí)現(xiàn)者可以采用各種方法,如hash表,查找樹等等。同樣的算法可以用于Class類的forName()的實(shí)現(xiàn)。
當(dāng)jvm發(fā)現(xiàn)還沒有加載過一個(gè)稱為”Lava”的類,它就開始查找并加載類文件”Lava.class”。它從類文件中抽取類型信息并放在了方法區(qū)中。
jvm于是以一個(gè)直接指向方法區(qū)lava類的指針替換了常量池第一項(xiàng)的符號(hào)引用。以后就可以用這個(gè)指針快速的找到lava類了。而這個(gè)替換過程稱為常量池解析(constant pool resolution)。在這里我們替換的是一個(gè)native指針。
jvm終于開始為新的lava對(duì)象分配空間了。這次,jvm仍然需要方法區(qū)中的信息。它使用指向lava數(shù)據(jù)的指針(剛才指向volcano常量池第一項(xiàng)的指針)找到一個(gè)lava對(duì)象究竟需要多少空間。
jvm總能夠從存儲(chǔ)在方法區(qū)中的類型信息知道某類型對(duì)象需要的空間。但一個(gè)對(duì)象在不同的jvm中可能需要不同的空間,而且它的空間分布也是不同的。(譯者:這與在C++中,不同的編譯器也有不同的對(duì)象模型是一個(gè)道理)
一旦jvm知道了一個(gè)Lava對(duì)象所要的空間,它就在堆上分配這個(gè)空間并把這個(gè)實(shí)例的變量speed初始化為缺省值0。假如lava的父對(duì)象也有實(shí)例變量,則也會(huì)初始化。
當(dāng)把新生成的lava對(duì)象的引用壓到棧中,第一條指令也結(jié)束了。下面的指令利用這個(gè)引用激活java代碼把speed變量設(shè)為初始值,5。另外一條指令會(huì)用這個(gè)引用激活Lava對(duì)象的flow()方法。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java編程幾個(gè)循環(huán)實(shí)例代碼分享
這篇文章主要介紹了Java編程幾個(gè)循環(huán)實(shí)例代碼分享,多看多練,小編覺得還是挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10hibernate關(guān)于session的關(guān)閉實(shí)例解析
這篇文章主要介紹了hibernate關(guān)于session的關(guān)閉實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02Mybatis-Plus saveBatch()批量保存失效的解決
本文主要介紹了Mybatis-Plus saveBatch()批量保存失效的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Java數(shù)據(jù)結(jié)構(gòu)之鏈表(動(dòng)力節(jié)點(diǎn)之Java學(xué)院整理)
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之鏈表(動(dòng)力節(jié)點(diǎn)之Java學(xué)院整理)的相關(guān)資料,需要的朋友可以參考下2017-04-04關(guān)于mybatis-plus邏輯刪除自動(dòng)填充更新時(shí)間的問題
mybatis-plus是對(duì)mybatis的增強(qiáng),mybatis-plus更像是面向?qū)ο缶幊?,?shù)據(jù)庫(kù)基本CRUD的操作可以不用手動(dòng)編寫SQL語句,大大提高了開發(fā)的效率,這篇文章主要介紹了mybatis-plus邏輯刪除自動(dòng)填充更新時(shí)間問題,需要的朋友可以參考下2022-07-07