溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java中的Person是什么

發(fā)布時間:2021-09-14 13:58:37 來源:億速云 閱讀:275 作者:柒染 欄目:編程語言

這篇文章將為大家詳細講解有關(guān)java中的Person是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

方法區(qū)

這次所講述的是運行時數(shù)據(jù)區(qū)的最后一個部分

java中的Person是什么

從線程共享與否的角度來看

java中的Person是什么

ThreadLocal:如何保證多個線程在并發(fā)環(huán)境下的安全性?典型應(yīng)用就是數(shù)據(jù)庫連接管理,以及會話管理

一 棧、堆、方法區(qū)的交互關(guān)系

下面就涉及了對象的訪問定位

java中的Person是什么

  • Person:存放在元空間,也可以說方法區(qū)

  • person:存放在Java棧的局部變量表中

  • new Person():存放在Java堆中

二 方法區(qū)的理解

《Java虛擬機規(guī)范》中明確說明:“盡管所有的方法區(qū)在邏輯上是屬于堆的一部分,但一些簡單的實現(xiàn)可能不會選擇去進行垃圾收集或者進行壓縮?!钡珜τ贖otSpotJVM而言,方法區(qū)還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。

所以,方法區(qū)看作是一塊獨立于Java堆的內(nèi)存空間。

java中的Person是什么

方法區(qū)主要存放的是 Class,而堆中主要存放的是 實例化的對象

  • 方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域。

  • 方法區(qū)在JVM啟動的時候被創(chuàng)建,并且它的實際的物理內(nèi)存空間中和Java堆區(qū)一樣都可以是不連續(xù)的。

  • 方法區(qū)的大小,跟堆空間一樣,可以選擇固定大小或者可擴展。

  • 方法區(qū)的大小決定了系統(tǒng)可以保存多少個類,如果系統(tǒng)定義了太多的類,導(dǎo)致方法區(qū)溢出,虛擬機同樣會拋出內(nèi)存溢出錯誤:ava.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace

    • 加載大量的第三方的jar包

    • Tomcat部署的工程過多(30~50個)

    • 大量動態(tài)的生成反射類

  • 關(guān)閉JVM就會釋放這個區(qū)域的內(nèi)存。

HotSpot中方法區(qū)的演進

  • 在jdk7及以前,習慣上把方法區(qū),稱為永久代。jdk8開始,使用元空間取代了永久代。

    • JDK 1.8后,元空間存放在堆外內(nèi)存中

  • 本質(zhì)上,方法區(qū)和永久代并不等價。僅是對hotspot而言的?!禞ava虛擬機規(guī)范》對如何實現(xiàn)方法區(qū),不做統(tǒng)一要求。例如:BEAJRockit / IBM J9 中不存在永久代的概念。

現(xiàn)在來看,當年使用永久代,不是好的idea。導(dǎo)致Java程序更容易oom(超過-XX:MaxPermsize上限)

java中的Person是什么

  • 而到了JDK8,終于完全廢棄了永久代的概念,改用與JRockit、J9一樣在本地內(nèi)存中實現(xiàn)的元空間(Metaspace)來代替

    java中的Person是什么

  • 元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代最大的區(qū)別在于:元空間不在虛擬機設(shè)置的內(nèi)存中,而是使用本地內(nèi)存

  • 永久代、元空間二者并不只是名字變了,內(nèi)部結(jié)構(gòu)也調(diào)整了

  • 根據(jù)《Java虛擬機規(guī)范》的規(guī)定,如果方法區(qū)無法滿足新的內(nèi)存分配需求時,將拋出OOM異常

三 設(shè)置方法區(qū)大小與OOM

  • 方法區(qū)的大小不必是固定的,JVM可以根據(jù)應(yīng)用的需要動態(tài)調(diào)整。

JKD7 及以前

  • 通過-xx:Permsize來設(shè)置永久代初始分配空間。默認值是20.75M

  • -XX:MaxPermsize來設(shè)定永久代最大可分配空間。32位機器默認是64M,64位機器模式是82M

  • 當JVM加載的類信息容量超過了這個值,會報異常OutofMemoryError:PermGen space。

java中的Person是什么

