java虛擬機(jī)中的內(nèi)存區(qū)域劃分-創(chuàng)新互聯(lián)

一、概述

在雙流等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì) 網(wǎng)站設(shè)計(jì)制作定制開(kāi)發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計(jì),全網(wǎng)整合營(yíng)銷(xiāo)推廣,外貿(mào)營(yíng)銷(xiāo)網(wǎng)站建設(shè),雙流網(wǎng)站建設(shè)費(fèi)用合理。

Java虛擬機(jī)規(guī)范規(guī)定的java虛擬機(jī)內(nèi)存其實(shí)就是java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū),其架構(gòu)如下:

java虛擬機(jī)中的內(nèi)存區(qū)域劃分

其中方法區(qū)和堆是由所有線程共享的數(shù)據(jù)區(qū)。

虛擬機(jī)棧,本地方法棧和程序計(jì)數(shù)器是線程隔離的數(shù)據(jù)區(qū)。

二、詳解

下面來(lái)具體介紹這幾個(gè)數(shù)據(jù)區(qū)。

1、程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,其作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解析器工作時(shí)通過(guò)改變程序計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。程序的分支、循環(huán)、跳轉(zhuǎn)、異常處理以及線程恢復(fù)等基礎(chǔ)功能都是依賴程序計(jì)數(shù)器來(lái)完成。

Java虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間片來(lái)實(shí)現(xiàn),在任何一個(gè)時(shí)刻,一個(gè)處理器只會(huì)執(zhí)行一條線程指令。因此,為了確保線程切換之后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器,因此程序計(jì)數(shù)器是線程私有的內(nèi)存。

如果線程正在執(zhí)行一個(gè)java方法,則計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。如果正在執(zhí)行的是Native方法( 簡(jiǎn)單地講,一個(gè)Native Method就是一個(gè)java調(diào)用非java代碼的接口。該方法的實(shí)現(xiàn)由非java語(yǔ)言實(shí)現(xiàn),比如C。這個(gè)特征并非java所特有,很多其它的編程語(yǔ)言都有這一機(jī)制,比如在C++中,你可以用extern "C"告知C++編譯器去調(diào)用一個(gè)C的函數(shù)。),則計(jì)數(shù)器的值為空。程序計(jì)數(shù)器是java虛擬機(jī)中唯一一個(gè)沒(méi)有規(guī)定任何內(nèi)存溢出OutOfMemoryError情況的內(nèi)存區(qū)域。

2、java虛擬機(jī)棧

Java虛擬機(jī)棧也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行時(shí)都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀用于存放局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法出口等信息。每個(gè)方法被調(diào)用直至執(zhí)行完成過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中從入棧到出棧的過(guò)程。

Java虛擬機(jī)棧的局部變量表存放了編譯器可知的8種java基本類型數(shù)據(jù)(boolean、byte、char、short、int、float、long、double)、對(duì)象引用refrence(注意不是對(duì)象實(shí)例本身)、方法返回地址returnAddress(指向了一條字節(jié)碼指令的地址)。

Java虛擬機(jī)棧的局部變量表空間單位是槽(Slot),其中64位長(zhǎng)度的double和long類型會(huì)占用兩個(gè)slot,其余的數(shù)據(jù)類型只占用一個(gè)slot。局部變量表所需內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),該方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。

Java虛擬機(jī)棧有兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的大深度時(shí),拋出StackOverflowError異常;如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常。

3、本地方法棧

本地方法棧與java虛擬機(jī)棧作用非常類似,其區(qū)別是:java虛擬機(jī)棧是為虛擬機(jī)執(zhí)行java方法服務(wù),而本地方法棧是為虛擬機(jī)調(diào)用的操作系統(tǒng)本地方法服務(wù)(如Native方法)。

Java虛擬機(jī)規(guī)范沒(méi)有對(duì)本地方法棧的實(shí)現(xiàn)和數(shù)據(jù)結(jié)構(gòu)做強(qiáng)制規(guī)定,Sun HotSpot虛擬機(jī)直接把java虛擬機(jī)棧和本地方法棧合二為一。

與java虛擬機(jī)棧類似,本地方法棧也會(huì)拋出StackOverflowError異常和OutOfMemoryError異常。

4、堆

堆是java虛擬機(jī)所管理的內(nèi)存區(qū)域中大一塊,java堆是被所有線程所共享的一塊內(nèi)存區(qū)域,在java虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,堆內(nèi)存的唯一目的就是存放對(duì)象實(shí)例。幾乎所有的對(duì)象實(shí)例(包括數(shù)組)都是在堆分配內(nèi)存。

