主頁 > 後端開發 > JVM學習-Java記憶體結構(詳細易懂)

JVM學習-Java記憶體結構(詳細易懂)

2021-02-01 12:14:12 後端開發

Java記憶體結構

  • 1.JVM概述
  • 2.程式計數器
    • 2.1.定義
    • 2.2.作用及特點解釋
  • 3.虛擬機堆疊
    • 3.1.堆疊的特點
    • 3.2.堆疊的演示
    • 3.3.堆疊的問題辨析
    • 3.4.堆疊的執行緒安全問題
    • 3.5.堆疊記憶體溢位(StackOverflowError)
    • 3.6.執行緒運行診斷
      • 3.6.1.案例1:cpu占用過多(linux系統為例)
      • 3.6.2.案例2:執行緒診斷_遲遲得不到結果
  • 4.本地方法堆疊
  • 5.堆
    • 5.1.定義
    • 5.2.堆記憶體溢位(OutOfMemoryError:Java heap space)
    • 5.3.堆記憶體診斷
  • 6.方法區
    • 6.1.定義
    • 6.2.定義
    • 6.3.方法區記憶體溢位(OutOfMemoryError: Metaspace)
    • 6.4.常量池

1.JVM概述

定義:
JVM全稱是Java Virtual Machine-java程式的運行環境(java二進制位元組碼的運行環境)

好處:

  • 一次撰寫,到處運行(跨平臺)
  • 自動記憶體管理,垃圾回收功能
  • 陣列下標越界檢查
  • 多型

比較JVM,JRE,JDK之間的聯系和區別,我們可以用一張圖來解釋
在這里插入圖片描述
JVM體系結構如圖所示
在這里插入圖片描述
一個類從Java源代碼(.java檔案)編譯成了Java二進制位元組碼以后,必須經過類加載器才能被加載到JVM里面才能運行,
我們一般把類放在方法區里,類將來創建的物件放在堆的部分,而堆里面的物件在呼叫方法時會用到虛擬機堆疊和程式計數器以及本地方發展,
方法執行時每行代碼是由執行引擎中的解釋器逐行進行執行的,方法里面的熱點代碼也就是頻繁呼叫的代碼,由即時編譯器來編譯執行,GC會對垃圾進行回收,
我們可以通過本地方法介面來呼叫作業系統提供的功能,

JVM的記憶體結構包括:
1.方法區
2.程式計數器
3.虛擬機堆疊
4.本地方法堆疊
5.堆

2.程式計數器

2.1.定義

Program Counter Register程式計數器(暫存器)
作用:
??是記住下一條jvm指令的執行地址
特點
??是執行緒私有的
??不會存在記憶體溢位
(記憶體結構中唯一一個不會記憶體溢位的結構)

在2.2中我們將會解釋程式計數器的作用及特點,

2.2.作用及特點解釋

 二進制位元組碼			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:中去呼叫println()方法,然后依次列印1,2,3,4,5,這些指令不能直接交給CPU來執行,必須經過解釋器的作用,它負責把一條一條的位元組碼指令解釋成機器碼,然后機器碼就可以交給CPU來執行,
也就是
二進制位元組碼->解釋器->機器碼->CPU
實際上程式計數器的作用就是在指令的執行程序中,記住下一條JVM指令的執行地址,
上面我們二進制位元組碼前面的數字0,3,4…我們可以把其理解為地址,根據這些地址資訊,我們就可以找到命令來執行,
在每次拿到指令交給CPU執行之后,程式計數器就會把下一條指令的地址放入到程式計數器中,等一條指令執行完成之后,解釋器就會到程式計數器中取到下一條指令的地址,再把其經過解釋器解釋成機器碼然后交給CPU執行,然后一直重復這樣的程序
在物理上,實作程式計數器是通過暫存器來實作的,暫存器是CPU組件里讀取最快的存盤單元