JDK8以后

  • 元數(shù)據(jù)區(qū)大小可以使用參數(shù) -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定

  • 默認值依賴于平臺。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即沒有限制。

  • 與永久代不同,如果不指定大小,默認情況下,虛擬機會耗盡所有的可用系統(tǒng)內(nèi)存。如果元數(shù)據(jù)區(qū)發(fā)生溢出,虛擬機一樣會拋出異常OutOfMemoryError:Metaspace

  • -XX:MetaspaceSize:設(shè)置初始的元空間大小。對于一個64位的服務(wù)器端JVM來說,其默認的-xx:MetaspaceSize值為21MB。這就是初始的高水位線,一旦觸及這個水位線,F(xiàn)ull GC將會被觸發(fā)并卸載沒用的類(即這些類對應(yīng)的類加載器不再存活)然后這個高水位線將會重置。新的高水位線的值取決于GC后釋放了多少元空間。如果釋放的空間不足,那么在不超過MaxMetaspaceSize時,適當提高該值。如果釋放空間過多,則適當降低該值。

  • 如果初始化的高水位線設(shè)置過低,上述高水位線調(diào)整情況會發(fā)生很多次。通過垃圾回收器的日志可以觀察到FullGC多次調(diào)用。為了避免頻繁地GC,建議將-XX:MetaspaceSize設(shè)置為一個相對較高的值。

如何解決這些OOM

  • 要解決OOM異?;騢eap space的異常,一般的手段是首先通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉(zhuǎn)儲快照進行分析,重點是確認內(nèi)存中的對象是否是必要的,也就是要先分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)

    • 內(nèi)存泄漏就是 有大量的引用指向某些對象,但是這些對象以后不會使用了,但是因為它們還和GC ROOT有關(guān)聯(lián),所以導(dǎo)致以后這些對象也不會被回收,這就是內(nèi)存泄漏的問題

  • 如果是內(nèi)存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈。于是就能找到泄漏對象是通過怎樣的路徑與GCRoots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的。掌握了泄漏對象的類型信息,以及GCRoots引用鏈的信息,就可以比較準確地定位出泄漏代碼的位置。

  • 如果不存在內(nèi)存泄漏,換句話說就是內(nèi)存中的對象確實都還必須存活著,那就應(yīng)當檢查虛擬機的堆參數(shù)(-Xmx與-Xms),與機器物理內(nèi)存對比看是否還可以調(diào)大,從代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時間過長的情況,嘗試減少程序運行期的內(nèi)存消耗。

四 方法區(qū)的內(nèi)部結(jié)構(gòu)

java中的Person是什么

《深入理解Java虛擬機》書中對方法區(qū)(Method Area)存儲內(nèi)容描述如下:它用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等。

java中的Person是什么

類型信息

對每個加載的類型(類class、接口interface、枚舉enum、注解annotation),JVM必須在方法區(qū)中存儲以下類型信息:

  1. 這個類型的完整有效名稱(全名=包名.類名)

  2. 這個類型直接父類的完整有效名(對于interface或是java.lang.object,都沒有父類)

  3. 這個類型的修飾符(public,abstract,final的某個子集)

  4. 這個類型直接接口的一個有序列表

域(Field)信息

  • JVM必須在方法區(qū)中保存類型的所有域的相關(guān)信息以及域的聲明順序。

  • 域的相關(guān)信息包括:域名稱、域類型、域修飾符(public,private,protected,static,final,volatile,transient的某個子集)

方法(Method)信息

JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序:

  • 方法名稱

  • 方法的返回類型(或void)

  • 方法參數(shù)的數(shù)量和類型(按順序)

  • 方法的修飾符(public,private,protected,static,final,synchronized,native,abstract的一個子集)

  • 方法的字節(jié)碼(bytecodes)、 操作數(shù)棧、局部變量表及大?。╝bstract和native方法除外)

  • 異常表(abstract和native方法除外)

每個異常處理的開始位置、結(jié)束位置、代碼處理在程序計數(shù)器中的偏移地址、被捕獲的異常類的常量池索引

non-final的類變量

  • 靜態(tài)變量和類關(guān)聯(lián)在一起,隨著類的加載而加載,他們成為類數(shù)據(jù)在邏輯上的一部分

  • 類變量被類的所有實例共享,即使沒有類實例時,你也可以訪問它

