Java記憶體結(jié)構(gòu)
- 1.JVM概述
- 2.程式計數(shù)器
-
- 2.1.定義
- 2.2.作用及特點(diǎn)解釋
- #3.虛擬機(jī)器堆疊
-
-
- ## 3.1.堆疊的特性
- 3.2.堆疊的示範(fàn)
- #3.5.棧記憶體溢位(StackOverflowError)
- 3.6.執(zhí)行緒運(yùn)行診斷
- 3.6.1.案例1:cpu佔(zhàn)用過多(linux系統(tǒng)為例)
- 3.6.2.案例2:執(zhí)行緒診斷_遲遲無法得到結(jié)果
- #4.本機(jī)方法堆疊
- 5.堆疊
- 5.1.定義
- 5.2.堆記憶體溢出(OutOfMemoryError:Java heap space)
- 5.3.堆記憶體診斷
6.1.定義
6.2.定義6.3.方法區(qū)記憶體溢位(OutOfMemoryError: Metaspace)
6.4.常數(shù)池
- #1.JVM概述
- 定義: ##JVM全名是Java Virtual Machine-java程式的運(yùn)行環(huán)境(java二進(jìn)位字節(jié)碼的運(yùn)行環(huán)境)
#好處:
##一次編寫,到處執(zhí)行(跨平臺)
自動記憶體管理,垃圾回收功能陣列下標(biāo)越界檢查多態(tài)
比較JVM,JRE,JDK之間的聯(lián)繫與差異
JVM體系結(jié)構(gòu)
如圖所示
## 一個類別從Java原始碼(.java檔)編譯成了Java二進(jìn)位字節(jié)碼以後,必須經(jīng)過類別載入器才能載入到JVM裡面才能運(yùn)作。 我們一般把類別放在方法區(qū)裡。類別將來創(chuàng)建的物件放在堆的部分,而堆裡面的物件在呼叫方法時會用到虛擬機(jī)器棧和程式計數(shù)器以及本地方發(fā)展。
我們可以透過本機(jī)方法介面來呼叫作業(yè)系統(tǒng)提供的功能。
JVM的記憶體結(jié)構(gòu)包括:1.方法區(qū) 2.程式計數(shù)器 3.虛擬機(jī)器堆疊
4.本地方法堆疊 5.堆
2.程式計數(shù)器
Program Counter Register程式計數(shù)器(暫存器) 作用:
??是記住下一條jvm指令的執(zhí)行位址 特點(diǎn)
??是線程私有的
??不會存在記憶體溢出(記憶體結(jié)構(gòu)中唯一一個不會不會記憶體溢出的結(jié)構(gòu))
在2.2中我們將會解釋程式計數(shù)器的作用及特性。
2.2.作用及特點(diǎn)解釋
?二進(jìn)制字節(jié)碼 JVM指令 Java源代碼?0:?getstatic?????#20?????????????????//?PrintStream?out?=?System.out;? ?3:?astore_1??????????????????????????//?-? ?4:?aload_1???????????????????????????//?out.println(1);? ?5:?iconst_1??????????????????????????//?-? ?6:?invokevirtual?#26?????????????????//?-? ?9:?aload_1???????????????????????????//?out.println(2);? ?10:?iconst_2??????????????????????????//?- ?11:?invokevirtual?#26?????????????????//?- ?14:?aload_1???????????????????????????//?out.println(3);? ?15:?iconst_3??????????????????????????//?- ?16:?invokevirtual?#26?????????????????//?- ?19:?aload_1???????????????????????????//?out.println(4);? ?20:?iconst_4??????????????????????????//?- ?21:?invokevirtual?#26?????????????????//?- ?24:?aload_1???????????????????????????//?out.println(5);? ?25:?iconst_5??????????????????????????//?- ?26:?invokevirtual?#26?????????????????//?- ?29:?return我們可以看到這些程式碼,第一行System.out賦值給了一個變量,在4:中去調(diào)用println()方法。然後依序列印1,2,3,4,5。這些指令不能直接交給CPU來執(zhí)行,必須經(jīng)過解譯器的作用。它負(fù)責(zé)把一條一條的字節(jié)碼指令解釋成機(jī)器碼,然後機(jī)器碼就可以交給CPU來執(zhí)行。 也就是###二進(jìn)位字節(jié)碼->解釋器->機(jī)器碼->CPU###### 實(shí)際程式計數(shù)器的作用就是在指令的執(zhí)行過程中,記住下一JVM指令的執(zhí)行位址。 ### 上面我們二進(jìn)位字節(jié)碼前面的數(shù)字0,3,4…我們可以把其理解為位址。根據(jù)這些地址信息,我們就可以找到命令來執(zhí)行。 ######每次拿到指令交給CPU執(zhí)行之後,程式計數(shù)器就會把下一指令的位址放入程式計數(shù)器中,等一條指令執(zhí)行完成之後,解釋器就會到程式計數(shù)器中取到下一指令的位址。再把其經(jīng)過解釋器解釋成機(jī)器碼然後交給CPU執(zhí)行。然後一直重複這樣的過程###。 ######在物理上,實(shí)作程式計數(shù)器是透過暫存器來實(shí)現(xiàn)的。寄存器是CPU元件裡讀取最快的儲存單元###。 ###
程序計數(shù)器是線程私有的
假如說上述代碼都在線程1中運(yùn)行,同時運(yùn)行的還有線程2和線程3,多個線程運(yùn)行的時候,CPU會給每個線程分配時間片,給線程1分配時間片,如果線程1在指定的時間沒有運(yùn)行完,它就會把狀態(tài)暫存,切換到線程2,線程2執(zhí)行自己的代碼。線程2執(zhí)行完了,再繼續(xù)執(zhí)行線程1的代碼,在線程切換的過程中,我們要記住下一條指令的執(zhí)行地址。就需要用到程序計數(shù)器。假如說線程1剛開始執(zhí)行到第9行代碼,恰好這個時候時間片用完,CPU切換到線程2去執(zhí)行,這時它就會把下一條指令的地址10記錄到程序計數(shù)器里面,而且程序計數(shù)器是線程私有的,它是屬于線程1的,等線程2代碼執(zhí)行完了,線程1搶到了時間片,它就會從自己的程序計數(shù)器里面取出下一行代碼。每個線程都有自己的程序計數(shù)器
3.虛擬機(jī)棧
3.1.棧的特點(diǎn)
棧類似現(xiàn)實(shí)生活中的子彈夾。棧最重要的特點(diǎn)是后進(jìn)先出。
如圖,1是最先進(jìn)入棧中的,3是最后進(jìn)入棧中的,但是在出棧的時候,3最先出棧,1最后出棧。即他們按照1,2,3的順序入棧,按照3,2,1的順序出棧
虛擬機(jī)棧就是我們線程運(yùn)行時需要的內(nèi)存空間,一個線程運(yùn)行時需要一個棧。如果將來有多個線程的話,它就會有多個虛擬機(jī)棧。
每個??梢钥闯墒怯啥鄠€棧幀組成,例如上圖中每個元素1,2,3都可以看成是棧幀。
一個棧幀就對應(yīng)著Java中一個方法的調(diào)用,即棧幀就是每個方法運(yùn)行時需要的內(nèi)存。每個方法運(yùn)行時需要的內(nèi)存一般有參數(shù),局部變量,返回地址,這些都需要占用內(nèi)存,所以每個方法執(zhí)行時,都要預(yù)先把這些內(nèi)存分配好。
當(dāng)我們調(diào)用第一個方法棧幀時,它就會給第一個方法分配棧幀空間,并且壓入棧內(nèi),當(dāng)這個方法執(zhí)行完了,就會把這個方法棧幀出棧,釋放這個方法所占用的內(nèi)存。
一個棧內(nèi)可能有多個棧幀存在。
總結(jié)
Java Virtual Machine Stacks(Java虛擬機(jī)棧)
- 每個線程運(yùn)行時所需要的內(nèi)存,稱為虛擬機(jī)棧
- 每個棧由多個棧幀(Frame)組成,對應(yīng)著每次方法調(diào)用時所占用的內(nèi)存
- 每個線程只能有一個活動棧幀,對應(yīng)著當(dāng)前正在執(zhí)行的那個方法(位于棧頂)
活動棧幀表示線程正在執(zhí)行的方法。
3.2.棧的演示
public?class?teststacks?{ public?static?void?main(String[]?args)?throws?InterruptedException{ method1(); } public?static?void?method1(){ method2(1,2); } public?static?int?method2(int?a,int?b){ int?c=a+b; return?c; }}
可以自行調(diào)試以上代碼來觀察棧中的變化情況。
入棧順序:main->method1->method2
出棧順序:method2->method1->main
3.3.棧的問題辨析
-
垃圾回收是否涉及棧內(nèi)存?
不涉及,垃圾回收只是回收堆內(nèi)存中的無用對象,棧內(nèi)存不需要對它執(zhí)行垃圾回收,隨著方法的調(diào)用結(jié)束,棧內(nèi)存就釋放了。 - 棧內(nèi)存分配越大越好嗎?
首先棧內(nèi)存可以指定:-Xss size(如果不指定棧內(nèi)存大小,不同系統(tǒng)會有一個不同的默認(rèn)值)
其次由于電腦內(nèi)存一定,假如有100Mb,如果給棧內(nèi)存指定為2Mb,則最多只能存在50個線程,所以并不是越大越好,棧內(nèi)存較大一般是可以進(jìn)行較多次的方法遞歸調(diào)用,而不會增強(qiáng)線程效率,反而會使線程數(shù)量減少,一般使用默認(rèn)大小。
3.4.棧的線程安全問題
看一個變量是否線程安全,首先就是看這個變量對多個線程是共享的還是私有的,共享的變量需要考慮線程安全。
其次局部變量也不能保證是線程安全的,需要看此變量是否逃離了方法的作用范圍(作為參數(shù)和返回值逃出方法作用范圍時需要考慮線程安全問題)
例如:
以下代碼中局部變量是私有的,是線程安全的
//多個線程同時執(zhí)行該方法,會不會造成x值混亂呢? //不會,因為x是方法內(nèi)的局部變量,是線程私有的,互不干擾 static?void?m1(){ int?x=0; for(int?i=0;i<p>但是如果我們把變量的類型改為static,此時就大不一樣了,x是靜態(tài)變量,線程1和線程2同時擁有同一個x,static變量針對多個線程是一個共享的,不加安全保護(hù)的話,就會出現(xiàn)線程安全問題。</p><pre class="brush:php;toolbar:false"> static?void?m1(){ static?int?x=0; for(int?i=0;i<p>我們再看幾個方法</p><pre class="brush:php;toolbar:false">public?static?void?main(String[]?args)?{ ????????StringBuilder?sb?=?new?StringBuilder(); ????????sb.append(4); ????????sb.append(5); ????????sb.append(6); ????????new?Thread(()->{ ????????????m2(sb); ????????}).start(); ????} ????public?static?void?m1()?{ ????????StringBuilder?sb?=?new?StringBuilder(); ????????sb.append(1); ????????sb.append(2); ????????sb.append(3); ????????System.out.println(sb.toString()); ????} ????public?static?void?m2(StringBuilder?sb)?{ ????????sb.append(1); ????????sb.append(2); ????????sb.append(3); ????????System.out.println(sb.toString()); ????} ????public?static?StringBuilder?m3()?{ ????????StringBuilder?sb?=?new?StringBuilder(); ????????sb.append(1); ????????sb.append(2); ????????sb.append(3); ????????return?sb; ????}
m1是線程安全的:m1中的sb是線程中的局部變量,它是屬于線程私有的
m2線程不安全:sb它是方法的參數(shù),有可能有其它的線程訪問到它,它就不再是線程私有的了,它對多個線程是共享的。
m3不是線程安全的:它被當(dāng)成返回結(jié)果返回了,返回了有可能其它的線程拿到這個對象,從而并發(fā)的修改。
3.5.棧內(nèi)存溢出(StackOverflowError)
什么情況下會導(dǎo)致棧內(nèi)存溢出吶?
1.棧幀過多導(dǎo)致棧內(nèi)存溢出(一般遞歸調(diào)用次數(shù)太多,進(jìn)棧太多導(dǎo)致溢出)
這里最容易出現(xiàn)的場景是函數(shù)的遞歸調(diào)用。
2.棧幀過大導(dǎo)致棧內(nèi)存溢出(不太容易出現(xiàn))
棧內(nèi)存溢出代碼演示1(自己開發(fā)):
測試以下的程序,其中遞歸函數(shù)沒有遞歸邊界
public?class?Demo1_2?{ private?static?int?count; ????public?static?void?main(String[]?args)?{ ????????try?{ ????????????method1(); ????????}?catch?(Throwable?e)?{ ????????????e.printStackTrace(); ????????????System.out.println(count); ????????} ????} ????private?static?void?method1()?{ ????????count++; ????????method1(); ????}}
運(yùn)行結(jié)果如下
…
這里報了錯誤StackOverflowError。
總共進(jìn)行了22846次遞歸調(diào)用
idea中設(shè)置棧內(nèi)存大?。?/strong>
將棧內(nèi)存設(shè)置的小一點(diǎn),發(fā)現(xiàn)5000多次遞歸調(diào)用就溢出了。
棧內(nèi)存溢出代碼演示2(第三方依賴庫出現(xiàn)):
本案例可以使用JsonIgnore注解解決循環(huán)依賴,數(shù)據(jù)轉(zhuǎn)換時,只讓部門類去關(guān)聯(lián)員工類,員工類不再關(guān)聯(lián)部門類,在員工類的部門屬性(dept)上加@JsonIgnore注解。具體使用詳情可以點(diǎn)擊此處查看
3.6.線程運(yùn)行診斷
3.6.1.案例1:cpu占用過多(linux系統(tǒng)為例)
排查步驟:
1.在linux中使用top命令,去查看后臺進(jìn)程對cpu的占用情況
注意,在這之前我們運(yùn)行了一道Java程序
Java代碼占用了CPU的99.3%.top命令只能定位到進(jìn)程,而無法定位到線程。
2.查看線程對cpu的占用情況:ps H -eo pid,tid,%cpu
如果顯示過多,可使用ps H -eo pid,tid,%cpu | grep 進(jìn)程id,過濾掉不想看的部分進(jìn)程
注意:ps不僅可以查看進(jìn)程,也可以查看線程對CPU的占用情況。H把進(jìn)程中的線程所有信息都展示出來。-eo規(guī)定輸出感興趣的內(nèi)容,這里我們想看看pid,tid和CPU的占用情況%cpu
當(dāng)線程數(shù)太多,排查不方便的話,我們可以用grep pid來進(jìn)行篩選,過濾掉不感興趣的進(jìn)程
ps H -eo pid,tid,%cpu |grep 32655
3.定位到是哪個線程占用內(nèi)存過高后,再使用Jdk提供的命令(jstack+進(jìn)程id)去查看進(jìn)程中各線程的運(yùn)行信息,需要把第二步中查到的線程id(十進(jìn)制)轉(zhuǎn)為十六進(jìn)制,然后進(jìn)行比較查詢到位置后判斷異常信息。
thread1,thread2,thread3是我們自己定義的線程。
可以根據(jù)線程id,找到有問題的線程,進(jìn)一步定位到問題代碼的源碼行號
3.6.2.案例2:線程診斷_遲遲得不到結(jié)果
仍然通過jdk提供的 jstack+進(jìn)程id的方式,去查看進(jìn)程中各個線程的運(yùn)行信息
4.本地方法棧
含義:Java虛擬機(jī)調(diào)用本地方法時,需要給本地方法提供的一些內(nèi)存空間
本地方法不是由Java編寫的代碼,由于Java有時不能直接和操作系統(tǒng)打交道,所以需要用C/C++語言來與操作系統(tǒng)打交道,那么Java就可以通過調(diào)用本地方法來獲得這些功能。本地方法非常的多,如Object類的clone(),hashCode方法,wait方法,notify方法等
public?native?int?hashCode();
5.堆
5.1.定義
1.虛擬機(jī)棧,程序計數(shù)器,本地方法棧,這些都是線程私有的,而堆和方法區(qū),是線程公用的一塊內(nèi)存區(qū)域。
2.通過new關(guān)鍵字創(chuàng)建的對象都會使用堆內(nèi)存
3.由于堆是線程共享的,堆內(nèi)的對象都要考慮線程安全問題(也有一些例外)
4.堆有垃圾回收機(jī)制,不再被引用的對象會被回收
5.2.堆內(nèi)存溢出(OutOfMemoryError:Java heap space)
對象一直存在于堆中未被回收,且占用內(nèi)存越來越大,最終導(dǎo)致堆內(nèi)存溢出(雖然堆中有垃圾回收機(jī)制,但垃圾回收機(jī)制不是回收所有的對象)
我們可以看看下面的代碼
public?static?void?main(String[]?args)?{ ????????int?i?=?0; ????????try?{ ????????????List<string>?list?=?new?ArrayList(); ????????????String?a?=?"hello"; ????????????while?(true)?{ ????????????????list.add(a);?//?hello,?hellohello,?hellohellohellohello?... ????????????????a?=?a?+?a;??//?hellohellohellohello ????????????????i++; ????????????} ????????}?catch?(Throwable?e)?{ ????????????e.printStackTrace(); ????????????System.out.println(i); ????????}}</string>
報了錯誤java.lang.OutOfMemoryError
代碼中每次都拼接一個hello,由于定義的list集合創(chuàng)建在try語句里面,所以在for循環(huán)不斷執(zhí)行過程中,list集合是不會被回收的,只要程序還沒到catch之前,它就一直有效。而字符串對象都被追加到了集合內(nèi)部,字符串對象由于一直被使用,所以不會被回收。
我們可以通過-Xmx來設(shè)置堆空間大小。
我們把堆內(nèi)存改成8M(之前內(nèi)存是4G),此時只運(yùn)行了17次。
5.3.堆內(nèi)存診斷
1.jps工具:jps,查看當(dāng)前進(jìn)程中有哪些Java進(jìn)程,并將進(jìn)程id顯示出來(idea中通過terminal命令行輸入命令)
2.jmap工具:jmap -heap 進(jìn)程id 查詢某一個時刻堆內(nèi)存的占用情況
3.jconsole工具:圖形界面的,多功能監(jiān)測工具,可連續(xù)監(jiān)測,使用流程圖如下(1-2-3):
6.方法區(qū)
6.1.定義
方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,他用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)常量、即時編譯器編譯后的代碼等數(shù)據(jù)。(與類有關(guān)的信息)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是他卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。方法區(qū)在虛擬機(jī)啟動時創(chuàng)建。
對于習(xí)慣在HotSpot虛擬機(jī)上開發(fā)、部署程序的開發(fā)者來說,很多都更愿意把方法取稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,僅僅是因為HotSpot虛擬機(jī)的設(shè)計團(tuán)隊選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內(nèi)存,能夠省去專門為方法區(qū)編寫內(nèi)存管理代碼的工作。對于其他虛擬機(jī)(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。原則上,如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)細(xì)節(jié),不受虛擬機(jī)規(guī)范約束,但使用永久代來實(shí)現(xiàn)方法區(qū),現(xiàn)在看來并不是一個好主意,因為這樣更容易遇到內(nèi)存溢出問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到進(jìn)程可用內(nèi)存的上限,例如32位系統(tǒng)中的4GB,就不會出現(xiàn)問題),而且有極少數(shù)方法(例如String.intern())會因這個原因?qū)е虏煌摂M機(jī)下有不同的表現(xiàn)。因此,對于HotSpot虛擬機(jī),根據(jù)官方發(fā)布的路線圖信息,現(xiàn)在也已放棄永久代并逐步改為采用Navtive Memory來實(shí)現(xiàn)方法區(qū)的規(guī)劃,在JDK1.7的HostSpot中,已經(jīng)把原本放在永久代的字符串常量池移出,jdk1.8中后稱作元空間,用的操作系統(tǒng)內(nèi)存。
Java虛擬機(jī)規(guī)范對方法區(qū)的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以喧囂而固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集。相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了。這區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說,這個區(qū)域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是這部分區(qū)域的回收確實(shí)是必要的。在Sun公司的BUG列表中,曾出現(xiàn)過的若干個嚴(yán)重的BUG就是由于低版本的HotSpot虛擬機(jī)對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。
文原文關(guān)于虛擬機(jī)的定義:
6.2.定義
jdk1.8之前,方法區(qū)是用的堆內(nèi)存,1.8之后,方法區(qū)用的操作系統(tǒng)內(nèi)存。
這塊不是太清晰,可以參考下此篇博客點(diǎn)擊查看
常量池分為靜態(tài)常量池和動態(tài)常量池,下圖中的常量池指的是動態(tài)常量池,因為它們已經(jīng)被讀入內(nèi)存中去,而靜態(tài)常量池存在于class文件中
6.3.方法區(qū)內(nèi)存溢出(OutOfMemoryError: Metaspace)
1.8以前會導(dǎo)致永久代內(nèi)存溢出
1.8以后會導(dǎo)致元空間內(nèi)存溢出
/** ?*?演示元空間內(nèi)存溢出?java.lang.OutOfMemoryError:?Metaspace ?*?-XX:MaxMetaspaceSize=8m ?*/public?class?Demo1_8?extends?ClassLoader?{?//?可以用來加載類的二進(jìn)制字節(jié)碼 ????public?static?void?main(String[]?args)?{ ????????int?j?=?0; ????????try?{ ????????????Demo1_8?test?=?new?Demo1_8(); ???????????? ????????????for?(int?i?=?0;?i?<p><strong>jdk1.8以后, 默認(rèn)情況下,方法區(qū)用的是系統(tǒng)內(nèi)存,所以加大還是不會導(dǎo)致內(nèi)存溢出,循環(huán)很多次都運(yùn)行成功。</strong><br><strong>當(dāng)設(shè)置了-XX:MaxMetaspaceSize=8m,到了5411次就溢出了。報的是java.lang.OutOfMemoryError: Metaspace錯誤</strong></p><p>而1.8以前永久代溢出報的錯誤是java.lang.OutOfMemoryError:PermGen space</p><p><strong>6.4.常量池</strong></p><p><img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/052/b93dd11973d3f8094228c146b3131c12-17.png" class="lazy" alt="JVM學(xué)習(xí)之 Java記憶體結(jié)構(gòu)"></p><p><strong>常量池,就是一張表,虛擬機(jī)指令根據(jù)這站常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量信息(如字符串常量、true和false)</strong>。<br><em><em>運(yùn)行時常量池,常量池是</em>.class文件中的,當(dāng)該類被加載,它的常量池信息就會放入運(yùn)行時常量池,并把里面的符號地址變?yōu)檎鎸?shí)地址</em>*。</p><pre class="brush:php;toolbar:false">public?class?HelloWorld?{ public?static?void?main(String[]?args)?{ System.out.println("hello,world"); }}
以上是一個helloworld程序,helloworld要運(yùn)行,肯定要先編譯成一個二進(jìn)制字節(jié)碼。
二進(jìn)制字節(jié)碼由類的基本信息、常量池、類方法定義(包含了虛擬機(jī)指令)。
反編譯HelloWorld(之前需要運(yùn)行將.java文件編譯成.class文件)
使用idea工具
F:\IDEA\projects\jvm>javap?-v?F:\IDEA\projects\jvm\out\production\untitled\HelloWorld.class
F:\IDEA\projects\jvm\out\production\untitled\是HelloWorld.class所在的路徑
顯示類的詳細(xì)信息
Classfile?/F:/IDEA/projects/jvm/out/production/untitled/HelloWorld.class ??Last?modified?2021-1-30;?size?533?bytes ??MD5?checksum?82d075eb7217b4d23706f6cfbd44f8f1 ??Compiled?from?"HelloWorld.java"public?class?HelloWorld ??minor?version:?0 ??major?version:?52 ??flags:?ACC_PUBLIC,?ACC_SUPER
可以看到類的文件,最后修改時間,簽名。以及版本等等。有的還有訪問修飾符、父類和接口等詳細(xì)信息。
顯示常量池
Constant?pool: ???#1?=?Methodref??????????#6.#20?????????//?java/lang/Object."<init>":()V ???#2?=?Fieldref???????????#21.#22????????//?java/lang/System.out:Ljava/io/PrintStream; ???#3?=?String?????????????#23????????????//?hello,world ???#4?=?Methodref??????????#24.#25????????//?java/io/PrintStream.println:(Ljava/lang/String;)V ???#5?=?Class??????????????#26????????????//?HelloWorld ???#6?=?Class??????????????#27????????????//?java/lang/Object ???#7?=?Utf8???????????????<init> ???#8?=?Utf8???????????????()V ???#9?=?Utf8???????????????Code ??#10?=?Utf8???????????????LineNumberTable ??#11?=?Utf8???????????????LocalVariableTable ??#12?=?Utf8???????????????this ??#13?=?Utf8???????????????LHelloWorld; ??#14?=?Utf8???????????????main ??#15?=?Utf8???????????????([Ljava/lang/String;)V ??#16?=?Utf8???????????????args ??#17?=?Utf8???????????????[Ljava/lang/String; ??#18?=?Utf8???????????????SourceFile ??#19?=?Utf8???????????????HelloWorld.java ??#20?=?NameAndType????????#7:#8??????????//?"<init>":()V ??#21?=?Class??????????????#28????????????//?java/lang/System ??#22?=?NameAndType????????#29:#30????????//?out:Ljava/io/PrintStream; ??#23?=?Utf8???????????????hello,world ??#24?=?Class??????????????#31????????????//?java/io/PrintStream ??#25?=?NameAndType????????#32:#33????????//?println:(Ljava/lang/String;)V ??#26?=?Utf8???????????????HelloWorld ??#27?=?Utf8???????????????java/lang/Object ??#28?=?Utf8???????????????java/lang/System ??#29?=?Utf8???????????????out ??#30?=?Utf8???????????????Ljava/io/PrintStream; ??#31?=?Utf8???????????????java/io/PrintStream ??#32?=?Utf8???????????????println ??#33?=?Utf8???????????????(Ljava/lang/String;)V</init></init></init>
顯示方法定義
{ ??public?HelloWorld(); ????descriptor:?()V ????flags:?ACC_PUBLIC ????Code: ??????stack=1,?locals=1,?args_size=1 ?????????0:?aload_0?????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V ?????????4:?return ??????LineNumberTable: ????????line?1:?0 ??????LocalVariableTable: ????????Start??Length??Slot??Name???Signature????????????0???????5?????0??this???LHelloWorld; ??public?static?void?main(java.lang.String[]); ????descriptor:?([Ljava/lang/String;)V ????flags:?ACC_PUBLIC,?ACC_STATIC ????Code: ??????stack=2,?locals=1,?args_size=1 ?????????0:?getstatic?????#2??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream; ?????????3:?ldc???????????#3??????????????????//?String?hello,world ?????????5:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V ?????????8:?return ??????LineNumberTable: ????????line?3:?0 ????????line?4:?8 ??????LocalVariableTable: ????????Start??Length??Slot??Name???Signature????????????0???????9?????0??args???[Ljava/lang/String;}</init>
第一個方法是public HelloWorld();它是編譯器自動為我們構(gòu)造的無參構(gòu)造方法。
第二個是public static void main(java.lang.String[]);即main方法
方噶里面就包括了虛擬機(jī)的指令了。
getstatic獲取一個靜態(tài)變量,即獲取System.out靜態(tài)變量
ldc是加載一個參數(shù),參數(shù)是字符串hello,world
invokevirtual虛方法調(diào)用,println方法
return執(zhí)行結(jié)束。
我們getstatic、ldc、invokevirtual后面都有一個#2,#3,#4。在解釋器翻譯這些虛擬機(jī)指令的時候,它會把這些#2,#3,#4進(jìn)行一個查表翻譯。比如getstatic #2,就去查常量池的表。在常量池中
#2 = Fieldref #21.#22 引用的是成員變量#21,#22.
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
然后再去找#28.29,30
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
所以現(xiàn)在我就知道了,我是要找到j(luò)ava.lang.system類下叫out的成員變量,類型是java/io。
同理,ldc是找#3 = String #23 Utf8 hello,world,它是虛擬機(jī)常量池的一個字符串。把helloworld常量變成字符串對象加載進(jìn)來。
invokevirtual #4 Methodref #24.#25 等等
所以常量池的作用就是給我們指令提供一些常量符號,根據(jù)這些常量符號,我們就可以根據(jù)查表的方式去找到它,這樣虛擬機(jī)才能成功的執(zhí)行它。
相關(guān)免費(fèi)學(xué)習(xí)推薦:java基礎(chǔ)教程
以上是JVM學(xué)習(xí)之 Java記憶體結(jié)構(gòu)的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)