Java堆是垃圾收集器管理的主要區(qū)域,從垃圾回收的角度看,由于現(xiàn)在的垃圾收集器基本都采用的是分代收集算法,因此java堆還可以初步細(xì)分為新生代和年老代。

Java虛擬機(jī)規(guī)范規(guī)定,堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可。在實(shí)現(xiàn)上即可以是固定大小的,也可以是可動(dòng)態(tài)擴(kuò)展的。如果在堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆大小也無(wú)法在擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。

堆設(shè)置的常用參數(shù)如下:??

-Xms :初始堆大小??

-Xmx :大堆大小,當(dāng)Xms和Xmx設(shè)置相同時(shí),堆就無(wú)法進(jìn)行自動(dòng)擴(kuò)展。

-XX:NewSize=n :設(shè)置年輕代大小??

-XX:NewRatio=n: 設(shè)置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個(gè)年輕代年老代和的1/4??

-XX:SurvivorRatio=n :年輕代中Eden區(qū)與Survivor區(qū)的比值。注意Survivor區(qū)有兩個(gè)。如:XX:SurvivorRatio=3,表示Eden區(qū)的大小是一個(gè)Survivor區(qū)的三倍,但Survivor區(qū)有兩個(gè),那么Eden:Survivor=3:2,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/5。?

-XX:MaxPermSize=n :設(shè)置持久代大小??

5、方法區(qū)

方法區(qū)與堆一樣,是被各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)。雖然java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是方法區(qū)卻有一個(gè)別名叫Non-Heap(非堆)。