/**
 * non-final的類變量
 *
 */
public class MethodAreaTest {
    public static void main(String[] args) {
        Order order = new Order();
        order.hello();
        System.out.println(order.count);
    }
}
class Order {
    public static int count = 1;
    public static final int number = 2;
    public static void hello() {
        System.out.println("hello!");
    }
}

如上代碼所示,即使我們把order設(shè)置為null,也不會出現(xiàn)空指針異常

全局常量 同時使用 static final 進行修飾

  • 被聲明為final的類變量的處理方法則不同,每個全局常量在編譯的時候就會被分配了。

運行時常量池 VS 常量池

java中的Person是什么

  • 方法區(qū),內(nèi)部包含了運行時常量池

  • 字節(jié)碼文件,內(nèi)部包含了常量池

  • 要弄清楚方法區(qū),需要理解清楚C1assFile,因為加載類的信息都在方法區(qū)。

  • 要弄清楚方法區(qū)的運行時常量池,需要理解清楚classFile中的常量池。

常量池

java中的Person是什么

  • 一個有效的字節(jié)碼文件中除了包含類的版本信息、字段、方法以及接口等描述符信息外,還包含一項信息就是常量池表(Constant Pool Table),包括各種字面量和對類型、域和方法的符號引用

為什么需要常量池?

一個java源文件中的類、接口,編譯后產(chǎn)生一個字節(jié)碼文件。而Java中的字節(jié)碼需要數(shù)據(jù)支持,通常這種數(shù)據(jù)會很大以至于不能直接存到字節(jié)碼里,換另一種方式,可以存到常量池,這個字節(jié)碼包含了指向常量池的引用。在動態(tài)鏈接的時候會用到運行時常量池,之前有介紹。

比如:如下的代碼:

public class SimpleClass {
    public void sayHello() {
        System.out.println("hello");
    }
}

雖然上述代碼只有194字節(jié),但是里面卻使用了String、System、PrintStream及Object等結(jié)構(gòu)。這里的代碼量其實很少了,如果代碼多的話,引用的結(jié)構(gòu)將會更多,這里就需要用到常量池了。

常量池中有什么
  • 數(shù)量值

  • 字符串值

  • 類引用

  • 字段引用

  • 方法引用

例如下面這段代碼

public class MethodAreaTest2 {
    public static void main(String args[]) {
        Object obj = new Object();
    }
}

將會被翻譯成如下字節(jié)碼

new #2  
dup
invokespecial
小結(jié)
  • 常量池、可以看做是一張表,虛擬機指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等類型

運行時常量池

  • 運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分。

  • 常量池表(Constant Pool Table)是Class文件的一部分,用于存放編譯期生成的各種字面量與符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中。

  • 運行時常量池,在加載類和接口到虛擬機后,就會創(chuàng)建對應(yīng)的運行時常量池。

  • JVM為每個已加載的類型(類或接口)都維護一個常量池。池中的數(shù)據(jù)項像數(shù)組項一樣,是通過索引訪問的。

  • 運行時常量池中包含多種不同的常量,包括編譯期就已經(jīng)明確的數(shù)值字面量,也包括到運行期解析后才能夠獲得的方法或者字段引用。此時不再是常量池中的符號地址了,這里換為真實地址。

    • 運行時常量池,相對于Class文件常量池的另一重要特征是:具備動態(tài)性。

  • 運行時常量池類似于傳統(tǒng)編程語言中的符號表(symboltable),但是它所包含的數(shù)據(jù)卻比符號表要更加豐富一些。

  • 當創(chuàng)建類或接口的運行時常量池時,如果構(gòu)造運行時常量池所需的內(nèi)存空間超過了方法區(qū)所能提供的最大值,則JVM會拋outofMemoryError異常。

五 方法區(qū)使用舉例

如下代碼

public class MethodAreaDemo {
    public static void main(String args[]) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a+b);
    }
}

字節(jié)碼執(zhí)行過程展示

java中的Person是什么

首先現(xiàn)將操作數(shù)500放入到操作數(shù)棧中

