一、程序計(jì)數(shù)器
程序計(jì)數(shù)器內(nèi)存很小,可以看作是當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)指示器。
有了它,程序就能被正確的執(zhí)行。
因?yàn)橛?strong>線程切換的存在,則每個(gè)線程必須有各自獨(dú)立的程序計(jì)數(shù)器,即線程私有的內(nèi)存。
這里再解釋一下什么是線程切換,線程切換指的是:
單處理器在執(zhí)行多線程時(shí)所進(jìn)行的線程切換,多線程的交替運(yùn)行會(huì)產(chǎn)生同時(shí)運(yùn)行的錯(cuò)覺。
程序計(jì)數(shù)器不會(huì)發(fā)生OOM原因:
占用內(nèi)存非常小,當(dāng)線程結(jié)束時(shí)程序計(jì)數(shù)器也會(huì)隨之回收。
二、本地方法棧與虛擬機(jī)棧
棧是stack的翻譯,那stack又是什么?
在英文語境中,stack指的是一摞盤子堆疊起來、一摞書堆疊起來的這種狀態(tài),也就是 a stack of books. 借這種現(xiàn)實(shí)物理情境來描述計(jì)算機(jī)中的數(shù)據(jù)結(jié)構(gòu)。
這種結(jié)構(gòu)的特征就是LIFO, Last In First Out, 即后進(jìn)先出。
也就是,一摞盤子,你只能一個(gè)一個(gè)往上堆,也只能一個(gè)一個(gè)從頂上往外取,對(duì)應(yīng)**入棧和出棧(彈棧)**的操作。
以上是對(duì)棧這種結(jié)構(gòu)的解釋。
接下來說這兩種棧結(jié)構(gòu):Native Method Stack 和 JVM stack.
棧是線程私有的,它的生命周期和線程是相同的。
棧里面保存棧幀。
什么是棧幀?
每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀。棧幀存儲(chǔ)了局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法出口等信息。每個(gè)方法從調(diào)用到運(yùn)行結(jié)束的過程,就對(duì)應(yīng)著一個(gè)棧幀在棧中入棧到出棧的過程。
棧有可能出現(xiàn)什么異常?
StackOverflowError和OutOfMemoryError。
前者主要是深度遞歸和復(fù)雜嵌套方法調(diào)用造成。
后者的話發(fā)生在棧在進(jìn)行動(dòng)態(tài)擴(kuò)展的時(shí)候,也就是說 jvm 實(shí)現(xiàn)中棧的大小此時(shí)是不固定的,因?yàn)?strong>線程操作需要更多的??臻g而在申請(qǐng)內(nèi)存的時(shí)候失敗就會(huì)拋出OutOfMemoryError錯(cuò)誤。
JVM 中的棧包括 Java 虛擬機(jī)棧和本地方法棧。
兩者的區(qū)別就是:
Java 虛擬機(jī)棧為 JVM 執(zhí)行 Java 方法服務(wù),本地方法棧則為 JVM 使用到的 Native 方法(比如C或C++)服務(wù)。
四、堆
Heap有什么特征?
-
??JVM管理的最大內(nèi)存區(qū)域
-
??線程共享
-
??存放對(duì)象實(shí)例
-
??內(nèi)存空間可以物理上不連續(xù)
-
??垃圾回收的主要區(qū)域
關(guān)于垃圾回收的內(nèi)容這里不展開講了。
五、方法區(qū)
JVM規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但有一個(gè)別名叫Non-Heap,即Heap中的Non-heap, 也是說明它和堆其實(shí)還是不一樣的。
方法區(qū)有什么特征?
??線程共享
??存放已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
??垃圾回收較少出現(xiàn),甚至可選擇不進(jìn)行垃圾回收
方法區(qū)的垃圾回收主要針對(duì)常量池的回收和對(duì)類的卸載。
這里主要說一下運(yùn)行時(shí)常量池:
Class文件中除了有類的版本、字段、方法、接口等描述信息外還有一項(xiàng)常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
這里出現(xiàn)了兩個(gè)常量池,它們是不一樣的,一個(gè)叫常量池,一個(gè)叫運(yùn)行時(shí)常量池(Runtime Constant Pool), 運(yùn)行時(shí)常量池具備動(dòng)態(tài)性,那怎么理解動(dòng)態(tài)性呢?
就是說除了編譯期產(chǎn)生的常量進(jìn)入了常量池在類加載后又緊接著進(jìn)入了運(yùn)行時(shí)常量池以外,運(yùn)行期間新的常量也會(huì)進(jìn)入運(yùn)行時(shí)常量池。
關(guān)于類加載的介紹我們?cè)賳为?dú)寫一篇。
《學(xué)一點(diǎn)關(guān)于JVM類加載的知識(shí)》
下面舉一些例子把這里具體搞搞清楚。
常量池有什么用 ?
**優(yōu)點(diǎn):**常量池避免了頻繁的創(chuàng)建和銷毀對(duì)象而影響系統(tǒng)性能,其實(shí)現(xiàn)了對(duì)象的共享。
下面具體講一下字符串常量池(String常量池):
String 是由 final 修飾的類,是不可以被繼承的。通常有兩種方式來創(chuàng)建對(duì)象。
//1、這種存在Heap中,每次new都會(huì)創(chuàng)建一個(gè)全新對(duì)象
String?str?=?new?String("abcd");
?
//2、這種是在棧上創(chuàng)建對(duì)象引用變量str,指向字符串常量池中的“abcd”(沒有的話新建一個(gè))
String?str?=?"abcd";
關(guān)于字符串 + 號(hào)連接問題:
對(duì)于字符串常量的 + 號(hào)連接,在程序編譯期,JVM就會(huì)將其優(yōu)化為 + 號(hào)連接后的值。所以在編譯期其字符串常量的值就確定了。
String?a?=?"a1";???
String?b?=?"a"?+?1;???
System.out.println((a?==?b));?//result?=?true??
?
String?a?=?"atrue";???
String?b?=?"a"?+?"true";???
System.out.println((a?==?b));?//result?=?true?
?
String?a?=?"a3.4";???
String?b?=?"a"?+?3.4;???
System.out.println((a?==?b));?//result?=?true?
關(guān)于字符串引用 + 號(hào)連接問題:
對(duì)于字符串引用的 + 號(hào)連接問題,由于字符串引用在編譯期是無法確定下來的,在程序的運(yùn)行期動(dòng)態(tài)分配并創(chuàng)建新的地址存儲(chǔ)對(duì)象。
public?static?void?main(String[]?args){
???????String?str1?=?"a";
???????String?str2?=?"ab";
???????String?str3?=?str1?+?"b";
???????System.out.print(str2?==?str3);//false
????}
通過 jad 反編譯工具,分析上述代碼到底做了什么。
public?class?TestDemo{
????public?TestDemo(){
????}
????public?static?void?main(String?args[]){
????????String?s?=?"a";
????????String?s1?=?"ab";
????????String?s2?=?(new?StringBuilder()).append(s).append("b").toString();
????????System.out.print(s1?=?s2);
????}
}
發(fā)現(xiàn)?new 了一個(gè) StringBuilder 對(duì)象,然后使用 append 方法優(yōu)化了 + 操作符。new 在堆上創(chuàng)建對(duì)象,而 String s1=“ab”則是在常量池中創(chuàng)建對(duì)象,兩個(gè)應(yīng)用所指向的內(nèi)存地址是不同的,所以 s1 == s2 結(jié)果為 false。
這里引出一個(gè)實(shí)際開發(fā)中關(guān)于字符串拼接的問題。就是盡量不要在 for 循環(huán)中使用 + 號(hào)來操作字符串。
因?yàn)槿绻谩?”號(hào)的話,每次循環(huán)都會(huì)創(chuàng)建和銷毀一個(gè)StringBuilder對(duì)象,這樣還不如在循環(huán)外創(chuàng)建一個(gè)StringBuilder對(duì)象,然后使用append方法。
public?static?void?main(String[]?args){
????????StringBuilder?s?=?new?StringBuilder();
????????for(int?i?=?0;?i?<?100;?i++){
????????????s.append("a");
????????}
????}
使用final修飾的字符串
public?static?void?main(String[]?args){
????????final?String?str1?=?"a";
????????String?str2?=?"ab";
????????String?str3?=?str1?+?"b";
????????System.out.print(str2?==?str3);//true
????}
final 修飾的變量是一個(gè)常量,編譯期就能確定其值。所以 str1 + "b"就等同于 "a" + "b",所以結(jié)果是 true。
String對(duì)象的intern方法。
public?static?void?main(String[]?args){
????????String?s?=?"ab";
????????String?s1?=?"a";
????????String?s2?=?"b";
????????String?s3?=?s1?+?s2;
????????System.out.println(s3?==?s);//false
????????System.out.println(s3.intern()?==?s);//true
????}
通過前面學(xué)習(xí)我們知道,s1+s2 實(shí)際上在堆上 new 了一個(gè) StringBuilder 對(duì)象,而 s 在常量池中創(chuàng)建對(duì)象 “ab”,所以 s3 == s 為 false。
但是 s3 調(diào)用 intern 方法,返回的是s3的內(nèi)容(ab)在常量池中的地址值。所以 s3.intern() == s 結(jié)果為 true。
往期推薦:
●?師爺,翻譯翻譯什么叫AOP
● 終于搞懂動(dòng)態(tài)代理了!
●?學(xué)會(huì)@ConfigurationProperties月薪過三千
●?0.o?讓我看看怎么個(gè)事兒之SpringBoot自動(dòng)配置
●?不是銀趴~是@Import!文章來源:http://www.zghlxwxcb.cn/news/detail-829300.html
●?Java反射,看完就會(huì)用文章來源地址http://www.zghlxwxcb.cn/news/detail-829300.html
到了這里,關(guān)于復(fù)習(xí)一下JVM內(nèi)存結(jié)構(gòu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!