jvm 運(yùn)行時(shí)內(nèi)存區(qū)域工扎?
線程私有的:
- 程序計(jì)數(shù)器
- 虛擬機(jī) 棧
- 本地方法棧
線程共享的:
- 堆
- 方法區(qū)
- 直接內(nèi)存(非運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分)
JDK8 將方法區(qū)異常了由元空間取代。
程序計(jì)數(shù)器
程序計(jì)數(shù)器簡(jiǎn)介护昧?
- 可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器莫其。字節(jié)碼解釋器通過這個(gè)計(jì)數(shù)器來選取下一條乣執(zhí)行的字節(jié)碼指令对竣。分支庇楞、循環(huán)、跳轉(zhuǎn)否纬、異常處理吕晌、線程恢復(fù)等功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
- 程序計(jì)數(shù)器是唯一不糊出現(xiàn)
OutOfMemoryError
異常的區(qū)域临燃。
程序計(jì)數(shù)器的主要作用睛驳?
- 字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制膜廊,如:順序執(zhí)行乏沸、選擇、循環(huán)爪瓜、異常處理蹬跃。
- 在多線程的情況下,記錄當(dāng)前線程執(zhí)行的位置铆铆,方便線程恢復(fù)執(zhí)行后繼續(xù)執(zhí)行蝶缀。
java 虛擬機(jī)棧
java虛擬機(jī)棧介紹?
java虛擬機(jī)棧是線程私有的薄货,它的生命周期和線程相同翁都。它描述的是java方法執(zhí)行的內(nèi)存模型,每次方法調(diào)用的數(shù)據(jù)都是通過棧傳遞的谅猾。
棧是由一個(gè)個(gè)棧幀組成柄慰,棧幀中擁有局部變量表、操作數(shù)棧税娜、動(dòng)態(tài)鏈接坐搔、方法出口信息。
局部變量表主要存放的數(shù)據(jù)類型敬矩?
- 各種基本數(shù)據(jù)類型的值薯蝎。
- 對(duì)象引用(可能是指向?qū)ο蟮闹刚蛘呤侵赶蛞粋€(gè)代表對(duì)象的句柄)。
java 虛擬機(jī)棧會(huì)拋出哪兩種異常谤绳?
StackOverFlowError: 若 Java 虛擬機(jī)棧的內(nèi)存大小不允許動(dòng)態(tài)擴(kuò)展占锯,那么當(dāng)線程請(qǐng)求棧的深度超過當(dāng)前 Java 虛擬機(jī)棧的最大深度的時(shí)候,就拋出 StackOverFlowError 錯(cuò)誤缩筛。
OutOfMemoryError:Java 虛擬機(jī)棧的內(nèi)存大小可以動(dòng)態(tài)擴(kuò)展消略, 如果虛擬機(jī)在動(dòng)態(tài)擴(kuò)展棧時(shí)無法申請(qǐng)到足夠的內(nèi)存空間,則拋出 OutOfMemoryError 異常瞎抛。
java 的方法是如何調(diào)用的艺演?
java 虛擬機(jī)棧類似數(shù)據(jù)結(jié)構(gòu)的棧,棧中主要保存的是棧幀,每一次的方法調(diào)用都是一個(gè)棧幀被壓棧的過程胎撤,每個(gè)方法調(diào)用結(jié)束晓殊,都會(huì)有一個(gè)棧幀彈出。java 的返回方式只有 return 或者拋出異常伤提,不管哪種巫俺,都會(huì)導(dǎo)致棧幀彈出。
本地方法棧
本地方法棧簡(jiǎn)介肿男?
本地方法棧和虛擬機(jī)棧非常類似介汹,只是虛擬機(jī)棧是為 java 方法調(diào)用服務(wù),兒本地方法棧是為虛擬機(jī)使用 native 方法服務(wù)的舶沛。在 Hotspot 中本地方法棧和虛擬機(jī)棧是合二為一的嘹承。
本地方法執(zhí)行時(shí),也會(huì)在本地方法棧中創(chuàng)建一個(gè)棧幀如庭,用以保存本地方法的局部變量表叹卷、操作數(shù)棧、動(dòng)態(tài)鏈接坪它、出口信息骤竹。
本地方法執(zhí)行完成后也會(huì)出棧。
本地方法調(diào)用也會(huì)拋出 StackOverFlowError哟楷、OutOfMemoryError。
堆
堆內(nèi)存簡(jiǎn)介否灾?
堆是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊卖擅,堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建墨技。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例惩阶,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存。
因?yàn)?JDK 1.7默認(rèn)開啟的逃逸分析扣汪,在方法中引用的對(duì)象如果沒有其他引用断楷,對(duì)象可以直接在棧中創(chuàng)建。
堆也是 java 垃圾收集器管理的主要區(qū)域崭别。
逃逸分析冬筒?
從 JDK 1.7 開始已經(jīng)默認(rèn)開啟逃逸分析,如果某些方法中的對(duì)象引用沒有被返回或者未被外面使用(也就是未逃逸出去)茅主,那么對(duì)象可以直接在棧上分配內(nèi)存舞痰。
堆內(nèi)存分區(qū)?
堆內(nèi)存通常被分為:新生代诀姚、老年代响牛、永久帶。JDK8之后,方法區(qū)(Hotspot的永久代)被移除呀打,由元空間取代矢赁。
永久代和方法區(qū)的關(guān)系?
方法區(qū)是 jvm 的規(guī)范贬丛,永久代是Hotspot對(duì)方法區(qū)的實(shí)現(xiàn)撩银。所以說在Hotspot中,方法區(qū)(永久代)是堆內(nèi)存中的瘫寝。
堆內(nèi)存中的新生代蜒蕾?
堆內(nèi)存中的新生代又分為三塊, Eden 區(qū)焕阿、兩個(gè) Survivor 區(qū)咪啡。兩個(gè) Survivor 區(qū)又叫 from 和 to,或者叫 s0 和s1暮屡。在 s0 和s1 中的對(duì)象撤摸,每經(jīng)歷一次新生代垃圾回收,年齡增加1褒纲,當(dāng)年齡達(dá)到15歲准夷,會(huì)晉升到老年代中。晉升老年代的年齡可以通過 -XX:MaxTenuringThreshold (最大年齡閾值)設(shè)置莺掠。
堆內(nèi)存中會(huì)拋出的異常衫嵌?
對(duì)內(nèi)存中主要拋出 OutOfMemoryError 內(nèi)存溢出錯(cuò)誤。內(nèi)存異常又分為兩種彻秆,java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
,當(dāng)gc時(shí)間長(zhǎng)楔绞,且回收內(nèi)存少時(shí),報(bào)這個(gè)錯(cuò)誤唇兑。
java.lang.OutOfMemoryError: Java heap space
酒朵,當(dāng)堆內(nèi)存不足以存放新創(chuàng)建的對(duì)象,就會(huì)報(bào)這個(gè)錯(cuò)扎附∧璧ⅲ可以通過 -Xmx 控制最大堆內(nèi)存大小,不過這個(gè)參數(shù)受制于物理內(nèi)存的大小留夜。
方法區(qū)
方法區(qū)簡(jiǎn)介匙铡?
方法區(qū)也是屬于各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息碍粥、運(yùn)行時(shí)常量池慰枕、靜態(tài)變量、以及編譯器編譯后的代碼等數(shù)據(jù)即纲。java 虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分具帮,不過方法區(qū)卻有一個(gè)非堆的別名。
JDK8 之前永久代沒有被移除,可以用下面兩個(gè)參數(shù)指定方法區(qū)的初始大小和最大大小蜂厅。
-XX:PermSize=N //方法區(qū) (永久代) 初始大小
-XX:MaxPermSize=N //方法區(qū) (永久代) 最大大小
JDK8 之后可以用下面兩個(gè)參數(shù)設(shè)置元空間的初始值和最大值匪凡。
-XX:MetaspaceSize=N //設(shè)置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //設(shè)置 Metaspace 的最大大小
為什么要用元空間替換永久代呢掘猿?
- 永久代會(huì)使用 JVM 的固定內(nèi)存空間病游,容易發(fā)生溢出。而元空間直接使用直接內(nèi)存稠通,不受 JVM 固定內(nèi)存空間控制衬衬。元空間溢出拋出
java.lang.OutOfMemoryError: MetaSpace
. - 在 JDK8中,合并 Hotspot 和 JRockit 時(shí)改橘,JRockit 從來都沒有叫永久代的地方滋尉,所以也就沒有必要設(shè)置永久代了。
方法區(qū)的運(yùn)行時(shí)常量池飞主?
運(yùn)行時(shí)常量池是方法區(qū)的一部分狮惜。Class 文件中除了有類的版本、字段碌识、方法碾篡、接口等描述信息外,還有常量池表(用于存放編譯期生成的各種字面量和符號(hào)引用)筏餐。
運(yùn)行時(shí)常量池也會(huì)拋出 OutOfMemoryError 錯(cuò)誤开泽。
JDK7之前,運(yùn)行時(shí)常量池包含字符串常量池魁瞪,存放在方法區(qū)穆律,此時(shí) hotspot 虛擬機(jī)對(duì)方法區(qū)的實(shí)現(xiàn)為永久代。
JDK7 字符串常量池放到了堆中佩番,運(yùn)行時(shí)常量池還在方法區(qū)中众旗。
JDK8 移除了方法區(qū)罢杉,則運(yùn)行時(shí)常量池隨著類文件元信息移到了元空間趟畏。
直接內(nèi)存?
直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分滩租,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域赋秀,但是這部分內(nèi)存也被頻繁地使用吗冤。而且也可能導(dǎo)致 OutOfMemoryError 錯(cuò)誤出現(xiàn)硕淑。
JDK1.4 中新加入的 NIO速客,使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存看靠,然后通過 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作趟据,避免在 Java 堆和 Native 堆之間來回復(fù)制數(shù)據(jù)泵肄,從而提高性能驮吱。
本機(jī)直接內(nèi)存的分配不會(huì)受到 Java 堆的限制雕憔,但是會(huì)受到物理機(jī)總內(nèi)存大小以及處理器尋址空間的限制身笤。
java 對(duì)象創(chuàng)建
對(duì)象創(chuàng)建過程豹悬?
- 類加載檢查
- 分配內(nèi)存
- 初始化零值
- 設(shè)置對(duì)象頭
- 執(zhí)行init方法
類加載檢查過程娇钱?
虛擬機(jī)遇到 new 指令時(shí)伤柄,首先檢查常量池中是否有這類的符號(hào)引用适刀,并檢查這個(gè)符號(hào)引用代表的類是否已經(jīng)被加載、解析细疚、初始化過蔗彤,如果沒有,先執(zhí)行內(nèi)加載過程疯兼。
分配內(nèi)存然遏?
在類加載檢查通過后,便可以確定對(duì)象的所需要內(nèi)存大小吧彪,接下來虛擬機(jī)將在堆內(nèi)存中待侵,為對(duì)象分配一塊確定大小的內(nèi)存空間。
內(nèi)存分配的方式姨裸?
內(nèi)存分配方式有指針碰撞和空閑列表兩種秧倾,選擇哪種分配方式由堆內(nèi)存是否規(guī)整決定,堆內(nèi)存是否規(guī)整又由于所采用的垃圾收集器是否帶有壓縮功能決定傀缩。收集器(CMS)那先。
當(dāng)使用標(biāo)記-清楚算法時(shí),內(nèi)存不規(guī)整赡艰,會(huì)使用空閑列表法售淡,空閑列表記錄可以使用的內(nèi)存空間,進(jìn)行分配慷垮。當(dāng)使用標(biāo)記-整理算法時(shí)揖闸,內(nèi)存規(guī)整,會(huì)使用指針碰撞法料身,指針只需要沿著沒有用過的內(nèi)存區(qū)域移動(dòng)對(duì)象大小的位置即可汤纸。收集器(Serial、ParNew)芹血。
初始化零值贮泞?
內(nèi)存分配完成后楞慈,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用啃擦,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值抖部。
設(shè)置對(duì)象頭?
初始化零值后议惰,虛擬機(jī)會(huì)對(duì)對(duì)象頭進(jìn)行必要的設(shè)置慎颗,比如對(duì)象的類信息,哈希碼言询,對(duì)象的分代年齡俯萎,以及是否啟用偏向鎖等。
執(zhí)行init方法运杭?
在完成設(shè)置對(duì)象頭后夫啊,從虛擬機(jī)的視角看,對(duì)象已經(jīng)創(chuàng)建完成辆憔。但是從java程序視角看撇眯,對(duì)象創(chuàng)建才開始,執(zhí)行 init 方法虱咧,將所有的零值設(shè)置為初始值熊榛。對(duì)象才算初始化完成。
對(duì)象的訪問定位腕巡?
java 程序通過棧上的引用來操作堆上的對(duì)象玄坦。對(duì)對(duì)象的訪問方式由虛擬機(jī)實(shí)現(xiàn)而定,一般有使用句柄和直接指針兩種:
使用句柄:需要在堆中開辟一塊空間存儲(chǔ)句柄绘沉,棧中的引用指向句柄的地址煎楣,句柄指向?qū)ο蠛蛯?duì)象在方法區(qū)中的類信息。
使用指針:棧中的引用直接指向堆中的對(duì)象车伞,由堆類考慮如何分配內(nèi)存地址择懂,方便通過對(duì)象定位到對(duì)象在方法區(qū)中的類信息。
字符串常量池相關(guān)
- 直接拼接和引用拼接
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; //常量池中的對(duì)象(編譯器優(yōu)化)
String str4 = str1 + str2; //在堆上創(chuàng)建的新的對(duì)象 (實(shí)際上是StringBuilder調(diào)用append方法和toString方法實(shí)現(xiàn)的)
String str5 = "string"; //常量池中的對(duì)象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
- 在編譯器有確切的值另玖,可以被優(yōu)化
final String str1 = "str";
final String str2 = "ing";
// 下面兩個(gè)表達(dá)式其實(shí)是等價(jià)的
String c = "str" + "ing";// 常量池中的對(duì)象
String d = str1 + str2; // 常量池中的對(duì)象
System.out.println(c == d);// true
final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing";// 常量池中的對(duì)象
String d = str1 + str2; // 在堆上創(chuàng)建的新的對(duì)象
System.out.println(c == d);// false
public static String getStr() {
return "ing";
}
String str2 = new String("abcd"); 創(chuàng)建步驟困曙?會(huì)創(chuàng)建幾個(gè)對(duì)象?
- 在堆中創(chuàng)建一個(gè)字符串對(duì)象日矫。
- 檢查字符串常量池中是否有和 new 的字符串值相等的字符串常量
- 如果沒有的話赂弓,需要在字符串常量池中也創(chuàng)建一個(gè)值相等的字符串常量绑榴。如果有的話哪轿,就直接返回堆中的字符串實(shí)例對(duì)象地址。
String 的 intern 方法翔怎?
直接使用雙引號(hào)聲明出來的
String
對(duì)象會(huì)直接存儲(chǔ)在常量池中窃诉。-
使用
String
提供的intern()
方法也有同樣的效果杨耙,如果常量池中存這個(gè)對(duì)象,直接返回常量池中該對(duì)象的引用飘痛。如果不存在:- JDK7 之前珊膜,會(huì)在常量池創(chuàng)建相同的對(duì)象,并返回常量池的字符串的引用宣脉。
- JDK7 之后车柠,是直接將堆中對(duì)象的引用地址放到常量池中,減少不必要的內(nèi)存開銷塑猖。
String s1 = "Javatpoint"; String s2 = s1.intern(); // 在常量池中存在竹祷,直接返回常量池的引用 String s3 = new String("Javatpoint"); // 新建堆上對(duì)象,返回堆上對(duì)象的引用 String s4 = s3.intern(); // 在常量池中存在羊苟,直接返回常量池的引用 System.out.println(s1==s2); // True System.out.println(s1==s3); // False System.out.println(s1==s4); // True System.out.println(s2==s3); // False System.out.println(s2==s4); // True System.out.println(s3==s4); // False
8中基本類型的包裝類和常量池技術(shù)塑陵?
Byte, Short, Integer, Long 這 4 種包裝類默認(rèn)創(chuàng)建了數(shù)值 [-128,127] 的相應(yīng)類型的緩存數(shù)據(jù)蜡励,Character 創(chuàng)建了數(shù)值在 [0,127] 范圍的緩存數(shù)據(jù)令花,Boolean 直接返回 True Or False。
兩種浮點(diǎn)數(shù)類型的包裝類 Float, Double 并沒有實(shí)現(xiàn)常量池技術(shù)凉倚。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 輸出 true
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// 輸出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 輸出 false
自動(dòng)裝箱會(huì)創(chuàng)建新對(duì)象兼都,導(dǎo)致 == 比較返回 false。
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
包裝類型進(jìn)行計(jì)算會(huì)自動(dòng)拆箱稽寒,使用 == 比較返回 true俯抖。
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println(i4 == i5);// false
System.out.println(i4 == i5 + i6);// true
System.out.println(40 == i5 + i6);// true