java中的Person是什么

然后存儲到局部變量表中

java中的Person是什么

然后重復(fù)一次,把100放入局部變量表中,最后再將變量表中的500 和 100 取出,進行操作

java中的Person是什么

將500 和 100 進行一個除法運算,在把結(jié)果入棧

java中的Person是什么

在最后就是輸出流,需要調(diào)用運行時常量池的常量

java中的Person是什么

最后調(diào)用invokevirtual(虛方法調(diào)用),然后返回

java中的Person是什么

返回時

java中的Person是什么

程序計數(shù)器始終計算的都是當前代碼運行的位置,目的是為了方便記錄 方法調(diào)用后能夠正常返回,或者是進行了CPU切換后,也能回來到原來的代碼進行執(zhí)行。

六 方法區(qū)的演進細節(jié)

  1. 首先明確:只有Hotspot才有永久代。BEA JRockit、IBMJ9等來說,是不存在永久代的概念的。原則上如何實現(xiàn)方法區(qū)屬于虛擬機實現(xiàn)細節(jié),不受《Java虛擬機規(guī)范》管束,并不要求統(tǒng)一

  2. Hotspot中方法區(qū)的變化:

JDK1.6及以前有永久代,靜態(tài)變量存儲在永久代上
JDK1.7有永久代,但已經(jīng)逐步 “去永久代”,字符串常量池,靜態(tài)變量移除,保存在堆中
JDK1.8及之后無永久代,類型信息,字段,方法,常量保存在本地內(nèi)存的元空間,但字符串常量池、靜態(tài)變量仍然在堆中。

JDK6的時候

java中的Person是什么

JDK7的時候

java中的Person是什么

JDK8的時候,元空間大小只受物理內(nèi)存影響

java中的Person是什么

為什么永久代要被元空間替代?

JRockit是和HotSpot融合后的結(jié)果,因為JRockit沒有永久代,所以他們不需要配置永久代

  • 隨著Java8的到來,HotSpot VM中再也見不到永久代了。但是這并不意味著類的元數(shù)據(jù)信息也消失了。這些數(shù)據(jù)被移到了一個與堆不相連的本地內(nèi)存區(qū)域,這個區(qū)域叫做元空間(Metaspace)。

  • 由于類的元數(shù)據(jù)分配在本地內(nèi)存中,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間,這項改動是很有必要的,原因有:

1)為永久代設(shè)置空間大小是很難確定的。

在某些場景下,如果動態(tài)加載類過多,容易產(chǎn)生Perm區(qū)的oom。比如某個實際Web工 程中,因為功能點比較多,在運行過程中,要不斷動態(tài)加載很多類,經(jīng)常出現(xiàn)致命錯誤。

“Exception in thread‘dubbo client x.x connector'java.lang.OutOfMemoryError:PermGen space”

而元空間和永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內(nèi)存。 因此,默認情況下,元空間的大小僅受本地內(nèi)存限制。

2)對永久代進行調(diào)優(yōu)是很困難的。

  • 主要是為了降低Full GC

有些人認為方法區(qū)(如HotSpot虛擬機中的元空間或者永久代)是沒有垃圾收集行為的,其實不然。《Java虛擬機規(guī)范》對方法區(qū)的約束是非常寬松的,提到過可以不要求虛擬機在方法區(qū)中實現(xiàn)垃圾收集。事實上也確實有未實現(xiàn)或未能完整實現(xiàn)方法區(qū)類型卸載的收集器存在(如JDK11時期的ZGC收集器就不支持類卸載)。 一般來說這個區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻。但是這部分區(qū)域的回收有時又確實是必要的。以前sun公司的Bug列表中,曾出現(xiàn)過的若干個嚴重的Bug就是由于低版本的HotSpot虛擬機對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏

方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不在使用的類型

StringTable為什么要調(diào)整位置

  • JDK7中將StringTable放到了堆空間中。因為永久代的回收效率很低,在full gc的時候才會觸發(fā)。而FULL  GC是老年代的空間不足、永久代不足時才會觸發(fā)。

  • 這就導(dǎo)致StringTable回收效率不高。而我們開發(fā)中會有大量的字符串被創(chuàng)建,回收效率低,導(dǎo)致永久代內(nèi)存不足。放到堆里,能及時回收內(nèi)存。