Sun HotSpot虛擬機(jī)把方法區(qū)叫永久代(Permanent Generation),方法區(qū)中最重要的部分是運(yùn)行時(shí)常量池。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面變量、符號(hào)引用、直接引用等,這些內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中,另外在運(yùn)行期間也可以將新的常量存放到常量池中,如String的intern()方法。當(dāng)調(diào)用 intern 方法時(shí),如果常量池已經(jīng)包含一個(gè)等于此 String 對(duì)象的字符串(該對(duì)象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對(duì)象添加到池中,并且返回此 String 對(duì)象的引用。

方法區(qū)和運(yùn)行時(shí)常量池在無(wú)法滿足內(nèi)存分配時(shí),也會(huì)拋出OutOfMemoryError異常。

6、直接內(nèi)存

直接內(nèi)存并不是java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是在java開(kāi)發(fā)中還是會(huì)使用到。

JDK1.4中新引入的NIO(new I/O),引入了一種基于通道(Channel)和緩沖區(qū)(Buffer)的I/O方式,可以使用操作系統(tǒng)本地方法庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在java堆里面的DirectByteBuffer對(duì)象作為堆外直接內(nèi)存的引用進(jìn)行操作,避免了java堆內(nèi)存和本地直接內(nèi)存間的數(shù)據(jù)拷貝,可以顯著提高性能。

雖然直接內(nèi)存并不直接收到j(luò)ava虛擬機(jī)內(nèi)存影響,但是如果java虛擬機(jī)各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制,從而導(dǎo)致直接內(nèi)存不足,動(dòng)態(tài)擴(kuò)展時(shí)也會(huì)拋出OutOfMemoryError異常。

三、實(shí)例演示

下面通過(guò)簡(jiǎn)單的小例子程序,演示Java虛擬機(jī)各部分內(nèi)存溢出情況:

1、java堆溢出

Java堆用于存儲(chǔ)實(shí)例對(duì)象,只要不斷創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有引用的可達(dá),來(lái)避免垃圾收集器回收實(shí)例對(duì)象,就會(huì)在對(duì)象數(shù)量達(dá)到堆大容量時(shí)產(chǎn)生OutOfMemoryError異常。

想要方便快速地產(chǎn)生堆溢出,要使用如下java虛擬機(jī)參數(shù):-Xms=10m(最小堆內(nèi)存為10MB),-Xmx=10m(大堆內(nèi)存為10MB,最小堆內(nèi)存和大堆內(nèi)存相同是為了避免堆動(dòng)態(tài)擴(kuò)展),-XX:+HeapDumpOnOutOfMemoryError可以讓java虛擬機(jī)在出現(xiàn)內(nèi)存溢出時(shí)產(chǎn)生當(dāng)前堆內(nèi)存快照以便進(jìn)行異常分析。

例子代碼如下:

public class HeapOOM{??

? static class OOMObject{??

}??

public static void main(String[] args){??

? List<OOMObject> list = new ArrayList<OOMObject>();??

? while(true){??

? list.add(new OOMObject());??

}??

}??

}??

運(yùn)行一段時(shí)間就會(huì)發(fā)現(xiàn)產(chǎn)生OutOfMemoryError異常,并且產(chǎn)生了堆內(nèi)存異常dump文件。

2、java虛擬機(jī)棧和本地方法棧溢出

由于Sun的HotSpot虛擬機(jī)不區(qū)分java虛擬機(jī)棧和本地方法棧,因此對(duì)于HotSpot虛擬機(jī)來(lái)說(shuō)-Xoss參數(shù)(設(shè)置本地方法棧大小)雖然存在,但是實(shí)際上是無(wú)效的,棧容量只能由-Xss參數(shù)設(shè)定。

由于Java虛擬機(jī)棧會(huì)出現(xiàn)StackOverflowError和OutOfMemoryError兩種異常,所以分別使用兩個(gè)例子演示這兩種情況:

(1)java虛擬機(jī)棧深度溢出

單線程的環(huán)境下,無(wú)論是由于棧幀太大,還是虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無(wú)法再分配的時(shí)候,虛擬機(jī)總拋出StackOverflowError異常。使用-Xss128k將java虛擬機(jī)棧大小設(shè)置為128kb,例子代碼如下:

public class JavaVMStackOF{??

? private int stackLength = 1;??

? public void stackLeak(){??

? statckLength++;??

? stackLeak();??

}??

public static void main(String[] args){??

? JavaVMStackOF oom = new JavaVMStackOF();??

oom.stackLeak();??

}??

}??

運(yùn)行一段時(shí)間后,產(chǎn)生StackOverflowError異常。Java虛擬機(jī)棧溢出一般會(huì)產(chǎn)生在方法遞歸調(diào)用過(guò)多而java虛擬機(jī)棧內(nèi)存不夠的情況下。

(2)java虛擬機(jī)棧內(nèi)存溢出

多線程環(huán)境下,能夠創(chuàng)建的線程大內(nèi)存=物理內(nèi)存-大堆內(nèi)存-大方法區(qū)內(nèi)存,在java虛擬機(jī)棧內(nèi)存一定的情況下,單個(gè)線程占用的內(nèi)存越大,所能創(chuàng)建的線程數(shù)目越小,所以在多線程條件下很容易產(chǎn)生java虛擬機(jī)棧內(nèi)存溢出的異常。

使用-Xss2m參數(shù)設(shè)置java虛擬機(jī)棧內(nèi)存大小為2MB,例子代碼如下:

public class JavaVMStackOOM{??

? private void dontStop(){??

? while(true){??

}??

}??

public void stackLeakByThread(){??

? while(true){??

? Thread t = new Thread(new Runnable(){??

? public void run(){??

? dontStop();??

}??

});??

t.start();??

}??

}? ?

public static void main(String[] args){??

? JavaVMStackOOM oom = new JavaVMStackOOM();??

? oom. stackLeakByThread();.??

}??

}??

運(yùn)行一段時(shí)間之后,java虛擬機(jī)棧就會(huì)因?yàn)閮?nèi)存太小無(wú)法創(chuàng)建線程而產(chǎn)生OutOfMemoryError。

3、運(yùn)行時(shí)常量池溢出

運(yùn)行時(shí)常量池屬于方法區(qū)的一部分,可以使用-XX:PermSize=10m和-XX:MaxPermSize=10m將永久代大內(nèi)存和最小內(nèi)存設(shè)置為10MB大小,并且由于永久代大內(nèi)存和最小內(nèi)存大小相同,因此無(wú)法擴(kuò)展。

String的intern()方法用于檢查常量池中如果有等于此String對(duì)象的字符串存在,則直接返回常量池中的字符串對(duì)象,否則,將此String對(duì)象所包含的字符串添加到運(yùn)行時(shí)常量池中,并返回此String對(duì)象的引用。因此String的intern()方法特別適合演示運(yùn)行時(shí)常量池溢出,例子代碼如下:

public class RuntimeConstantPoolOOM{??

? public static void main(String[] args){??

List<String> list = new ArrayList<String>();??

? int i = 0;??

? while(true){??

? list.add(String.valueOf(i++).intern());??

}??

}??

}??

運(yùn)行一段時(shí)間,永久代內(nèi)存不夠,運(yùn)行時(shí)常量池因無(wú)法再添加常量而產(chǎn)生OutOfMemoryError。