程式計數器是執行緒私有的
假如說上述代碼都在執行緒1中運行,同時運行的還有執行緒2和執行緒3,多個執行緒運行的時候,CPU會給每個執行緒分配時間片,給執行緒1分配時間片,如果執行緒1在指定的時間沒有運行完,它就會把狀態暫存,切換到執行緒2,執行緒2執行自己的代碼,執行緒2執行完了,再繼續執行執行緒1的代碼,在執行緒切換的程序中,我們要記住下一條指令的執行地址,就需要用到程式計數器,假如說執行緒1剛開始執行到第9行代碼,恰好這個時候時間片用完,CPU切換到執行緒2去執行,這時它就會把下一條指令的地址10記錄到程式計數器里面,而且程式計數器是執行緒私有的,它是屬于執行緒1的,等執行緒2代碼執行完了,執行緒1搶到了時間片,它就會從自己的程式計數器里面取出下一行代碼,每個執行緒都有自己的程式計數器

3.虛擬機堆疊

3.1.堆疊的特點

堆疊類似現實生活中的子彈夾,堆疊最重要的特點是后進先出,
在這里插入圖片描述
如圖,1是最先進入堆疊中的,3是最后進入堆疊中的,但是在出堆疊的時候,3最先出堆疊,1最后出堆疊,即他們按照1,2,3的順序入堆疊,按照3,2,1的順序出堆疊

虛擬機堆疊就是我們執行緒運行時需要的記憶體空間,一個執行緒運行時需要一個堆疊,如果將來有多個執行緒的話,它就會有多個虛擬機堆疊,
每個堆疊可以看成是由多個堆疊幀組成,例如上圖中每個元素1,2,3都可以看成是堆疊幀,
一個堆疊幀就對應著Java中一個方法的呼叫,即堆疊幀就是每個方法運行時需要的記憶體,每個方法運行時需要的記憶體一般有引數,區域變數,回傳地址,這些都需要占用記憶體,所以每個方法執行時,都要預先把這些記憶體分配好,
當我們呼叫第一個方法堆疊幀時,它就會給第一個方法分配堆疊幀空間,并且壓入堆疊內,當這個方法執行完了,就會把這個方法堆疊幀出堆疊,釋放這個方法所占用的記憶體
一個堆疊內可能有多個堆疊幀存在,

總結
Java Virtual Machine Stacks(Java虛擬機堆疊)

  • 每個執行緒運行時所需要的記憶體,稱為虛擬機堆疊
  • 每個堆疊由多個堆疊幀(Frame)組成,對應著每次方法呼叫時所占用的記憶體
  • 每個執行緒只能有一個活動堆疊幀,對應著當前正在執行的那個方法(位于堆疊頂)

活動堆疊幀表示執行緒正在執行的方法,

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.堆疊的問題辨析

  1. 垃圾回收是否涉及堆疊記憶體?
    不涉及,垃圾回收只是回收堆記憶體中的無用物件,堆疊記憶體不需要對它執行垃圾回收,隨著方法的呼叫結束,堆疊記憶體就釋放了,
  2. 堆疊記憶體分配越大越好嗎?
    首先堆疊記憶體可以指定:-Xss size(如果不指定堆疊記憶體大小,不同系統會有一個不同的默認值)
    其次由于電腦記憶體一定,假如有100Mb,如果給堆疊記憶體指定為2Mb,則最多只能存在50個執行緒,所以并不是越大越好,堆疊記憶體較大一般是可以進行較多次的方法遞回呼叫,而不會增強執行緒效率,反而會使執行緒數量減少,一般使用默認大小

3.4.堆疊的執行緒安全問題

看一個變數是否執行緒安全,首先就是看這個變數對多個執行緒是共享的還是私有的,共享的變數需要考慮執行緒安全,
其次區域變數也不能保證是執行緒安全的,需要看此變數是否逃離了方法的作用范圍(作為引數和回傳值逃出方法作用范圍時需要考慮執行緒安全問題)
例如:
以下代碼中區域變數是私有的,是執行緒安全的

	//多個執行緒同時執行該方法,會不會造成x值混亂呢?
	//不會,因為x是方法內的區域變數,是執行緒私有的,互不干擾
	static void m1(){
		int x=0;
		for(int i=0;i<5000;i++){
			x++;
		}
		System.out.println(x);
	}

但是如果我們把變數的型別改為static,此時就大不一樣了,x是靜態變數,執行緒1和執行緒2同時擁有同一個x,static變數針對多個執行緒是一個共享的,不加安全保護的話,就會出現執行緒安全問題,

	static void m1(){
		static int x=0;
		for(int i=0;i<5000;i++){
			x++;
		}
		System.out.println(x);
	}

