1. 程式人生 > >最簡單的JVM記憶體結構圖

最簡單的JVM記憶體結構圖

JVM記憶體結構圖


大家好,好幾天沒有更新了,今天的內容有點多,我們詳細介紹下JVM內部結構圖,還是和之前一樣,案例先行,方便大家理解記憶。

/**
 * @author :jiaolian
 * @date :Created in 2021-03-10 21:28
 * @description:helloworld測試jvm記憶體區域
 * @modified By:
 * 公眾號:叫練
 */
public class HelloWorldTest {

    public static void main(String[] args) {
        //新建HelloWorldTest物件;
        HelloWorldTest helloWorldTest = new HelloWorldTest();
        //新建2個執行緒呼叫sayHello
        for (int i=0; i<2; i++) {
            new Thread(()->helloWorldTest.sayHello("world")).start();
        }
    }

    /**
     * 對某人說hello
     * @param who
     */
    public void sayHello(String who) {
        System.out.println(Thread.currentThread().getName()+"hello!"+who);
    }
} 

 

如上程式碼:在主執行緒中for迴圈新建2個執行緒呼叫sayHello,最後兩個執行緒分別對世界問好!這段程式碼比較好理解,就不貼輸出結果了。我們編寫並運行了這段程式碼,我們主要看看這段程式碼在JVM中是怎麼運作的。

首先,我們編寫一個HelloWorldTest.Java檔案,經過javac編譯會轉化成位元組碼HelloWorldTest.class,為什麼要轉化成位元組碼呢?因為Java虛擬機器能識別!最後由類載入子系統ClassLoader將位元組碼裝載到記憶體。每塊記憶體各有自己的作用,最後由執行引擎來執行位元組碼。下面我們重點介紹下各塊記憶體發揮的作用!

 

方法區


方法區主要裝一些靜態資訊,比如:類元資料,常量池,方法資訊,類變數等。如上程式碼HelloWorldTest.class是類元資料,sayHello,main都是方法資訊等都是放在方法區儲存的。方法區中還需要注意兩點:

  1. 如果方法區太大,超過設定,會報OutOfMemoryError:PermGen space錯誤。gclib工具可以動態生成類測試該錯誤。
  2. 在JDK1.7以前,方法區叫永久代,而1.8之後叫元空間。原因是JDK1.8為了釋放管理壓力,把執行時常量池交給堆去管理。

 


堆中主要存放例項物件。你可以這麼理解,只要看到用關鍵字new的物件,資料都放在堆中。如上程式碼HelloWorldTest helloWorldTest = new HelloWorldTest();helloWorldTest是HelloWorldTest物件的引用,指向new出來的HelloWorldTest物件例項,helloWorldTest引用是放在棧中的,也叫區域性變數(方法內申明的物件型別或普通型別),我們簡單畫圖來表示下堆,棧,方法區關係。當JVM執行了HelloWorldTest helloWorldTest = new HelloWorldTest();這句話,JVM記憶體結構看起來是這樣的。如果指向物件引用消失,物件會被GC回收。

在堆記憶體中,記憶體需要劃分成兩塊區域,新生代和老年代。如下圖所示。

  1. 新生代:在堆記憶體中,新生代又分為三塊,eden(伊甸園建立新生命,對應new物件),from,to,這三塊記憶體區域都屬於新生代,預設比例是8:1:1,每次new物件都會先儲存到eden中,如果eden區域記憶體滿了,會觸發monitor gc回收該區域,還未回收的物件會放入from或者to,from,to記憶體其中一塊是空的,方便物件在記憶體中整理標記,每GC一次,from,to兩塊空間物件每移動一次,還未回收的物件年紀也會增加1,到達一定年紀(預設是15歲),就會進入老年代了。
  2. 老年代:當老年代滿了,會觸發Full GC回收,如果系統太大,Full GC都回收不了,程式會出現類似java.lang.OutOfMemoryError: Java heap space,我們可以通過配置JVM引數:如-Xmx32m 設定最大堆記憶體為32M。

對堆分塊原因是方便JVM自動處理垃圾回收。堆記憶體是GC回收的主要區域。

 

 