4、方法區(qū)溢出

運(yùn)行時(shí)常量池是方法區(qū)的一部分,他們都屬于HotSpot虛擬機(jī)中的永久代內(nèi)存區(qū)域。方法區(qū)用于存放Class的相關(guān)信息,Java的反射和動(dòng)態(tài)代理可以動(dòng)態(tài)產(chǎn)生Class,另外第三方的CGLIB可以直接操作字節(jié)碼,也可以動(dòng)態(tài)產(chǎn)生Class,實(shí)驗(yàn)通過(guò)CGLIB來(lái)演示,同樣使用-XX:PermSize=10m和-XX:MaxPermSize=10m將永久代大內(nèi)存和最小內(nèi)存設(shè)置為10MB大小,并且由于永久代大內(nèi)存和最小內(nèi)存大小相同,因此無(wú)法擴(kuò)展。例子代碼如下:

public class JavaMethodAreaOOM{??

? public static void main(String[] args){??

? while(true){??

? Enhancer enhancer = new Enhancer();??

? enhancer.setSuperClass(OOMObject.class);??

? enhancer.setUseCache(false);??

? enhancer.setCallback(new MethodInterceptor(){??

? public Object intercept(Object obj, Method method, Object[] args,? ?

MethodProxy proxy)throws Throwable{??

? return proxy.invokeSuper(obj, args);??

}??

});??

enhancer.create();??

}??

}??

class OOMObject{??

}? ?

}??

運(yùn)行一段時(shí)間之后,永久代內(nèi)存不夠,方法區(qū)無(wú)法再存放CGLIB創(chuàng)建處理的Class信息,產(chǎn)生方法區(qū)OutOfMemoryError。

5、本機(jī)直接內(nèi)存溢出

Java虛擬機(jī)可以通過(guò)參數(shù)-XX:MaxDirectMemorySize設(shè)定本機(jī)直接內(nèi)存可用大小,如果不指定,則默認(rèn)與java堆內(nèi)存大小相同。JDK中可以通過(guò)反射獲取Unsafe類(Unsafe的getUnsafe()方法只有啟動(dòng)類加載器Bootstrap才能返回實(shí)例)直接操作本機(jī)直接內(nèi)存。通過(guò)使用-XX:MaxDirectMemorySize=10M,限制大可使用的本機(jī)直接內(nèi)存大小為10MB,例子代碼如下:

public class DirectMemoryOOM{??

? private static final int _1MB = 1024* 1024 * 1024;??

? publc static void main(String[] args) throws Exception{??

? Field unsafeField = Unsafe.class.getDeclaredFields()[0];??

? unsafeField.setAccessible(true);??

? Unsafe unsafe = (Unsafe) unsafeField.get(null);??

? while(true){??

? //unsafe直接想操作系統(tǒng)申請(qǐng)內(nèi)存??

? unsafe.allocateMemory(_1MB);??

}??

}??

}??

當(dāng)運(yùn)行一段時(shí)間之后,10MB的本機(jī)直接內(nèi)存被分配光,無(wú)法在進(jìn)行直接內(nèi)存分配時(shí),產(chǎn)生OutOfMemoryError。

三、總結(jié)

java虛擬機(jī)內(nèi)存結(jié)構(gòu)中的程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧這三個(gè)區(qū)域隨線程創(chuàng)建而生,隨線程銷(xiāo)毀而滅,因此這三個(gè)區(qū)域的內(nèi)存分配和回收是確定的,java垃圾收集器重點(diǎn)關(guān)注的是java虛擬機(jī)的堆內(nèi)存和方法區(qū)內(nèi)存。

歡迎加入Java技術(shù)交流群:659270626

群內(nèi)提供免費(fèi)的學(xué)習(xí)指導(dǎo),提供Spring源碼、MyBatis、Netty、Redis,Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx、分布式、高并發(fā)、性能調(diào)優(yōu)等架構(gòu)技術(shù)架構(gòu)資料以及免費(fèi)的解答

不懂的問(wèn)題都可以在本群提出來(lái),之后還會(huì)有職業(yè)生涯規(guī)劃以及面試指導(dǎo) 。

創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買(mǎi)多久送多久。

網(wǎng)頁(yè)題目:java虛擬機(jī)中的內(nèi)存區(qū)域劃分-創(chuàng)新互聯(lián)
本文鏈接:http://www.muchs.cn/article48/dhoehp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作企業(yè)建站、動(dòng)態(tài)網(wǎng)站、標(biāo)簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站