我們再看幾個方法

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它是方法的引數,有可能有其它的執行緒訪問到它,它就不再是執行緒私有的了,它對多個執行緒是共享的,
m3不是執行緒安全的:它被當成回傳結果回傳了,回傳了有可能其它的執行緒拿到這個物件,從而并發的修改,

3.5.堆疊記憶體溢位(StackOverflowError)

什么情況下會導致堆疊記憶體溢位吶?
1.堆疊幀過多導致堆疊記憶體溢位(一般遞回呼叫次數太多,進堆疊太多導致溢位)
這里最容易出現的場景是函式的遞回呼叫,
2.堆疊幀過大導致堆疊記憶體溢位(不太容易出現)

堆疊記憶體溢位代碼演示1(自己開發):
測驗以下的程式,其中遞回函式沒有遞回邊界

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中設定堆疊記憶體大小:
在這里插入圖片描述
將堆疊記憶體設定的小一點,發現5000多次遞回呼叫就溢位了,

堆疊記憶體溢位代碼演示2(第三方依賴庫出現):
在這里插入圖片描述
本案例可以使用JsonIgnore注解解決回圈依賴,資料轉換時,只讓部門類去關聯員工類,員工類不再關聯部門類,在員工類的部門屬性(dept)上加@JsonIgnore注解,具體使用詳情可以點擊此處查看

3.6.執行緒運行診斷

3.6.1.案例1:cpu占用過多(linux系統為例)

排查步驟:

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規定輸出感興趣的內容,這里我們想看看pid,tid和CPU的占用情況%cpu
在這里插入圖片描述
當執行緒數太多,排查不方便的話,我們可以用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有時不能直接和作業系統打交道,所以需要用C/C++語言來與作業系統打交道,那么Java就可以通過呼叫本地方法來獲得這些功能,本地方法非常的多,如Object類的clone(),hashCode方法,wait方法,notify方法等

public native int hashCode();

5.堆

5.1.定義

1.虛擬機堆疊,程式計數器,本地方法堆疊,這些都是執行緒私有的,而堆和方法區,是執行緒公用的一塊記憶體區域
2.通過new關鍵字創建的物件都會使用堆記憶體
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);
        }
}

在這里插入圖片描述
報了錯誤java.lang.OutOfMemoryError
代碼中每次都拼接一個hello,由于定義的list集合創建在try陳述句里面,所以在for回圈不斷執行程序中,list集合是不會被回收的,只要程式還沒到catch之前,它就一直有效,而字串物件都被追加到了集合內部,字串物件由于一直被使用,所以不會被回收,
我們可以通過-Xmx來設定堆空間大小,
在這里插入圖片描述
我們把堆記憶體改成8M(之前記憶體是4G),此時只運行了17次,

5.3.堆記憶體診斷

1.jps工具:jps,查看當前行程中有哪些Java行程,并將行程id顯示出來(idea中通過terminal命令列輸入命令)
2.jmap工具:jmap -heap 行程id 查詢某一個時刻堆記憶體的占用情況
3.jconsole工具:圖形界面的,多功能監測工具,可連續監測,使用流程圖如下(1-2-3):

6.方法區

6.1.定義

方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,他用于存盤已被虛擬機加載的類資訊、常量、靜態常量、即時編譯器編譯后的代碼等資料,(與類有關的資訊),雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是他卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來方法區在虛擬機啟動時創建
對于習慣在HotSpot虛擬機上開發、部署程式的開發者來說,很多都更愿意把方法取稱為“永久代”(Permanent Generation),本質上兩者并不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實作方法區而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分記憶體,能夠省去專門為方法區撰寫記憶體管理代碼的作業,對于其他虛擬機(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的,原則上,如何實作方法區屬于虛擬機實作細節,不受虛擬機規范約束,但使用永久代來實作方法區,現在看來并不是一個好主意,因為這樣更容易遇到記憶體溢位問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到行程可用記憶體的上限,例如32位系統中的4GB,就不會出現問題),而且有極少數方法(例如String.intern())會因這個原因導致不同虛擬機下有不同的表現,因此,對于HotSpot虛擬機,根據官方發布的路線圖資訊,現在也已放棄永久代并逐步改為采用Navtive Memory來實作方法區的規劃,在JDK1.7的HostSpot中,已經把原本放在永久代的字串常量池移出,jdk1.8中后稱作元空間,用的作業系統記憶體,
Java虛擬機規范對方法區的限制非常寬松,除了和Java堆一樣不需要連續的記憶體和可以喧囂而固定大小或者可擴展外,還可以選擇不實作垃圾收集,相對而言,垃圾收集行為在這個區域是比較少出現的,但并非資料進入了方法區就如永久代的名字一樣“永久”存在了,這區域的記憶體回收目標主要是針對常量池的回收和對型別的卸載,一般來說,這個區域的回收“成績”比較難以令人滿意,尤其是型別的卸載,條件相當苛刻,但是這部磁區域的回收確實是必要的,在Sun公司的BUG串列中,曾出現過的若干個嚴重的BUG就是由于低版本的HotSpot虛擬機對此區域未完全回收而導致記憶體泄漏,
根據Java虛擬機規范的規定,當方法區無法滿足記憶體分配需求時,將拋出OutOfMemoryError例外,