棧記憶體空間相對於堆空間比較小,也屬於執行緒私有,棧中主要是一堆疊幀,是先進後出的,理解起來棧幀對應就是一個方法,方法中包含區域性變數,方法引數,還有方法出口,訪問常量指標,和異常資訊表,其中異常資訊表和常量指標資訊我們在方法體中可能看不出來,但通過工具Jclasslib工具類在反編譯class檔案可以體現出來,異常資訊表可以處理當程式執行報錯,會跳轉到具體哪行程式碼執行,JVM中就是通過異常表反饋的。我們還是結合例子和圖來詳細分析下。當程式執行時,JVM中棧可能如下圖呈現狀態。

    

一個執行緒可能對應多個棧幀,棧幀都是從上往下壓入,先進後出,如下圖所示,在方法A中呼叫方法B,在方法B中呼叫C,在方法C中呼叫方法D,主執行緒對應棧幀的壓棧情況,出棧順序是D->C->B->A,最終程式結束。另外還需注意:運算元棧的意思是儲存區域性變數計算的中間結果,比如在方法A中定義int x = 1;在JVM中會將區域性變數入運算元棧用來之後的計算。棧也是有空間大小的,如果棧太大,超過棧深度,會類似報錯,java.lang.OutOfMemoryError: Java stack space,最常見的例子就是遞迴了。你會寫demo測試遞迴例子嗎?

 

 

程式計數器


程式計數器也是執行緒獨享的,多執行緒執行程式依賴於CPU分配時間片執行,畫個簡單的圖,看看多執行緒怎麼利用CPU時間片的。如下圖,執行緒0和執行緒1分配cpu時間片交替執行程式,假設此時執行緒0先獲取到了時間片,時間片用完後CPU會將時間片再分配給執行緒1,執行緒1執行完畢後,此時,時間片又回到執行緒0來執行,那麼問題來了,執行緒0上次執行到哪兒了呢?具體是程式碼的多少行了呢,該行程式碼有沒有執行完畢?此時程式計數器就發揮作用了,程式計數器儲存了執行緒的執行現場,方便下次恢復執行。這也是為什麼程式計數器是執行緒獨享的原因。

 

 

本地方法棧


本地方法棧就不過多介紹了,和棧結構一樣,是一塊獨立的區域,只是對應的是native方法。

 

直接記憶體


直接記憶體獨立於JVM記憶體之外的記憶體,可以直接和NIO介面互動,NIO介面會頻繁操作記憶體,如果放在JVM管理,無疑會增加JVM開銷,所以單獨將這塊提出來,而且直接記憶體操作資料相比較JVM更快,顯而易見提升了程式效能。

 

 

記憶體分配效能優化-逃逸分析


我們之前說過,只要是看到關鍵字new,物件分配肯定在堆上,下面我們來看一個案例。

/**
 * @author :jiaolian
 * @date :Created in 2021-03-10 16:10
 * @description:逃逸分析測試
 * @modified By:
 * 公眾號:叫練
 */
public class EscapeTest {

    //private static Object object;
    public static void alloc() {
        //一個物件相當於16k大小,非逃逸物件
        //object = new Object();
        Object object = new Object();
    }

    public static void main(String[] args) throws InterruptedException {
        //億次記憶體
        long begin=System.currentTimeMillis();
        for (int i=0; i<10000000; i++) {
            alloc();
        }
        long end=System.currentTimeMillis();
        System.out.println("time:"+(end-begin));
    }
}

 

如上程式碼,我們在主函式裡面通過for迴圈1億次來new Object,一個object為16k,大致估算下有GB資料了,此時我們手動配置JVM引數,-XX:+PrintGC -Xmx10M -XX:+DoEscapeAnalysis;設定列印GC資訊,預設最大的堆記憶體是10M。

  1. -XX:+PrintGC。表示控制檯列印GC資訊。
  2. -Xmx10M。設定最大的堆記憶體為10M。
  3. -XX:+DoEscapeAnalysis。 開啟逃逸分析(預設開啟)。

