也能夠稱爲java內存區域,這是一種規範,具體實現和使用哪一種虛擬機有關。運行時數據區和java內存模型不是一回事,不要弄混。html
官方文檔地址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.htmljava
線程共享,類裝載過程當中產生的java.lang.Class對象保存在方法區,而不是堆,請參考《深刻理解java虛擬機》P215。數組
jdk1.8以前HotSpot經過永久帶實現方法區,爲了對方法區的GC能夠像堆同樣管理內存,可以複用代碼,其餘虛擬機沒有永久帶的概念,永久代的設計實現方法去並非一個好的選擇,由於更容易出現內存溢出。數據結構
方法區主要存放類信息、常量、靜態變量、即時編譯後的代碼等。oracle
垃圾回收主要是針對常量池回收和類型的卸載,這塊區域的回收很難,尤爲是類型卸載,能夠選擇不進行垃圾回收,可是回收頗有必要的。jvm
PS:jdk1.8及之後,方法區被移除,經過Metaspace實現,而Metaspace使用的是直接內存,可使用參數:-XX:MetaspaceSize
來指定元工具
數據區的大小。佈局
Tomcat中配置打印GC相關信息:能夠證實MetaSpace的存在性能
JAVA_OPTS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log"
而後直接打開,或者經過GC分析工具打開,就能夠發現存在Metaspace內存,Server模式下默認使用Parallel垃圾收集器學習
一、MetaSpace使用的是本地內存,PermGen使用的是jvm內存
二、java.lang.OutOfMemoryError: PermGen Space這個異常不存在了
三、字符串常量池存放在PermGen,容易出現性能問題和內存溢出,因此jdk1.7移動到了堆中
四、類、方法等信息大小比較難肯定,因此很難直接設置PermGen的大小
五、永久代會爲 GC 帶來沒必要要的複雜度,而且回收效率偏低
六、還有一個很重要的點,要合併HotSpot和JRockit的代碼,而JRockit沒有MetaSpace,事實也證實了經過PermGen實現是一個錯誤的選擇
線程私有,生命週期和線程相同,每執行一個方法都會建立一個棧幀,從執行到結束,對應着棧幀在虛擬機棧的入棧到出棧過程,能夠類比數據結構中的棧,java方法兩種返回方式:
一、return語句
二、拋出異常
這兩種方式都會致使棧幀被彈出。
棧幀:
保存着局部變量表、操做數棧、方法出入口等。
局部變量表:
用來保存方法參數和返回值,也就是基本數據類型、對象的引用、returnAddress類型(指向一個字節碼指令的地址)。
double和long佔用兩個局部變量空間(variable slot),其他佔用1個,slot空間大小在編譯期間就肯定,方法運行期間沒法改變,也就是當程序發生異常,打印的堆棧信息,就是虛擬機棧。
舉個栗子:解釋局部變量表和操做數棧
public static Integer f1() { int a = 1; int b = 2; return a + b; }
咱們經過javap進行反編譯查看字節碼知道,1和2這種int類型保存在局部變量表,而a+b的操做是從局部變量表中load數據到操做數棧,而後完成加法的操做。
PS:可能出現Stack OverflowError、OutOfMemoryError錯誤。
線程私有,和虛擬機棧類似,一個爲Java方法服務,一個爲了本地方法服務,本地方法棧中方法實現的語言、方式等沒有規定,由具體的虛擬機肯定,在HotSpot中只有棧,沒有虛擬機棧和本地方法棧的區別,其餘的虛擬機如J九、JRocket等可能實現就不一樣,咱們默認使用都是HotSpot。
本地方法被執行的時候,在本地方法棧也會建立一個棧幀,用於存放該本地方法的局部變量表、操做數棧、動態連接、出口信息。
PS:可能出現Stack OverflowError、OutOfMemoryError錯誤
線程共享,這是虛擬機內存最大的一塊區域,也是GC的主要區域,幾乎全部的對象和數組都保存在這裏。
內存回收的角度分爲:
新生代:Eden Space、From Survivor、To Survivor
老年代:
一、主要用來保存大對象(能夠經過-XX:PretenureSizeThreshold 設置大對象的閥值)。
二、或者重新生代通過15次 minor GC存活下來的對象(-XX:MaxTenuringThreshold)。
三、第二條不是絕對的,VM動態判斷,若是Survivor空間中相同年齡全部對象大小的綜合大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold要求的年齡。
默認Eden Space:From Survivor:To Survivor=8:1:1,能夠經過-XX:SurvivorRatio調節,不一樣垃圾收集器的調優策略不一樣的,因此不要百度到須要調節這個參數,就認爲必定有效。
-XX:NewRatio:爲Young區和Old區的比例
PS:進一步劃分的目的是更好地回收內存,或者更快地分配內存。
線程私有,是一塊很小的內存區域,記錄着當前虛擬機字節碼指令的地址(對於JNI,值爲undefined),字節碼解釋器經過改變計數器的值來選擇下一條執行的字節碼,分支、循環、跳轉、異常處理、線程恢復等功能都要依靠計數器。
線程上下文切換的時候,爲了可以恢復到正確的執行位置,須要每一個線程都擁有一個獨立的線程計數器,互不影響。
PS:惟一一個沒有規定OOM的區域
不屬於Java內存區域,有可能出現OOM,jdk1.4出現了NIO,它能夠經過Native Libraries分配堆外內存,經過Java堆中的DirectByteBuffer對象做爲引用進行操做,在某些場景明顯提升性能,覺得避免了Java堆和Native堆來回複製數據。
直接內存的分配不受Java堆大小限制,而是收到本機總內存的限制。
上面說了jvm運行時數據區是一種規範,而對於HotSpot來講,堆區和非堆區(就是jdk1.8以前的方法區)的內存結構以下:
堆區的結構在上面有介紹過,在jdk1.8中,方法區由Metaspace實現,包含CCS(壓縮類空間),只有啓用短指針纔會存在這部份內存。
Metaspace:
存放的就是方法區的的數據。包含Class、Package、Method、Field、字節碼、常量池、符號引用等
CCS:
堆中的對象都有一個指向class對象的指針,64位系統中每一個指針都是64位,爲了性能考慮,使用短指針32位的,若是使用短指針就會啓用壓縮類空間,將這些class對象保存在CCS當中。也就是保存着32位指針的Class。
CodeCache:
JIT即便編譯的Native代碼、JNI使用的C代碼
咱們能夠驗證一下CCS的啓用與關閉:默認開啓
經過jps -l查找Java進程,而後經過
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jstat -gc 23631 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 20480.0 19968.0 0.0 0.0 283136.0 144816.6 52736.0 13931.5 35416.0 34236.6 4480.0 4208.6 10 0.215 2 0.220 0.435
而後在Catalina.sh中JAVA_OPTS加入-XX:-UseCompressedClassPointers進行禁用,而後重啓
[root@iZuf6fkfhthmdm1nwdg5isZ bin]# jstat -gc 24008 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 20992.0 20480.0 0.0 0.0 282112.0 160448.0 51712.0 14654.7 35416.0 34133.3 0.0 0.0 10 0.192 2 0.153 0.345
驗證CodeCache的存在:
由於CodeCache保存的是即時編譯的代碼的代碼,咱們經過-Xint解釋執行的方式啓動,固然啓動確定會變慢的,由於默認以Mix方式啓動而後重啓Tomcat
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 19456.0 20480.0 14800.5 0.0 282624.0 260677.0 52224.0 22069.4 32000.0 30489.7 0.0 0.0 10 0.140 1 0.065 0.205
咱們發現MC(Metaspace Capacity)變小了,也證實了CodeCache的存在
由於你們通常都是經過周志明老師的書裏面學習jvm的內容,對於常量池的部分的講解很容易讓人搞懵逼,最開始說Class文件中包含常量池,指的是Class常量池,又說運行時常量池能夠經過intern()動態添加字符串,看到這裏我都蒙了,intern()是操做字符串常量池的,難道字符串常量池是運行時常量池的一部分嗎?
而實現方法區的內存溢出是就是經過intern()實現,說的也是運行時常量池致使的內存溢出,因此我只能認爲字符串常量池是運行時常量池的一部分,並且jdk1.7以後,運行時常量池和字符串常量池都從方法區移到堆中,因此,我不得不相信
不知道理解的對不對,有不一樣意見的能夠評論提出來。。。。
在HotSpot中經過StringTable類實現功能,StringTable是一個hash表,默認長度大小1009,被全部類共享。字符串常量由字符組成,保存在StringTable上面。在jdk1.6當中,StringTable的長度是固定1009,若是存放在StringTable中的字符串不少,形成hash衝突的概率很大,鏈表過長,當經過String.intern()查找String Pool時,性能就會下降。
在jdk1.7當中,StringTable的長度能夠經過-XX:StringTableSize設置
存放的內容:
String.intern()主要是爲了複用字符串常量,節省內存空間
在jdk1.6及之前的版本,存放的都是字符串常量,使用""聲明的字符串都存儲在這,例如:String str = "abc";
在jdk1.7以後,String.intern()發生變化,若是字符串常量池不存在這個String對象,若是堆區存在這個對象,直接複製到字符串常量池,不然仍是要建立字符串,而後返回字符串對象的引用。所以除了字符串常量,也能夠存放堆中字符串常量的引用
PS:在jdk1.7以後,字符串常量池從方法區轉移到堆中
首先java代碼經過javac編譯成class文件,class文件中保存着類的相關信息(版本,類、字段、方法、接口等信息),除此以外,還有Class常量池,用來存放編譯器產生的各類字面量(Literal)和符號引用(Symbolic References),每一個class文件都有一個class常量池。
字面量:1.String 2.基本數據類型 3.聲明final的常量
符號引用:1.類和方法的全限定名(相似於com.cfets.**.**這種) 2.字段的名稱和描述符 3.方法的名稱和描述符
就是class常量池被加載到方法區以後的版本,區別就是:字面量能夠經過String.intern()動態添加,符號引用解析爲直接引用(類加載的解析階段)
當類加載到內存之中,jvm會把class常量池的內存存放到運行時常量池,因此,運行時常量池也是每一個class都有的。
符號引用:上述有說明。以一組符號來描述所引用的目標,只要能定位到目標,不管是任何形式的字面量,和jvm實現的內存佈局無關。
直接引用:直接指向目標的指針、相對偏移量或者間接定位到目標的句柄,句柄和指針對應對象的訪問定位方式,和jvm實現的內存佈局有關。
PS:JDK1.7及以後版本的 JVM 已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池.因此jdk1.7以後,運行時常量池和字符串常量池都從方法區移到堆中
解釋執行:
逐條翻譯字節碼爲可運行的機器碼,優點在於不用等待。
即時編譯:
以方法爲單位將字節碼翻譯成機器碼,實際運行當中效率更高。
在HotSpot中默認採用混合模式,其先解釋執行字節碼,而後將其中的熱點代碼(屢次執行,循環等)直接編譯成機器碼,下次就不用再編譯了,讓其更快速地運行。使用混合模式的緣由有二八定律和jvm優化的考慮在裏面
經過java -version命令能夠查看:
# java -version java version "1.8.0_102" Java(TM) SE Runtime Environment (build 1.8.0_102-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
經過-Xint(解釋執行), -Xcomp(即時編譯), 和-Xmixed設置編譯方式,不過通常狀況下不須要修改。
原文出處:https://www.cnblogs.com/huigelaile/p/diamondshine.html