文原文關于虛擬機的定義:

在這里插入圖片描述

6.2.定義

jdk1.8之前,方法區是用的堆記憶體,1.8之后,方法區用的作業系統記憶體,
這塊不是太清晰,可以參考下此篇博客點擊查看
常量池分為靜態常量池和動態常量池,下圖中的常量池指的是動態常量池,因為它們已經被讀入記憶體中去,而靜態常量池存在于class檔案中
在這里插入圖片描述

6.3.方法區記憶體溢位(OutOfMemoryError: Metaspace)

1.8以前會導致永久代記憶體溢位

1.8以后會導致元空間記憶體溢位

/**
 * 演示元空間記憶體溢位 java.lang.OutOfMemoryError: Metaspace
 * -XX:MaxMetaspaceSize=8m
 */
public class Demo1_8 extends ClassLoader { // 可以用來加載類的二進制位元組碼
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成類的二進制位元組碼
                ClassWriter cw = new ClassWriter(0);
                //引數:版本號, public, 類名, 包名, 父類, 介面
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 生成類,二進制位元組碼用byte來表示,回傳 byte[]
                byte[] code = cw.toByteArray();
                // 執行了類的加載
                test.defineClass("Class" + i, code, 0, code.length); // Class 物件
            }
        } finally {
            System.out.println(j);
        }
    }
}

jdk1.8以后, 默認情況下,方法區用的是系統記憶體,所以加大還是不會導致記憶體溢位,回圈很多次都運行成功,
當設定了-XX:MaxMetaspaceSize=8m,到了5411次就溢位了,報的是java.lang.OutOfMemoryError: Metaspace錯誤

而1.8以前永久代溢位報的錯誤是java.lang.OutOfMemoryError:PermGen space

6.4.常量池

在這里插入圖片描述

常量池,就是一張表,虛擬機指令根據這站常量表找到要執行的類名、方法名、引數型別、字面量資訊(如字串常量、true和false)
運行時常量池,常量池是.class檔案中的,當該類被加載,它的常量池資訊就會放入運行時常量池,并把里面的符號地址變為真實地址*,

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("hello,world");
	}
}

以上是一個helloworld程式,helloworld要運行,肯定要先編譯成一個二進制位元組碼,
二進制位元組碼由類的基本資訊、常量池、類方法定義(包含了虛擬機指令)
反編譯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

顯示方法定義

{
  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;
}

第一個方法是public HelloWorld();它是編譯器自動為我們構造的無參構造方法,
第二個是public static void main(java.lang.String[]);即main方法
方噶里面就包括了虛擬機的指令了,
getstatic獲取一個靜態變數,即獲取System.out靜態變數
ldc是加載一個引數,引數是字串hello,world
invokevirtual虛方法呼叫,println方法
return執行結束,
我們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;
所以現在我就知道了,我是要找到java.lang.system類下叫out的成員變數,型別是java/io,
同理,ldc是找#3 = String #23 Utf8 hello,world,它是虛擬機常量池的一個字串,把helloworld常量變成字串物件加載進來,
invokevirtual #4 Methodref #24.#25 等等
所以常量池的作用就是給我們指令提供一些常量符號,根據這些常量符號,我們就可以根據查表的方式去找到它,這樣虛擬機才能成功的執行它,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/255200.html

標籤:java

上一篇:2019年第十屆藍橋杯[Java]

下一篇:趣味編程|手寫一個集成多資料源mongodb的 starter

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more