執行程式,列印結果如下圖所示。一共進行了3次GC,你可能有疑問?10M堆記憶體需要容納GB資料衝擊,怎麼也需要N次GC,為什麼只有3次GC?如果設定-XX:-DoEscapeAnalysis關閉逃逸分析,GC可能會出現上千次。執行時間也從3毫秒增至1000毫秒以上。說明了非逃逸物件沒有新建的堆上,而是建在棧上了。這樣做的好處:從程式GC執行次數和執行時間上來看,程式執行效率提高了。

  • 原因分析:

觀察我們上述案例程式碼中alloc()方法,方法中Object object = new Object();object是一個區域性變數,每次新建後到下一次迴圈再新建,上一次新建的物件就會出棧,object引用指向的物件就會失效,失效的物件就會被GC回收了。開啟逃逸分析後,new Object()建立的物件就不在堆上分配空間了,而放到了棧上。這就是JVM通過逃逸分析對記憶體的優化。思考下,如果將private static Object object;註釋放開,object還會是非逃逸物件嗎?

注意:逃逸物件不能在棧上分配空間!

相信到這裡你已經對逃逸分析應該有一個比較清晰的認識了。

 

總結


好了,寫的有點累了,寫的不全同時還有許多需要修正的地方,希望親們加以指正和點評,喜歡的請點贊加關注哦。點關注,不迷路,我是【叫練】公眾號,微訊號【jiaolian123abc】邊叫邊練。

相關推薦

簡單JVM記憶體構圖