靜態(tài)變量存放在那里?

靜態(tài)引用對應(yīng)的對象實體始終都存在堆空間

可以使用 jhsdb.exe,需要在jdk9的時候才引入的

staticobj隨著Test的類型信息存放在方法區(qū),instanceobj隨著Test的對象實例存放在Java堆,localobject則是存放在foo()方法棧幀的局部變量表中。

java中的Person是什么

測試發(fā)現(xiàn):三個對象的數(shù)據(jù)在內(nèi)存中的地址都落在Eden區(qū)范圍內(nèi),所以結(jié)論:只要是對象實例必然會在Java堆中分配。

接著,找到了一個引用該staticobj對象的地方,是在一個java.lang.Class的實例里,并且給出了這個實例的地址,通過Inspector查看該對象實例,可以清楚看到這確實是一個java.lang.Class類型的對象實例,里面有一個名為staticobj的實例字段:

java中的Person是什么

從《Java虛擬機規(guī)范》所定義的概念模型來看,所有Class相關(guān)的信息都應(yīng)該存放在方法區(qū)之中,但方法區(qū)該如何實現(xiàn),《Java虛擬機規(guī)范》并未做出規(guī)定,這就成了一件允許不同虛擬機自己靈活把握的事情。JDK7及其以后版本的HotSpot虛擬機選擇把靜態(tài)變量與類型在Java語言一端的映射class對象存放在一起,存儲于Java堆之中,從我們的實驗中也明確驗證了這一點

六 方法區(qū)的垃圾回收

有些人認為方法區(qū)(如Hotspot虛擬機中的元空間或者永久代)是沒有垃圾收集行為的,其實不然?!禞ava虛擬機規(guī)范》對方法區(qū)的約束是非常寬松的,提到過可以不要求虛擬機在方法區(qū)中實現(xiàn)垃圾收集。事實上也確實有未實現(xiàn)或未能完整實現(xiàn)方法區(qū)類型卸載的收集器存在(如JDK11時期的zGC收集器就不支持類卸載)。

一般來說這個區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當苛刻。但是這部分區(qū)域的回收有時又確實是必要的。以前sun公司的Bug列表中,曾出現(xiàn)過的若干個嚴重的Bug就是由于低版本的HotSpot虛擬機對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。

方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不再使用的類型。

  • 先來說說方法區(qū)內(nèi)常量池之中主要存放的兩大類常量:字面量和符號引用。字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,包括下面三類常量:

    • 類和接口的全限定名

    • 字段的名稱和描述符

    • 方法的名稱和描述符

  • HotSpot虛擬機對常量池的回收策略是很明確的,只要常量池中的常量沒有被任何地方引用,就可以被回收。

  • 回收廢棄常量與回收Java堆中的對象非常類似。(關(guān)于常量的回收比較簡單,重點是類的回收)

  • 判定一個常量是否“廢棄”還是相對簡單,而要判定一個類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時滿足下面三個條件:

    • 該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實例。

    • 加載該類的類加載器已經(jīng)被回收,這個條件除非是經(jīng)過精心設(shè)計的可替換類加載器的場景,如OSGi、JSP的重加載等,否則通常是很難達成的。

    • 該類對應(yīng)的java.lang.C1ass對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。I

  • Java虛擬機被允許對滿足上述三個條件的無用類進行回收,這里說的僅僅是“被允許”,而并不是和對象一樣,沒有引用了就必然會回收。關(guān)于是否要對類型進行回收,HotSpot虛擬機提供了-Xnoclassgc參數(shù)進行控制,還可以使用-verbose:class 以及 -XX:+TraceClass-Loading、-XX:+TraceClassUnLoading查看類加載和卸載信息

  • 在大量使用反射、動態(tài)代理、CGLib等字節(jié)碼框架,動態(tài)生成JSP以及oSGi這類頻繁自定義類加載器的場景中,通常都需要Java虛擬機具備類型卸載的能力,以保證不會對方法區(qū)造成過大的內(nèi)存壓力。

總結(jié)

java中的Person是什么

關(guān)于java中的Person是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI