成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

JDK 方法區(qū)變遷史:版本間的差異與改進(jìn)

開發(fā)
經(jīng)常看到初學(xué)JVM的讀者會因?yàn)榉椒▍^(qū)這一概念提出一些混淆的問題和概念,所以這篇文章來幫助讀者梳理一下JVM中方法區(qū)的概念。

經(jīng)常看到初學(xué)JVM的讀者會因?yàn)榉椒▍^(qū)這一概念提出下面這些混淆的問題和概念:

  • 什么是方法區(qū)?
  • 方法區(qū)和永久代還有元空間是什么關(guān)系?
  • JDK8版本的常量和靜態(tài)變量是在堆區(qū)?永久代?還是方法區(qū)?還是元空間?

所以,筆者這里就以這篇文章來幫助讀者梳理一下JVM中方法區(qū)的概念。

詳解各版本JVM方法區(qū)

方法區(qū)簡介

方法區(qū)其實(shí)是一個(gè)《Java虛擬機(jī)規(guī)范》一個(gè)邏輯上的概念,對于不同版本的JVM都有不同的實(shí)現(xiàn),就以我們常用的HotSpot JVM而言,方法區(qū)還有一個(gè)別名叫Non-Heap,即非堆內(nèi)存,這么定義的目的自然是要讓Java開發(fā)者明白方法區(qū)和堆是一塊獨(dú)立于Java堆的內(nèi)存空間,而這里筆者也列出方法區(qū)幾個(gè)通用的概念:

  • 方法區(qū)和Java堆內(nèi)存一樣也是屬于各個(gè)線程共享的內(nèi)存區(qū)域。
  • 方法區(qū)在JVM啟動就時(shí)創(chuàng)建,并且它實(shí)際的物理內(nèi)存空間和Java堆內(nèi)存一樣可以是不連續(xù)的,注意筆者所說,可以是不連續(xù)的。
  • 方法區(qū)內(nèi)存大小也可以選擇固定大小或者可擴(kuò)展。
  • 方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類,如果系統(tǒng)定義了太多的類,同樣會出現(xiàn)內(nèi)存溢出的問題,可能是java.lang.OutOfMemoryError:PermGen space(永久代空間滿了),也可能是java.lang.OutOfMemoryError:Metaspace(元空間滿了),這一點(diǎn)筆者會在后文中方法區(qū)在各個(gè)版本中的實(shí)現(xiàn)進(jìn)行拓展說明。

這里我們補(bǔ)充說明一下,后文所涉及的不同版本的JVM版本都是以HotSpot虛擬機(jī)展開探討。

JDK7之前的版本

先來在JDK7之前的版本內(nèi)存結(jié)構(gòu)圖,在這些版本上邏輯上方法區(qū)和堆區(qū)在邏輯上是連續(xù)的,實(shí)際上在物理內(nèi)存上來說,它們卻可是一塊連續(xù)的內(nèi)存。在JDK7之前的版本,它們都用的是一個(gè)名為PermGen(永久代)的虛作為方法區(qū)的實(shí)現(xiàn)。 這也是為什么很多讀者會把永久代和老年代混淆,實(shí)際上這兩個(gè)完全不是一個(gè)概念,在JDK7之前的版本,永久代僅僅是作為方法區(qū)的實(shí)現(xiàn)并和老年代捆綁在一起,當(dāng)老年代或者永久代任何一個(gè)內(nèi)存空間滿了的時(shí)候,都會觸發(fā)一次垃圾收集。

在這些個(gè)版本的JVM,方法區(qū)即永久代存儲的是:

  • 類信息
  • 字段信息
  • 方法信息
  • 常量
  • 靜態(tài)變量
  • 即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)

JDK7版本的變化

JDK7則是基于原有的內(nèi)存結(jié)構(gòu)的基礎(chǔ)上將部分?jǐn)?shù)據(jù)進(jìn)行轉(zhuǎn)移:

  • 將符號引用(Symbols)轉(zhuǎn)移到Native Memory(本地內(nèi)存),可能很多讀者經(jīng)常聽到本地內(nèi)存這一概念,這里筆者進(jìn)行拓展解釋一下,本地內(nèi)存即JVM運(yùn)行時(shí)內(nèi)存,它是不受GC管理的一塊內(nèi)存區(qū)域,是直接由操作系統(tǒng)分配給JVM的一塊內(nèi)存,需要程序手動進(jìn)行獲取和釋放。
  • 因?yàn)橛谰么腉C是跟隨著老年代觸發(fā)的,所以考慮到垃圾回收的效率,JDK7將所有字符串常量的信息都直接移動到Java Heap中。
  • 類的靜態(tài)變量轉(zhuǎn)移到Java Heap中。

JDK8版本對于方法區(qū)的實(shí)現(xiàn)

最后我們再來說說現(xiàn)主流的JDK8版本,它基于JDK7的存儲方式,將永久代(Perm Gen) 改為元空間(Metaspace) 作為方法區(qū)的實(shí)現(xiàn),同時(shí)元空間不再與堆內(nèi)存連續(xù),是一個(gè)劃分在本地內(nèi)存(Native memory) 的一塊內(nèi)存區(qū)域,這也就意味著JDK8版本實(shí)現(xiàn)的方法區(qū)不參與Java Heap的GC,僅僅處理元數(shù)據(jù)空間那些已卸載類的垃圾回收。

所以JDK8版本的內(nèi)存結(jié)構(gòu)最終如下圖所示,這也就意味著JDK7版本對永久代的設(shè)置參數(shù)(-XX:MaxPermSize) 變?yōu)闊o效參數(shù),取而代之的是對元空間空間大小設(shè)置的參數(shù)(-XX:MetaspaceSize)。

實(shí)踐驗(yàn)證觀點(diǎn)

接下來我們通過幾段代碼來印證筆者的觀點(diǎn),來看看這段代碼,筆者這里直接聲明了一段最大長度的靜態(tài)數(shù)組,這個(gè)數(shù)組長度為Integer.MAX_VALUE,粗略估算這個(gè)數(shù)組大致需要占用4G左右的內(nèi)存空間。

//聲明一個(gè)靜態(tài)數(shù)組
 public static int[] arr=new int[Integer.MAX_VALUE];

    public void test(){
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public static void main(String[] args) {
       new Main().test();
    }

輸出結(jié)果如下,可以看到直接拋出了OOM異常,這也就意味著靜態(tài)變量在JDK8版本的堆內(nèi)存中。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
 at com.sharkChili.webTemplate.Main.<clinit>(Main.java:16)
Exception in thread "main" 

同理的再來看看這段代碼。筆者聲明了一個(gè)常量數(shù)組,如果它也存在于堆內(nèi)存中的話,那么它的運(yùn)行結(jié)果也是OOM:

//常量全局?jǐn)?shù)組
 final int[] arr = new int[Integer.MAX_VALUE];

    public void test() {
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public static void main(String[] args) {
        new Main().test();
    }

意料之內(nèi),在JDK8版本常量也是分配于堆內(nèi)存中:

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
 at com.sharkChili.webTemplate.Main.<init>(Main.java:15)
 at com.sharkChili.webTemplate.Main.main(Main.java:25)

接下來這個(gè)實(shí)驗(yàn)比較特殊,我們都知道CGLIB是一個(gè)強(qiáng)大且高性能的字節(jié)碼生成庫,它支持運(yùn)行時(shí)擴(kuò)展Java類或接口實(shí)現(xiàn),本質(zhì)上就是動態(tài)生成一個(gè)子類并覆蓋要代理的類。所以為了驗(yàn)證JDK8版本的類信息是否是存于堆區(qū)還是方法區(qū),我們就基于一個(gè)CGLIB通過無限循環(huán)去創(chuàng)建無數(shù)的代理類,讓JVM去存儲這些類定義的信息,看看最終拋出的是OOM還是元空間不足。

為了能夠更快看到效果,筆者手動調(diào)整了一下元空間的大小:

-XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m

示例代碼如下,通過無限循環(huán)生成代理類并創(chuàng)建EmptyObject的代理對象:

public static void main(String[] args) {
       while (true){
           Enhancer enhancer = new Enhancer();
           //設(shè)置代理目標(biāo)
           enhancer.setSuperclass(EmptyObject.class);
   //不生成同屬性類的靜態(tài)緩存
           enhancer.setUseCache(false);

           //設(shè)置單一回調(diào)對象,在調(diào)用中攔截對目標(biāo)方法的調(diào)用
           enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(objects, args));
   //如有必要,生成一個(gè)新類,并使用指定的回調(diào)(如果有的話)創(chuàng)建一個(gè)新的對象實(shí)例。
           enhancer.create();
       }

    }

啟動后我們使用jvisualvm查看當(dāng)前程序的GC情況,可以看到Java Heap運(yùn)行正常,即時(shí)創(chuàng)建的無用代理對象都會被回收掉:

再來看看元空間,可以看到隨著實(shí)踐的推移,無數(shù)個(gè)全新的代理類的信息存到元空間,因?yàn)樵臻g不受GC管理,所以使用內(nèi)存不斷增加:

最終如預(yù)期所說出現(xiàn)java.lang.OutOfMemoryError: Metaspace:

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
 at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
 at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
 at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
 at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
 at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
 at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
 at com.sharkChili.webTemplate.Main.main(Main.java:39)

常見面試題

1.為什么JDK8要將取消永久代的概念

