Android JVM學習(紮實基礎版)

android禿老師 發佈 2022-08-13T21:32:27.539483+00:00

簡述JVM 全稱 Java Virtual Machine,它是一種規範。JVM 是一個虛擬化的作業系統,類似於 Linux 或者 Windows 的作業系統,只是它架在作業系統上,接收字節碼,把字節碼翻譯成作業系統上的機器碼且進行執行。

簡述

JVM 全稱 java Virtual Machine,它是一種規範。JVM 是一個虛擬化的作業系統,類似於 Linux 或者 Windows 的作業系統,只是它架在作業系統上,接收字節碼,把字節碼翻譯成作業系統上的機器碼且進行執行。

JVM基本結構

由下圖可以清楚的看到JVM的內存空間分為3大部分:

  • 堆內存
  • 方法區
  • 棧內存

虛擬機棧

虛擬機棧是先進後出(FILO)的數據結構,是每個線程私有的,線程在運行時,在執行每個方法的時候都會打包成一個棧幀,存儲了局部變量表,操作數棧,動態連結,方法出口 等信息,然後放入棧。每個時刻正在執行的當前方法就是虛擬機棧頂的棧楨。方法的執行就對應著棧幀在虛擬機棧中入棧和出棧的過程。

棧幀

在每個 Java 方法被調用的時候,都會創建一個棧幀,併入棧。一旦方法完成相應的調用,則出棧。

棧幀一般包含四個區域:(局部變量表、操作數棧、動態連接、返回地址)

局部變量表:

用於存放我們的局部變量的(方法中的變量),主要存放我們的 Java 的八大基礎數據類型,如果是局部的一些對象,只需要存放它的一個引用地址即可。

操作數棧

存放 java 方法執行的操作數的,它就是一個棧,先進後出的棧結構,操作數棧,就是用來操作的,操作的元素可以是任意的 java 數據類型。

動態連接

每個棧幀中都包含一個在常量池中對當前方法的引用, 目的是支持方法調用過程的動態連接。

返回地址

正常退出,即正常執行到任何方法的返回字節碼指令,如 RETURN、IRETURN、ARETURN 等、

異常退出

我們來寫一個簡單的java代碼查看彙編指令

public class Stack {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a + b;
        System.out.println(c);
    }
}

執行 javac 生成class,再執行 javap -v Stack.class

ClassFile /C:/Users/Jack/Desktop/Stack.class
  Last modified 2021-11-24; size 406 bytes
  MD5 checksum 38b185e462a0e2aa0a06afdc67dfe12c
  Compiled from "Stack.java"
public class com.test.Stack
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // com/Stack
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Stack.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               com/Stack
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public com.test.Stack();
    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 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10 //將常量10壓入操作數棧(int取值-128~127時使用bipush指令)
         2: istore_1         //將10從操作數棧存儲到局部變量表第1個位置
         3: bipush        20 //將常量20壓入操作數棧
         5: istore_2         //將20從操作數棧存儲到局部變量表第2個位置
         6: iload_1          //加載局部變量第1個變量壓入操作數棧
         7: iload_2          //加載局部變量第2個變量壓入操作數棧
         8: iadd             //操作數棧中的前兩個變量相加,並將結果壓入操作數棧頂
         9: istore_3         //將相加的得出的結果30從操作數棧存儲到局部變量表第3個位置
        10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_3
        14: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        17: return           // 返回
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
        line 9: 17
}
SourceFile: "Stack.java"

程序計數器

程序計數器(Program Counter Register)是一塊較小的內存空間,是線程私有的,各線程之間獨立存儲,互不影響。它可以看作是當前線程所執行的字節碼的行號指示器。比如如下字節碼內容,在每個字節碼`前面都有一個數字,我們可以認為它就是程序計數器存儲的內容。它主要用來記錄各個線程執行的字節碼的地址,例如,分支、循環、跳轉、異常、線程恢復等都依賴於計數器。

方法區

方法區(Method Area)是可供各條線程共享的運行時內存區域。它存儲了每一個類的結構信息,例如運行時常量池(Runtime Constant Pool)欄位和方法數據、構造函數和普通方法的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法。方法區是 JVM 對內存的「邏輯劃分」,在 JDK1.7 及之前很多開發者都習慣將方法區稱為「永久代」,是因為在 HotSpot 虛擬機中,設計人員使用了永久代來實現了 JVM 規範的方法區。在 JDK1.8 及以後使用了元空間來實現方法區。

本地方法棧

本地方法棧跟 Java 虛擬機棧的功能類似,它服務的對象是 native 方法。你甚至可以認為虛擬機棧和本地方法棧是同一個區域。虛擬機規範無強制規定,各版本虛擬機自由實現 ,HotSpot 直接把本地方法棧和虛擬機棧合二為一 。

運行時常量池

運行時常量池(Runtime Constant Pool)是每一個類或接口的常量池(Constant_Pool)的運行時表示形式,它包括了若干種不同的常量:從編 譯期可知的數值字面量到必須運行期解析後才能獲得的方法或欄位引用。運行時常量池是方法區的一部分。

堆是 JVM 上最大的內存區域,我們創建的幾乎所有的對象,都是在這裡存儲的。我們常說的垃圾回收,操作的對象就是堆。堆空間一般是程序啟動時,就申請了,但是並不一定會全部使用。隨著對象的頻繁創建,堆空間占用的越來越多,就需要不定期的對不再使用的對象進行回收。這個在 Java 中,就叫作 GC(Garbage Collection)。

對於普通對象來說,JVM 會首先在堆上創建對象,然後在其他地方使用的其實是它的引用。比如,把這個引用保存在虛擬機棧的局部變量表中。對於基本數據類型來說(byte、short、int、long、float、double、char),有兩種情況。當你在方法體內聲明了基本數據類型的對象,它就會在棧上直接分配。其他情況,都是在堆上分配。

直接內存

直接內存有一種更加科學的叫法,堆外內存。JVM 在運行時,會從作業系統申請大塊的堆內存,進行數據的存儲;同時還有虛擬機棧、本地方法棧和程序計數器,這塊稱之為棧區。作業系統剩餘的內存也就是堆外內存。

文章主要淺析了JVM的原理解析。作為基礎學習,Android開發路上肯定是要打好基礎的。當然關於JVM知識還有其他深入學習。關注博主,每次都會帶來不一樣的《Android技術分享》。博主也整理了一些Android進階的技術知識,我分享在這裡哦!私信:「手冊」可領取!


【私信『「手冊」』獲取】Android核心技術進階手冊

文末

Android的運行環境都是在ART虛擬機或者是Dalvik虛擬機上,為什麼Android程式設計師需要學習JVM,理解JVM可以幫助我們更好的了解Java內存區域、對象的創建和內存分配、垃圾的回收以及常見的垃圾回收算法等等,然後將其運用到Android開發中,有助於處理app中的內存問題。

關鍵字: