關(guān)于java方法區(qū)詳解
方法區(qū)
保存在著被加載過(guò)的每一個(gè)類的信息;這些信息由類加載器在加載類的時(shí)候,從類的源文件中抽取出來(lái);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)橥ㄟ^(guò)用戶定義的類加載器可以動(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è)類的名字叫啥,他粑粑是誰(shuí)、有沒(méi)有實(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)單說(shuō)就是在編譯期間,就可以確定下來(lái)的值;
符號(hào)引用:不同于我們常說(shuō)的引用,它們是對(duì)類型,域和方法的引用,類似于面向過(guò)程語(yǔ)言使用的前期綁定,對(duì)方法調(diào)用產(chǎn)生的引用;
存在這里面的數(shù)據(jù),類似于保存在數(shù)組中,外部根據(jù)索引來(lái)獲得它們 ;
字段信息
- 聲明的順序
- 修飾符
- 類型
- 名字
方法信息
- 聲明的順序
- 修飾符
- 返回值類型
- 名字
- 參數(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)系起來(lái), 因此,類的元數(shù)據(jù)里面保存了一個(gè)Class對(duì)象的引用;
方法表
為了提高訪問(wèn)效率,必須仔細(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可以通過(guò)方法表快速激活實(shí)例方法。(譯者:這里的方法表與C++中的虛擬函數(shù)表一樣,但java方法全都 是virtual的,自然也不用虛擬二字了。
正像java宣稱沒(méi)有 指針了,其實(shí)java里全是指針。更安全只是加了更完備的檢查機(jī)制,但這都是以犧牲效率為代價(jià)的,個(gè)人認(rèn)為java的設(shè)計(jì)者 始終是把安全放在效率之上的,所有java才更適合于網(wǎng)絡(luò)開(kāi)發(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ū)中,通過(guò)解析存在方法區(qū)中的字節(jié)碼,jvm激活了main()方法,在執(zhí)行時(shí),jvm保持了一個(gè)指向當(dāng)前類(Volcano)常量池的指針。
注意jvm在還沒(méi)有加載Lava類的時(shí)候就已經(jīng)開(kāi)始執(zhí)行了。正像大多數(shù)的jvm一樣,不會(huì)等所有類都加載了以后才開(kāi)始執(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表,查找樹(shù)等等。同樣的算法可以用于Class類的forName()的實(shí)現(xiàn)。
當(dāng)jvm發(fā)現(xiàn)還沒(méi)有加載過(guò)一個(gè)稱為”Lava”的類,它就開(kāi)始查找并加載類文件”Lava.class”。它從類文件中抽取類型信息并放在了方法區(qū)中。
jvm于是以一個(gè)直接指向方法區(qū)lava類的指針替換了常量池第一項(xiàng)的符號(hào)引用。以后就可以用這個(gè)指針快速的找到lava類了。而這個(gè)替換過(guò)程稱為常量池解析(constant pool resolution)。在這里我們替換的是一個(gè)native指針。
jvm終于開(kāi)始為新的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)文章
springboot配置resilience4j全過(guò)程
這篇文章主要介紹了springboot配置resilience4j全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
Spring?Cloud?通過(guò)?Gateway?webflux實(shí)現(xiàn)網(wǎng)關(guān)異常處理
在某一個(gè)服務(wù)中出現(xiàn)異常,通過(guò)@ControllerAdvice?+?@ExceptionHandler?統(tǒng)一異常處理,即使在微服務(wù)架構(gòu)中,也可以將上述統(tǒng)一異常處理放入到公共的微服務(wù)中,這樣哪一個(gè)微服務(wù)需要,直接引入模塊,本文重點(diǎn)介紹Spring?Cloud?通過(guò)?Gateway?webflux實(shí)現(xiàn)網(wǎng)關(guān)異常處理,一起看看吧2023-11-11
SpringBoot監(jiān)控模塊Actuator的用法詳解
Spring?Boot?Actuator?是?Spring?Boot?自帶的一個(gè)功能模塊,提供了一組已經(jīng)開(kāi)箱即用的生產(chǎn)環(huán)境下常用的特性和服務(wù),比如應(yīng)用程序的健康檢查、信息暴露、度量收集、日志記錄等,本文將給大家詳細(xì)SpringBoot監(jiān)控模塊Actuator的用法2023-06-06
解決Error:(5, 28) java: 程序包org.apache.ibatis.io
這篇文章主要介紹了解決Error:(5, 28) java: 程序包org.apache.ibatis.io不存在問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解
在SpringBoot項(xiàng)目中簡(jiǎn)單使用定時(shí)任務(wù),不過(guò)由于要借助cron表達(dá)式且都提前定義好放在配置文件里,不能在項(xiàng)目運(yùn)行中動(dòng)態(tài)修改任務(wù)執(zhí)行時(shí)間,實(shí)在不太靈活?,F(xiàn)在我們就來(lái)實(shí)現(xiàn)可以動(dòng)態(tài)修改cron表達(dá)式的定時(shí)任務(wù),感興趣的可以了解一下2022-11-11
Java數(shù)據(jù)結(jié)構(gòu)優(yōu)先隊(duì)列實(shí)練
通常都把隊(duì)列比喻成排隊(duì)買(mǎi)東西,大家都很守秩序,先排隊(duì)的人就先買(mǎi)東西。但是優(yōu)先隊(duì)列有所不同,它不遵循先進(jìn)先出的規(guī)則,而是根據(jù)隊(duì)列中元素的優(yōu)先權(quán),優(yōu)先權(quán)最大的先被取出,這篇文章主要介紹了java優(yōu)先隊(duì)列的真題,感興趣的朋友一起看看吧2022-07-07
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(53)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-08-08

