? ? ? ?我們的java代碼經(jīng)過了類加載到了JVM運(yùn)行時(shí)候缅叠,就需要使用到多塊內(nèi)存空間,不同的空間存放不同的數(shù)據(jù),配合代碼流程,才能讓程真正運(yùn)行起來钓账。
? ? ? JVM內(nèi)存劃分在JDK1.8之后和之前是略有不同主要是將方法區(qū)調(diào)整為了元空間
JDK1.8之前:
JDK1.8之后:
? ? ? ?圖上線程區(qū)的域的數(shù)據(jù)是私有的却盘,其他區(qū)域的數(shù)據(jù)是共享的勃救。
? ? ? ?內(nèi)存的劃分其實(shí)就是為了我們的代碼在運(yùn)行的過程中根據(jù)不同的需要來使用的迈套,具體有以下:
(1)方法區(qū)(存放類)
? ? ? ? 方法區(qū)與堆一樣,是各個(gè)線程共享內(nèi)存區(qū)域,方法區(qū)也被稱為非堆或者永久代侵佃。方法區(qū)在JDK1.8以前的版本麻昼,代表JVM的一塊區(qū)域。主要存放.class文件里加載進(jìn)來的類馋辈,同時(shí)也會(huì)存放一些類似常量池的東西在這個(gè)區(qū)域抚芦。 但是在JDK1.8之后這里就改名為MetaSpace(元空間),主要還是用來存放各種類相關(guān)信息迈螟。
? ? ? ? 整個(gè)永久代有一個(gè)JVM本身設(shè)置的固定大小上限燕垃,無法進(jìn)行調(diào)整。但是1.8后元空間使用的是直接內(nèi)存井联,受本機(jī)內(nèi)存的限制卜壕,內(nèi)存溢出的幾率比永久代小了很多
(2)運(yùn)行時(shí)常量
? ? ? .class文件中除了存放類版本、字段烙常、方法轴捎、接口等描述信息外、還有常量池(用于存放編譯期生成的各種字面量和符號(hào)引用)蚕脏,?運(yùn)行時(shí)常量是方法區(qū)的一部分侦副,受到方法區(qū)內(nèi)存限制,當(dāng)常量無法在申請(qǐng)到內(nèi)存空間時(shí)候會(huì)拋出OutofMemoryError異常驼鞭。在JDK1.7之前運(yùn)行時(shí)常量池邏輯包含字符串常量池秦驯,JDK1.7字符串常量池被從方法區(qū)拿到了堆中。運(yùn)行時(shí)常量池還在方法區(qū)挣棕,JDK1.8移除了永久代译隘,運(yùn)行時(shí)常量轉(zhuǎn)移到元空間中
(3)程序計(jì)數(shù)器(執(zhí)行代碼指令)
? ? ? ?我們所開發(fā)的代碼是.java文件是面向開發(fā)人員的,但是計(jì)算機(jī)是無法讀懂的洛心,所以就需要編譯為.class字節(jié)碼文件固耘,字節(jié)碼才是計(jì)算機(jī)可以理解的文件。字節(jié)碼的指令會(huì)讓程序執(zhí)行各種操作词身。
? ? ? ? 程序計(jì)數(shù)器可以看作是當(dāng)前線程所執(zhí)行字節(jié)碼指令的行號(hào)指示器厅目,主要有兩個(gè)作用
1.字節(jié)碼解釋器通過改變程序計(jì)數(shù)器,來依次讀取需要執(zhí)行的指令法严,從而實(shí)現(xiàn)代碼的流程控制(順序執(zhí)行损敷、選擇、循環(huán)深啤、異常處理)
2.多線程的情況下拗馒,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來也能知道上次運(yùn)行的位置墓塌。每條線程間的程序計(jì)數(shù)器是互不影響的瘟忱,獨(dú)立存儲(chǔ)、也就是"線程私有"苫幢。
? ? ? ?程序計(jì)數(shù)器是唯一一個(gè)不會(huì)出現(xiàn)OutofMemoryError的內(nèi)存區(qū)域访诱,生命周期是從線程的創(chuàng)建至線程的銷毀。
(4)JAVA虛擬機(jī)棧
? ? ? ?我們?cè)诜椒ㄖ薪?jīng)常會(huì)定義一些局部變量韩肝,就會(huì)保存在JAVA的虛擬機(jī)棧中触菜。JAVA虛擬機(jī)棧是由多個(gè)幀棧組成,如果線程執(zhí)行了一個(gè)方法哀峻,那么就會(huì)對(duì)這個(gè)方法調(diào)用涡相,創(chuàng)建一個(gè)對(duì)應(yīng)的幀棧都包含(局部變量表、操作數(shù)棧剩蟀、動(dòng)態(tài)鏈接催蝗、方法出口等信息)。每一次函數(shù)調(diào)用就會(huì)有一個(gè)對(duì)應(yīng)的幀棧被壓入JAVA棧育特,每個(gè)函數(shù)調(diào)用結(jié)束后就會(huì)有一個(gè)幀棧被彈出丙号,棧的順序是先進(jìn)后出
? ? ? ?JAVA虛擬機(jī)棧也是私有的,生命周期也是從線程的創(chuàng)建至線程的銷毀缰冤。虛擬機(jī)棧會(huì)出現(xiàn)兩種錯(cuò)誤:
1.StackOverFlowError: 如果虛擬棧的內(nèi)存大小不允許動(dòng)態(tài)擴(kuò)展犬缨,那么當(dāng)線程請(qǐng)求棧的深度大于java虛擬機(jī)棧的最大深度時(shí),會(huì)拋出該異常
2.OutofMemoryError: 虛擬機(jī)棧的內(nèi)存大小允許動(dòng)態(tài)擴(kuò)展棉浸,如果虛擬機(jī)在動(dòng)態(tài)擴(kuò)展棧時(shí)怀薛,無法申請(qǐng)到足夠的內(nèi)存空間,會(huì)拋出該異常
(5)本地方法棧
? ? ? ? 與JAVA虛擬機(jī)棧相似迷郑,區(qū)別:虛擬棧執(zhí)行JAVA的方法枝恋,而本地方法棧是為虛擬機(jī)使用到的Native方法服務(wù)(在HotSpot虛擬機(jī)中與JAVA虛擬機(jī)棧合二為一)。棧的順序是先進(jìn)后出嗡害,也會(huì)產(chǎn)生兩種異常
(6)堆
? ? ? ? JAVA的堆內(nèi)存是用來存放我們代碼中創(chuàng)建的各種對(duì)象鼓择,是所有線程共享的一塊區(qū)域。幾乎所有的對(duì)象實(shí)例以及數(shù)組都是在這里分配內(nèi)存就漾,但是隨著JIT動(dòng)態(tài)編譯技術(shù)的發(fā)展與逃逸分析技術(shù)的成熟呐能,從JDK1.7開始已經(jīng)默認(rèn)開啟了逃逸分析,如果某些方法的對(duì)象引用沒有被返回或者是未被使用(也就是未逃逸出去)抑堡,那么對(duì)象可以直接在棧上分配內(nèi)存摆出。堆的順序是先進(jìn)先出
? ? ? ?JAVA堆是垃圾回收的主要區(qū)域,因此也叫GC堆首妖。是最容易產(chǎn)生?OutOfMemoryError 異常偎漫,例如:
1.OutOfMemoryError: GC Overhead Limit Exceeded: 當(dāng)JVM花費(fèi)很長(zhǎng)的時(shí)間執(zhí)行垃圾回收,并且只能回收到很小的堆空間時(shí)有缆,就會(huì)引發(fā)此異常
2.java.lang.OutOfMemoryError: Java heap space: 如果在新建對(duì)象時(shí)象踊,堆內(nèi)存空間存放不下温亲,就會(huì)引發(fā)此異常(和配置的內(nèi)存大小有關(guān),與物理內(nèi)存無關(guān))
? ? ? ? 棧與堆都是JAVA用來存儲(chǔ)數(shù)據(jù)的地方杯矩。棧的優(yōu)勢(shì)為存儲(chǔ)比堆快栈虚,僅次于CPU中的寄存器,缺點(diǎn)是存在棧中的數(shù)據(jù)大小與生命期是確定的史隆,缺乏靈活性魂务。堆的優(yōu)勢(shì)可以動(dòng)態(tài)的分配內(nèi)存大小,生命期也是非固定的泌射,缺點(diǎn)是需要在運(yùn)行是進(jìn)行動(dòng)態(tài)分配粘姜,存取速度較慢
(7)直接內(nèi)存
? ? ? ?直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是虛擬機(jī)的內(nèi)存區(qū)域熔酷,但是這部分內(nèi)存也被頻繁使用到孤紧,也會(huì)導(dǎo)致OutOfMemoryError異常。本地內(nèi)存不受java堆堆限制拒秘,但是會(huì)受到本機(jī)總內(nèi)存大小及處理器尋址空間限制
? ? ? ? ?在JDK1.4中加入的NIO(New Input/OutPut)類坛芽,引入了一種基于渠道(channel)與緩沖區(qū)(Buffer)的I/O方式,它可以直接使用Native函數(shù)庫直接分配堆外內(nèi)存翼抠,然后通過一個(gè)存儲(chǔ)在JAVA堆中的DirectByteBuffer對(duì)象作為這塊的內(nèi)存引用操作咙轩,這樣避免了在Java堆與Native堆來回的復(fù)制數(shù)據(jù),從而提高了性能