JVM記憶體結構圖 大家好,好幾天沒有更新了,今天的內容有點多,我們詳細介紹下JVM內部結構圖,還是和之前一樣,案例先行,方便大家理解記憶。 /** * @author :jiaolian * @date :Created in 2021-03-10 21:28 * @description

一個簡單記憶體池AutoMemory

    C/C++中記憶體管理是個最麻煩的事情,記憶體申請釋放,記憶體洩露,記憶體越界,甚至是記憶體碎片,就會導致程式出Core或者變慢。如何有效的管理記憶體,有很多方法,我認為最簡單的方式是用一個記憶體池來管理記憶體。     談到記憶體池的時候,就有必要說下程式的生命週

簡單例子圖解JVM記憶體分配和回收

一、簡介 JVM採用分代垃圾回收。在JVM的記憶體空間中把堆空間分為年老代和年輕代。將大量(據說是90%以上)建立了沒多久就會消亡的物件儲存在年輕代,而年老代中存放生命週期長久的例項物件。年輕代中又被分為Eden區(聖經中的伊甸園)、和兩個Survivor區。新的物件分配是首先放在Eden區

JVM 記憶體劃分簡單說明

電腦的記憶體條由作業系統管理,JVM需要請求作業系統分配記憶體,JVM對分配的記憶體進行劃分,分成了5個區域。 1PC暫存器(程式計數器):記憶體和CPU之間互動 2本地方法棧:虛擬機器無法實現,呼叫作業系統中的功能。eg:window 複製,剪下...,利用這片記憶體實現。 3方法和資

目錄 1.1. JVM記憶體模型總體架構圖 1 1.2. JAVA堆 2 1.3. 方法區 元空間(Metaspace) 2 1.4. 虛擬機器棧 3 1.5. 本地方法區 4 2. 垃圾回收演算法 4 2

目錄 1.1. JVM記憶體模型總體架構圖 1 1.2. JAVA堆 2 1.3. 方法區 元空間(Metaspace) 2 1.4. 虛擬機器棧 3 1.5. 本地方法區 4 2. 垃圾回收演算法 4 2.1. 標記-清除演算法(Mark-Sweep) 4

020自定義BaseAdapter,然後繫ListView的簡單例子

1 首先在drawable/中匯入7張圖片 2寫一個名為activity_item.xml 檔案 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:

排查記憶體洩漏簡單和直觀的方法

記憶體洩漏無疑會嚴重影響使用者體驗,一些本應該廢棄的資源和物件無法被釋放,導致手機記憶體的浪費,app使用的卡頓,那麼如何排查記憶體洩漏呢? 當然,首先我門有google的官方文件可以參考: 排查記憶體洩漏官方文件 官方文件(二) 大部分部落格的方法

簡單的方法修改JVM調優的執行引數。

前言: Java的專案執行在jvm裡面,預設配置是256/512m的初始化堆記憶體大小。有時候專案比較大的話,就需要配置一下JVM的相關引數,也算是JVM的部分調優了。 配置JVM虛擬機器的引數來進行專案調優,主要有兩個情景。 1、在Linux下配置tomcat,在執行時通過tomcat去載

jvm記憶體模型的簡單理解

jvm記憶體模型 1.方法區和堆是所有執行緒共享的資料區 1)堆:存放物件的例項 2)方法區:存放已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼 3)執行時常量池:用於存放編譯期生成的各種字面量和符號引用 2.程式計數器、虛擬機器棧、本地

這大概是一篇簡單清晰的Java JVM執行流程

一、JVM的體系結構 類裝載系統 1、定位和匯入二進位制class檔案 2、驗證匯入類的正確性 3、為類分配初始化記憶體 4、幫助解析符號引用 執行引擎 執行包在裝載類的方法中的指令,也就是方法 執行區資料 虛擬機器會在整個計算機記憶體中開闢一塊記憶體儲

Spark任務提交 yarn-cluster模式 解決jvm記憶體溢位問題 以及簡單概述jdk7方法區和jdk8元空間

yarn-cluster 提價任務流程 1、提交方式 ./spark-submit --master yarn --deploy-mode cluster --class org.apache.spark.examples.SparkPi ../lib/spark-exampl

一個簡單但實用的檢視JVM記憶體是否存在記憶體溢位以及檢視GC情況的命令

jstat -gcutil pid 2000:2秒鐘列印一次記憶體佔用和GC情況 可以檢視記憶體佔用情況,GC次數及耗時,以及每次GC成果 S0:倖存區1佔用率 S1:倖存區2佔用率 E:Eden區佔用率 O:老年區佔用率 M:元資料區(java8,相當於ja

JVM 記憶體洩露簡單示例

      在文章 JVM如何判斷物件"死"和"活"中,介紹瞭如何判斷物件生存與否,其中說到了根搜尋演算法,是否與GC Roots有通路來判斷物件的生死屬性。那麼,java虛擬機發送記憶體洩露的原因是什麼呢,主要有以下兩個特點:             (1)被分配的物件是

Memcached原始碼分析之記憶體管理篇之item構圖及slab構圖

.Memcached原始碼分析之記憶體管理篇 部落格分類: linuxc  . 使用命令 set(key, value) 向 memcached 插入一條資料, memcached 內部是如何組織資料呢 一 把資料組裝成 item memcached 接受到客戶端的資料後

目前全的機器學習知識構圖(11月1日更新)

機器學習、深度學習、python基礎、數學基礎、學習分類、專案構建流程、最常用的學習框架悉數列出 。 如有不足請留言補充,也可關注我微訊號,留言即可。請大家掃描二維碼關注我的微信公眾號。本公眾號每天

WPF Binding 簡單的用法繫後臺資料!!!

後臺: public class cnm     {         public static string name;         public string Name { get => name; set => name = value; }    

JVM記憶體模型的簡單瞭解

    瞭解JVM的運作對於Java程式設計師來說是一個知根知底的過程,可以幫助程式設計師寫出高效的程式碼,同時對自己程式碼的運轉有個瞭解,本文我們簡單地介紹下JVM的記憶體模型。    首先的,我們說說程式計數器,程式計數器的作用其實類似於傳統處理器中的PC,是正在執行的位

史上詳細JVM,Java記憶體區域講解

本人免費整理了Java高階資料一共30G,需要自己領取; 傳送門:https://mp.weixin.qq.co

簡單的 Java記憶體模型 講解

本部落格系列是學習併發程式設計過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。 併發程式設計系列部落格傳送門 前言 在網上看了很多文章,也看了好幾本書中關於JMM的介紹,我發現JMM確實是Java中比較難以理解的概念。網上很多文章中關於JMM的介紹要麼是照搬了

ionic 簡單的路由形式,頭部固定,下面tab切換-------一個簡單的單頁切換起飛了

top log cnblogs .cn inset badge left plus set <ion-header-bar class="bar-dark" align-title="left"> <h1 class="title" >微信 &l