Java Memory Structure
- 1.JVM Overview
- 2.Program Counter
-
- 2.1.Definition
- 2.2.Explanation of functions and characteristics
- 3.Virtual machine stack
- 3.1. Characteristics of stack
- 3.2. Demonstration of stack
- 3.3. Analysis of stack issues
- 3.4. Thread safety issues of stack
- 3.5.Stack Memory overflow (StackOverflowError)
- 3.6. Thread running diagnosis
- 3.6.1. Case 1: Too much cpu usage (linux system as an example)
- 3.6.2. Case 2: Thread diagnosis_no result for a long time
- 4.Local method stack
- 5.Heap
- 5.1.Definition
- 5.2.Heap memory overflow (OutOfMemoryError:Java heap space)
- 5.3.Heap memory diagnosis
- 6.Method area
- 6.1.Definition
- 6.2.Definition
- 6.3.Method area memory overflow (OutOfMemoryError: Metaspace)
- 6.4. Constant pool
1.JVM overview
Definition:
JVM The full name is Java Virtual Machine - the running environment of java programs (the running environment of java binary bytecode)
Benefits:
- Write once and run everywhere (cross-platform)
- Automatic memory management, garbage collection function
- Array subscript out-of-bounds check
- Polymorphism
Compare JVM, JRE, JDK The connections and differences between , we can use a picture to explain
JVM architectureAs shown in the picture
a After a class is compiled from Java source code (.java file) into Java binary bytecode, it must go through the class loader before it can be loaded into the JVM before it can run.
We usually put classes in the method area. The objects created by the class in the future are placed in the heap, and the objects in the heap will use the virtual machine stack and program counter as well as local development when calling methods.
When the method is executed, each line of code is executed line by line by the interpreter in the execution engine. The hot code in the method is the code that is frequently called and is compiled and executed by the just-in-time compiler. GC will collect garbage.
We can call the functions provided by the operating system through the local method interface.
The memory structure of JVM includes:
1. Method area
2. Program counter
3. Virtual machine stack
4. Local method stack
5. Heap
2.Program Counter
2.1.Definition
Program Counter RegisterProgram Counter( Register)
Function:
?It is to remember the execution address of the next jvm instruction
Features
?It is private to the thread
There will be no memory overflow(The only one in the memory structure that will not Memory overflow structure)
In 2.2 we will explain the function and characteristics of the program counter.
2.2. Explanation of functions and features
?二進制字節(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
We can see these codes. The first line of System.out assigns a value to a variable and calls it in 4: println() method. Then print 1,2,3,4,5 in sequence. These instructions cannot be directly handed over to the CPU for execution and must go through the interpreter. It is responsible for interpreting bytecode instructions one by one into machine code, and then the machine code can be handed over to the CPU for execution.
That is,
Binary bytecode->Interpreter->Machine code->CPU
In fact, the function of the program counter is to record during the execution of instructions. Live the execution address of the next JVM instruction.
The numbers 0, 3, 4 in front of our binary bytecode above...we can understand them as addresses. Based on this address information, we can find the command to execute.
Every time an instruction is given to the CPU for execution, the program counter will put the address of the next instruction into the program counter. After the execution of an instruction is completed, the interpreter will go to the program counter. Get the address of the next instruction. It is then interpreted into machine code by the interpreter and then handed over to the CPU for execution. Then keep repeating this process.
Physically, the program counter is implemented through registers. Registers are the fastest read storage units in CPU components.
程序計數(shù)器是線程私有的
假如說上述代碼都在線程1中運行,同時運行的還有線程2和線程3,多個線程運行的時候,CPU會給每個線程分配時間片,給線程1分配時間片,如果線程1在指定的時間沒有運行完,它就會把狀態(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.虛擬機棧
3.1.棧的特點
棧類似現(xiàn)實生活中的子彈夾。棧最重要的特點是后進先出。
如圖,1是最先進入棧中的,3是最后進入棧中的,但是在出棧的時候,3最先出棧,1最后出棧。即他們按照1,2,3的順序入棧,按照3,2,1的順序出棧
虛擬機棧就是我們線程運行時需要的內存空間,一個線程運行時需要一個棧。如果將來有多個線程的話,它就會有多個虛擬機棧。
每個??梢钥闯墒怯啥鄠€棧幀組成,例如上圖中每個元素1,2,3都可以看成是棧幀。
一個棧幀就對應著Java中一個方法的調用,即棧幀就是每個方法運行時需要的內存。每個方法運行時需要的內存一般有參數(shù),局部變量,返回地址,這些都需要占用內存,所以每個方法執(zhí)行時,都要預先把這些內存分配好。
當我們調用第一個方法棧幀時,它就會給第一個方法分配棧幀空間,并且壓入棧內,當這個方法執(zhí)行完了,就會把這個方法棧幀出棧,釋放這個方法所占用的內存。
一個棧內可能有多個棧幀存在。
總結
Java Virtual Machine Stacks(Java虛擬機棧)
- 每個線程運行時所需要的內存,稱為虛擬機棧
- 每個棧由多個棧幀(Frame)組成,對應著每次方法調用時所占用的內存
- 每個線程只能有一個活動棧幀,對應著當前正在執(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; }}
可以自行調試以上代碼來觀察棧中的變化情況。
入棧順序:main->method1->method2
出棧順序:method2->method1->main
3.3.棧的問題辨析
-
垃圾回收是否涉及棧內存?
不涉及,垃圾回收只是回收堆內存中的無用對象,棧內存不需要對它執(zhí)行垃圾回收,隨著方法的調用結束,棧內存就釋放了。 - 棧內存分配越大越好嗎?
首先棧內存可以指定:-Xss size(如果不指定棧內存大小,不同系統(tǒng)會有一個不同的默認值)
其次由于電腦內存一定,假如有100Mb,如果給棧內存指定為2Mb,則最多只能存在50個線程,所以并不是越大越好,棧內存較大一般是可以進行較多次的方法遞歸調用,而不會增強線程效率,反而會使線程數(shù)量減少,一般使用默認大小。
3.4.棧的線程安全問題
看一個變量是否線程安全,首先就是看這個變量對多個線程是共享的還是私有的,共享的變量需要考慮線程安全。
其次局部變量也不能保證是線程安全的,需要看此變量是否逃離了方法的作用范圍(作為參數(shù)和返回值逃出方法作用范圍時需要考慮線程安全問題)
例如:
以下代碼中局部變量是私有的,是線程安全的
//多個線程同時執(zhí)行該方法,會不會造成x值混亂呢? //不會,因為x是方法內的局部變量,是線程私有的,互不干擾 static?void?m1(){ int?x=0; for(int?i=0;i<p>但是如果我們把變量的類型改為static,此時就大不一樣了,x是靜態(tài)變量,線程1和線程2同時擁有同一個x,static變量針對多個線程是一個共享的,不加安全保護的話,就會出現(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不是線程安全的:它被當成返回結果返回了,返回了有可能其它的線程拿到這個對象,從而并發(fā)的修改。
3.5.棧內存溢出(StackOverflowError)
什么情況下會導致棧內存溢出吶?
1.棧幀過多導致棧內存溢出(一般遞歸調用次數(shù)太多,進棧太多導致溢出)
這里最容易出現(xiàn)的場景是函數(shù)的遞歸調用。
2.棧幀過大導致棧內存溢出(不太容易出現(xiàn))
棧內存溢出代碼演示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(); ????}}
運行結果如下
…
這里報了錯誤StackOverflowError。
總共進行了22846次遞歸調用
idea中設置棧內存大?。?/strong>
將棧內存設置的小一點,發(fā)現(xiàn)5000多次遞歸調用就溢出了。
棧內存溢出代碼演示2(第三方依賴庫出現(xiàn)):
本案例可以使用JsonIgnore注解解決循環(huán)依賴,數(shù)據轉換時,只讓部門類去關聯(lián)員工類,員工類不再關聯(lián)部門類,在員工類的部門屬性(dept)上加@JsonIgnore注解。具體使用詳情可以點擊此處查看
3.6.線程運行診斷
3.6.1.案例1:cpu占用過多(linux系統(tǒng)為例)
排查步驟:
1.在linux中使用top命令,去查看后臺進程對cpu的占用情況
注意,在這之前我們運行了一道Java程序
Java代碼占用了CPU的99.3%.top命令只能定位到進程,而無法定位到線程。
2.查看線程對cpu的占用情況:ps H -eo pid,tid,%cpu
如果顯示過多,可使用ps H -eo pid,tid,%cpu | grep 進程id,過濾掉不想看的部分進程
注意:ps不僅可以查看進程,也可以查看線程對CPU的占用情況。H把進程中的線程所有信息都展示出來。-eo規(guī)定輸出感興趣的內容,這里我們想看看pid,tid和CPU的占用情況%cpu
當線程數(shù)太多,排查不方便的話,我們可以用grep pid來進行篩選,過濾掉不感興趣的進程
ps H -eo pid,tid,%cpu |grep 32655
3.定位到是哪個線程占用內存過高后,再使用Jdk提供的命令(jstack+進程id)去查看進程中各線程的運行信息,需要把第二步中查到的線程id(十進制)轉為十六進制,然后進行比較查詢到位置后判斷異常信息。
thread1,thread2,thread3是我們自己定義的線程。
可以根據線程id,找到有問題的線程,進一步定位到問題代碼的源碼行號
3.6.2.案例2:線程診斷_遲遲得不到結果
仍然通過jdk提供的 jstack+進程id的方式,去查看進程中各個線程的運行信息
4.本地方法棧
含義:Java虛擬機調用本地方法時,需要給本地方法提供的一些內存空間
本地方法不是由Java編寫的代碼,由于Java有時不能直接和操作系統(tǒng)打交道,所以需要用C/C++語言來與操作系統(tǒng)打交道,那么Java就可以通過調用本地方法來獲得這些功能。本地方法非常的多,如Object類的clone(),hashCode方法,wait方法,notify方法等
public?native?int?hashCode();
5.堆
5.1.定義
1.虛擬機棧,程序計數(shù)器,本地方法棧,這些都是線程私有的,而堆和方法區(qū),是線程公用的一塊內存區(qū)域。
2.通過new關鍵字創(chuàng)建的對象都會使用堆內存
3.由于堆是線程共享的,堆內的對象都要考慮線程安全問題(也有一些例外)
4.堆有垃圾回收機制,不再被引用的對象會被回收
5.2.堆內存溢出(OutOfMemoryError:Java heap space)
對象一直存在于堆中未被回收,且占用內存越來越大,最終導致堆內存溢出(雖然堆中有垃圾回收機制,但垃圾回收機制不是回收所有的對象)
我們可以看看下面的代碼
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之前,它就一直有效。而字符串對象都被追加到了集合內部,字符串對象由于一直被使用,所以不會被回收。
我們可以通過-Xmx來設置堆空間大小。
我們把堆內存改成8M(之前內存是4G),此時只運行了17次。
5.3.堆內存診斷
1.jps工具:jps,查看當前進程中有哪些Java進程,并將進程id顯示出來(idea中通過terminal命令行輸入命令)
2.jmap工具:jmap -heap 進程id 查詢某一個時刻堆內存的占用情況
3.jconsole工具:圖形界面的,多功能監(jiān)測工具,可連續(xù)監(jiān)測,使用流程圖如下(1-2-3):
6.方法區(qū)
6.1.定義
方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內存區(qū)域,他用于存儲已被虛擬機加載的類信息、常量、靜態(tài)常量、即時編譯器編譯后的代碼等數(shù)據。(與類有關的信息)。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是他卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區(qū)分開來。方法區(qū)在虛擬機啟動時創(chuàng)建。
對于習慣在HotSpot虛擬機上開發(fā)、部署程序的開發(fā)者來說,很多都更愿意把方法取稱為“永久代”(Permanent Generation),本質上兩者并不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區(qū),或者說使用永久代來實現(xiàn)方法區(qū)而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內存,能夠省去專門為方法區(qū)編寫內存管理代碼的工作。對于其他虛擬機(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。原則上,如何實現(xiàn)方法區(qū)屬于虛擬機實現(xiàn)細節(jié),不受虛擬機規(guī)范約束,但使用永久代來實現(xiàn)方法區(qū),現(xiàn)在看來并不是一個好主意,因為這樣更容易遇到內存溢出問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到進程可用內存的上限,例如32位系統(tǒng)中的4GB,就不會出現(xiàn)問題),而且有極少數(shù)方法(例如String.intern())會因這個原因導致不同虛擬機下有不同的表現(xiàn)。因此,對于HotSpot虛擬機,根據官方發(fā)布的路線圖信息,現(xiàn)在也已放棄永久代并逐步改為采用Navtive Memory來實現(xiàn)方法區(qū)的規(guī)劃,在JDK1.7的HostSpot中,已經把原本放在永久代的字符串常量池移出,jdk1.8中后稱作元空間,用的操作系統(tǒng)內存。
Java虛擬機規(guī)范對方法區(qū)的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內存和可以喧囂而固定大小或者可擴展外,還可以選擇不實現(xiàn)垃圾收集。相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的,但并非數(shù)據進入了方法區(qū)就如永久代的名字一樣“永久”存在了。這區(qū)域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說,這個區(qū)域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當苛刻,但是這部分區(qū)域的回收確實是必要的。在Sun公司的BUG列表中,曾出現(xiàn)過的若干個嚴重的BUG就是由于低版本的HotSpot虛擬機對此區(qū)域未完全回收而導致內存泄漏。
根據Java虛擬機規(guī)范的規(guī)定,當方法區(qū)無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
文原文關于虛擬機的定義:
6.2.定義
jdk1.8之前,方法區(qū)是用的堆內存,1.8之后,方法區(qū)用的操作系統(tǒng)內存。
這塊不是太清晰,可以參考下此篇博客點擊查看
常量池分為靜態(tài)常量池和動態(tài)常量池,下圖中的常量池指的是動態(tài)常量池,因為它們已經被讀入內存中去,而靜態(tài)常量池存在于class文件中
6.3.方法區(qū)內存溢出(OutOfMemoryError: Metaspace)
1.8以前會導致永久代內存溢出
1.8以后會導致元空間內存溢出
/** ?*?演示元空間內存溢出?java.lang.OutOfMemoryError:?Metaspace ?*?-XX:MaxMetaspaceSize=8m ?*/public?class?Demo1_8?extends?ClassLoader?{?//?可以用來加載類的二進制字節(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以后, 默認情況下,方法區(qū)用的是系統(tǒng)內存,所以加大還是不會導致內存溢出,循環(huán)很多次都運行成功。</strong><br><strong>當設置了-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 learning Java memory structure"></p><p><strong>常量池,就是一張表,虛擬機指令根據這站常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量信息(如字符串常量、true和false)</strong>。<br><em><em>運行時常量池,常量池是</em>.class文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變?yōu)檎鎸嵉刂?lt;/em>*。</p><pre class="brush:php;toolbar:false">public?class?HelloWorld?{ public?static?void?main(String[]?args)?{ System.out.println("hello,world"); }}
以上是一個helloworld程序,helloworld要運行,肯定要先編譯成一個二進制字節(jié)碼。
二進制字節(jié)碼由類的基本信息、常量池、類方法定義(包含了虛擬機指令)。
反編譯HelloWorld(之前需要運行將.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所在的路徑
顯示類的詳細信息
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
可以看到類的文件,最后修改時間,簽名。以及版本等等。有的還有訪問修飾符、父類和接口等詳細信息。
顯示常量池
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();它是編譯器自動為我們構造的無參構造方法。
第二個是public static void main(java.lang.String[]);即main方法
方噶里面就包括了虛擬機的指令了。
getstatic獲取一個靜態(tài)變量,即獲取System.out靜態(tài)變量
ldc是加載一個參數(shù),參數(shù)是字符串hello,world
invokevirtual虛方法調用,println方法
return執(zhí)行結束。
我們getstatic、ldc、invokevirtual后面都有一個#2,#3,#4。在解釋器翻譯這些虛擬機指令的時候,它會把這些#2,#3,#4進行一個查表翻譯。比如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)在我就知道了,我是要找到java.lang.system類下叫out的成員變量,類型是java/io。
同理,ldc是找#3 = String #23 Utf8 hello,world,它是虛擬機常量池的一個字符串。把helloworld常量變成字符串對象加載進來。
invokevirtual #4 Methodref #24.#25 等等
所以常量池的作用就是給我們指令提供一些常量符號,根據這些常量符號,我們就可以根據查表的方式去找到它,這樣虛擬機才能成功的執(zhí)行它。
相關免費學習推薦:java基礎教程
The above is the detailed content of JVM learning Java memory structure. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)