大體來說取消永久代有以下兩個(gè)原因:

  • 首要原因是Hotspot和JRockit代碼合并,前者并沒有所謂的永久代。
  • 為了提高垃圾的回收的效率。我們都知道在JDK8版本之前老年代和永久代內(nèi)存空間是連續(xù)的,任何一個(gè)滿了都可能觸發(fā)GC,這種做法對于永久代來說回收效率偏低(每次GC基本回收不了多少垃圾),且Hotspot為了做到這一點(diǎn)還需要專門對元數(shù)據(jù)信息進(jìn)行特殊處理,所以為了簡化GC處理,JDK8版本就將方法區(qū)改為使用元空間實(shí)現(xiàn),如此后續(xù)對于元數(shù)據(jù)內(nèi)存優(yōu)化可以專門處理而無需考慮對于堆空間的影響。

2.什么是方法區(qū)?是如何實(shí)現(xiàn)的?

方法區(qū)是Java虛擬機(jī)規(guī)范中定義的一塊用于存儲類信息、常量、靜態(tài)變量以及編譯器便后的代碼數(shù)據(jù)的邏輯內(nèi)存區(qū)域,注意這里筆者所強(qiáng)調(diào)的是邏輯內(nèi)存邏輯區(qū)域,而非物理形式的內(nèi)存區(qū)域,而對應(yīng)的內(nèi)存實(shí)現(xiàn),在不同的JDK版本不同的實(shí)現(xiàn):

  • 在JDK6的版本方法區(qū)都是通過永久代進(jìn)行實(shí)現(xiàn),存儲類信息、常量池(包括字符串常量池)、靜態(tài)變量和JIT編譯器編譯后的代碼等數(shù)據(jù)。
  • JDK7方法區(qū)還是永久代實(shí)現(xiàn),只不過將字符串常量池和靜態(tài)變量都存放到堆內(nèi)存中,主要原因是永久代GC效率太低,只有在full gc的時(shí)候才會回收,所以將字符串常量池放到堆區(qū)保證高效的回收字符串。
  • 從JDK8開始方法區(qū)的實(shí)現(xiàn)直接用元空間來實(shí)現(xiàn),而元空間使用的即native memory,也就是本地內(nèi)存,而本地內(nèi)存即動態(tài)向操作系統(tǒng)獲取的內(nèi)存空間,需要程序手動進(jìn)行獲取和釋放,從Java的角度來說就是不受JVM虛擬機(jī)所約束的內(nèi)存空間。也正是因?yàn)檫@幾個(gè)特點(diǎn),保證元空間可以根據(jù)應(yīng)用程序的需求動態(tài)調(diào)整大小,避免永久代內(nèi)存溢出問題的同時(shí)還減少的GC回收的壓力。
責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2020-12-21 10:14:48

黑客網(wǎng)絡(luò)安全網(wǎng)絡(luò)攻擊

2011-08-23 10:49:44

算法

2024-01-26 08:33:14

JDK17JDK11版本

2014-12-31 17:16:15

知乎架構(gòu)變遷史

2012-12-20 14:09:20

瀏覽器

2025-02-27 00:32:35

2010-07-30 08:30:38

VisualVMVisualVM 1.VisualVM 1.

2023-09-05 08:16:14

API架構(gòu)

2011-07-18 09:34:51

2010-07-30 13:17:33

NFS V3

2011-07-29 18:03:30

IT職位變遷云計(jì)算

2019-11-07 21:41:21

AndroidiOS不同

2009-07-07 16:10:02

JDK最新版本JDK安裝JDK下載

2011-04-12 14:26:04

電信間工作區(qū)綜合布線

2020-09-20 17:50:38

編程語言PythonJava

2021-02-08 23:10:08

春晚紅包互聯(lián)網(wǎng)

2018-05-21 09:03:00

NASSAN案例

2011-11-03 15:25:07

Android

2009-08-18 22:15:38

VMware快照改進(jìn)方

2020-11-29 19:09:28

iOS蘋果 系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 亚洲精品一区二区网址 | 久久久www | 亚洲国产专区 | 日韩一级在线 | 国产精品永久 | 国产精品一区二区三 | 亚洲黄色在线免费观看 | 成人性视频免费网站 | 天天干天天操天天看 | 欧美一级淫片007 | 91久久北条麻妃一区二区三区 | 91久久国产精品 | 91黄色免费看 | 欧美日本在线观看 | 成人网av| 久久久久成人精品 | 中文字幕一区二区三区日韩精品 | 日韩中文字幕 | 亚洲精品视 | 欧洲一区二区三区 | 日韩视频一区二区 | 国产精品电影在线观看 | 四虎成人免费电影 | 精品国产一区二区三区日日嗨 | 秋霞精品| 一区二区三区在线观看视频 | 欧美午夜一区二区三区免费大片 | 一区二区三区成人 | 毛片入口 | 超碰成人在线观看 | 欧美精品欧美精品系列 | 欧美在线视频网 | 一区二区在线 | av免费电影在线 | 四虎最新视频 | 日韩2020狼一二三 | 日本视频中文字幕 | 综合久久99| 97精品视频在线观看 | 久久一| 成人精品鲁一区一区二区 |