想理解JVM看了這篇文章,就知道了!

2020-12-10 計算機java編程

本次將對jvm有更深入的學習,我們不僅要讓程序能跑起來,而且是可以跑的更快!可以分析解決在生產環境中所遇到的各種「棘手」的問題,比如運行的應用卡住了,日誌不輸出,程序沒有反應,CPU負載突然升高,多線程應用下,如何分配線程數量等。

2|0JVM介紹

2|1什麼是JVM

作為java工程師,對於jvm肯定不陌生。JVM是Java Virtual Machine的縮寫,通俗來說也就是運行java代碼的容器。當項目啟動時,會根據jvm相關配置參數,在計算機的內存中開啟一片空間用於運行JVM。之後java相關代碼就會被加載進JVM中運行。

百度百科對JVM的定義:

2|2為什麼要了解JVM

對於Java程式設計師來說,在虛擬機自動內存管理機制的幫助下,不再需要為每一個new操作去寫配對的delete/free代碼,不容易出現內存洩漏和內存溢出問題,看起來由虛擬機管理內存一切都很美好。不過,也正是因為Java程式設計師把控制內存的權力交給了Java虛擬機,一旦出現內存洩漏和溢出方面的問題,如果不了解虛擬機是怎樣使用內存的,那排查錯誤、修正問題將會成為一項異常艱難的工作。

3|0JVM內存模型

3|1JVM整體架構

由上面的圖可以看出,JVM虛擬機中主要是由三部分構成,分別是類加載子系統、運行時數據區、執行引擎。類加載子系統Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。運行時數據區 Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。這些區域有各自的用途,以及創建和銷毀的時間,有的區域隨著虛擬機進程的啟動而一直存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。執行引擎 執行引擎用於執行JVM字節碼指令,主要有兩種方式,分別是解釋執行和編譯執行,區別在於,解釋執行是在執行時翻譯成虛擬機指令執行,而編譯執行是在執行之前先進行編譯再執行。解釋執行啟動快,執行效率低。編譯執行,啟動慢,執行效率高。垃圾回收器就是自動管理運行數據區的內存,將無用的內存佔用進行清除,釋放內存資源。本地方法庫、本地庫接口 在jdk的底層中,有一些實現是需要調用本地方法完成的(使用c或c++寫的方法),就是通過本地庫接口調用完成的。比如:System.currentTimeMillis()方法。

3|2運行時數據區

運行時數據區是jvm中最為重要的部門。也是我們在調優時需要重點關注的區域,下面我們一起了解下這個部分的具體內容。

根據《Java虛擬機規範》中的規定,在運行時數據區將內存分為方法區(Method Area)、Java堆區(JavaHeap)、Java虛擬機棧(Java Virtual Machine Stack)、程序計數器(Program Counter Register)、本地方法棧(Native Method Stacks)。

3|3程序計數器

程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,它是程序控制流的指示器,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

由於Java虛擬機的多線程是通過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)都只會執行一條線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為「線程私有」的內存。

3|4java虛擬機棧

與程序計數器一樣,Java虛擬機棧也是線程私有的,它的生命周期與線程相同。Java虛擬機棧描述的是Java方法執行的線程內存模型:每個方法被執行的時候,Java虛擬機都會同步創建一個棧幀,用於存儲局部變量表、操作數棧、動態連接、方法出口等信息。每一個方法被調用直至執行完畢的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。

局部變量表

局部變量表是一組變量值的存儲空間,用於存放方法參數和方法內部定義的局部變量。在Class文件中,方法的Code屬性的max_locals數據項中確定了該方法所需分配的局部變量表的最大容量。該表以變量槽(Variable Slot)為最小單位,一個slot可以存放32位以內的數據,比如:boolean、byte、char、short、int、float等數據,如果存儲long、double類型數據,需要佔用2個solt。虛擬機通過索引定位的方式使用局部變量表,索引值的範圍是從0開始至局部變量表最大的變量槽數量。如果訪問的是32位數據類型的變量,索引N就代表了使用第N個變量槽,如果訪問的是64位數據類型的變量,則說明會同時使用第N和N+1兩個變量槽。局部變量表中第0位索引的變量槽默認是用於傳遞方法所屬對象實例的引用,在方法中可以通過關鍵字「this」來訪問到這個隱含的參數。其餘參數則按照參數表順序排列,佔用從1開始的局部變量槽,參數表分配完畢後,再根據方法體內部定義的變量順序和作用域分配其餘的變量槽。操作數棧

操作數棧也常被稱為操作棧,它是一個先進後出棧。操作數棧的最大深度也在編譯的時候被寫入到Code屬性的max_stacks數據項之中。操作數棧的每一個元素都可以是包括long和double在內的任意Java數據類型。32位數據類型所佔的棧容量為1,64位數據類型所佔的棧容量為2。方法剛剛開始執行的時候,這個方法的操作數棧是空的,在方法的執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是出棧和入棧操作。操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,例如iadd指令,不能出現一個long和一個float使用iadd命令相加的情況。動態連接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池裡指向方法的符號引用作為參數。這些符號引用一部分會在類加載階段或者第一次使用的時候就被轉化為直接引用,這種轉化被稱為靜態解析。另外一部分將在每一次運行期間都轉化為直接引用,這部分就稱為動態連接。方法出口

當一個方法開始執行後,只有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者,方法是否有返回值以及返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法的方式稱為「正常調用完成」。另外一種退出方式是在方法執行的過程中遇到了異常,並且這個異常沒有在方法體內得到妥善處理。無論是Java虛擬機內部產生的異常,還是代碼中使用throw字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種退出方法的方式稱為「異常調用完成」。這種方法的返回是不會給它的上層調用者提供任何返回值的。無論採用何種退出方式,在方法退出之後,都必須返回到最初方法被調用時的位置,程序才能繼續執行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層主調方法的執行狀態。方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值(如果有的話)壓入調用者棧幀的操作數棧中,調整PC計數器的值以指向方法調用指令後面的一條指令等。圖解

以 int i = 1; 這樣代碼為例,看看虛擬機棧的執行

3|5本地方法棧

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別只是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的本地(Native)方法服務。

3|6Java堆區

Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,Java世界裡「幾乎」所有的對象實例都在這裡分配內存。需要注意的是,《Java虛擬機規範》並沒有對堆進行細緻的劃分,所以對於堆的講解要基於具體的虛擬機,我們以使用最多的HotSpot虛擬機為例進行講解。 Java堆是垃圾收集器管理的內存區域,因此它也被稱作「GC堆」,這就是我們做JVM調優的重點區域部分。

jdk1.7中堆內存的劃分

Young 年輕區(代)Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中,Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製對象用,在Eden區間變滿的時候,GC就會將存活的對象移到空閒的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。Tenured 年老區Tenured區主要保存生命周期長的對象,一般是一些老的對象,當一些對象在Young複製轉移一定的次數以後,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。Perm 永久區Perm代主要保存class,method,filed對象,這部份的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應用伺服器的時候,有時候會遇到java.lang. OutOfMemoryError : PermGen space 的誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啟動應用伺服器可以解決問題。Virtual區:最大內存和初始內存的差值,就是Virtual區。jdk1.8中堆內存的劃分

由上圖可以看出,jdk1.8的內存模型是由2部分組成,年輕代+ 年老代。年輕代:Eden + 2*Survivor年老代:OldGen在jdk1.8中變化最大的Perm區,用Metaspace(元數據空間)進行了替換。需要特別說明的是:Metaspace所佔用的內存空間不是在虛擬機內部,而是在本地內存空間中,這也是與1.7的永久代最大的區別所在。

空間分配

如果沒有指定堆內存大小,默認初始堆內存為物理內存的1/64,最大不超過物理內存的1/4或1G。注意的是元空間會自動擴容,默認情況下不收限制。

為什麼廢棄1.7中的永久區

官方給出的解釋是:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因為JRockit沒有永久代,不需要配置永久代。

3|7方法區

方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼緩存等數據。《Java虛擬機規範》中把方法區描述為堆的一個邏輯部分,它卻有一個別名叫作「非堆」(Non-Heap),目的是與Java堆區分開來。JDK8之前將HotSpot虛擬機把收集器的分代設計擴展至方法區,所以可以將永久代看做是方法區,JDK8之後廢棄永久代,用元空間來代替。3|8對象的訪問

相關焦點

  • JVM菜鳥進階高手之路十四:分析篇
    題目回顧JVM菜鳥進階高手之路十三,問題現象就是相同的代碼,jvm參數不一樣,表現的現象不一樣。可以去看看我的JVM菜鳥進階高手之路六(JVM每隔一小時執行一次Full GC)、以及JVM菜鳥進階高手之路七(tomcat調優以及tomcat7、8性能對比)圖片就取的這兩篇裡面的。
  • 面經手冊 · 第23篇《JDK、JRE、JVM,是什麼關係?》
    四、總結五、系列推薦一、前言截至到這已經寫了22篇面經手冊,你看了多少?😄其實小傅哥就是借著面經的幌子在講 Java 核心技術,探索這些核心知識點面試的背後到底在問什麼。「記住」,讓懂了就是真的懂,比看水文、背答案要爽的多!嗯,就是有時候燒腦!二、面試題謝飛機,小記!,也不知道咋了,總感覺有些面試攻擊性不大,但侮辱性極強!
  • 別翻了,這篇文章絕對讓你深刻理解java類的加載以及ClassLoader源碼分析
    如果下面這道面試題都做對了,那沒錯了,這篇文章你就不用看了,真的。這篇文章將通過對Java類加載機制的講解,讓各位熟練理解java類的加載機制。首先給各位打個預防針:可能沒有了解過JVM的童鞋可能看的很蒙,感覺全是理論的感覺,不勉強一字一句的「死看」,只要達到一種概念印象就好!等到有一定理解認識之後再回頭看一遍就好很多了,畢竟學習是一種循進漸進的過程,記住沒有捷徑!
  • 想做讀書筆記,卻又不知道該怎麼做,看完這篇文章後你就明白了
    有些人,在讀書的時候,想做讀書筆記,卻又不知道該怎麼做。導致看過的書,沒有印象,那這時候,該怎麼做讀書筆記呢?讀書筆記的作用是,可以幫助我們在讀書的時候,清晰地記錄每一篇文章的邏輯脈絡,可以幫助我們更好地了解和記住這篇文章;讀書筆記可以讓一本書從厚變薄,從一篇文章的開頭至結尾,從書中具體的語言文字變成腦子裡抽象的地圖。
  • JVM之jvisualvm的簡單使用
    JVM之jvisualvm的簡單使用閱讀本篇教程之前,必須先閱讀JVM之jconsole的簡單使用這篇教程。jvisualvm比jconsole稍微好用了那麼一點點,其實就是圖形化界面做的比jconsole好看了一點。還有一點就是jvisualvm支持插件安裝。
  • JVM 面試基礎準備篇(一)
    JVM 面試基礎準備篇(一)1. 計算機原理2.機器語言我們把CPU能夠直接認識的數據指令,稱為機器語言,也就是010101001這種形式不同廠商的 CPU不同 CPU 使用的 CPU 指令集是不一樣的,這就會有不兼容的問題;而且要是直接操作 01 這種形式的,非常麻煩並且容易出錯,硬體資源管理起來也不方便。
  • 想寫拆書稿,先看完這篇文章
    今天,我讓好友把那個視頻給我轉發過來了,視頻中的人說,她兩年前就開始寫拆書稿了,還說了一篇500元,我不知道她說的一篇,是指的一本書,還是一篇文章?從她這樣的說法看來,她根本就沒寫過拆書稿,也不理解拆書稿。什麼是拆書稿?
  • JDK、JRE、JVM,是什麼關係?
    一、前言截至到這已經寫了22篇面經手冊,你看了多少?其實小傅哥就是借著面經的幌子在講 Java 核心技術,探索這些核心知識點面試的背後到底在問什麼。想問一些面試官,是因為大家都在問所以你問,還是你想從這裡問出什麼? 其實可能很多面試官如果不了解這些技術,往往會被求職者的答案擊碎內心,哈哈哈哈哈哈。
  • 來自JVM的一封ClassFile介紹信
    這裡邊有個cp_info這個是存儲什麼呢?這個是一個常量池。這個裡邊存儲了方法名、類名、接口名以及源碼中那些真正的常量。其它的項涉及到這些 名稱都是存一個常量池的一個索引值。也就是一個引用。這會你也許又要問。uX和*_info後面那些類似欄位名稱的東西是什麼意思呢。為了避免與類的欄位和類的實例等概念產生混淆,這裡我們給這些類似欄位的東東起了一個新的名詞叫:項。
  • 想上手Vue2.0,不知道怎麼學效率最高?那就看這篇文章
    前言昨天傳了收集整理的Vue面經文章,發現很多朋友對框架還是很感興趣的。想起來自己之前入坑的時候,無意中找到過一篇文章,文章是一個大佬對學習路線的分享。所以這篇文章把這個影響我很久學習進程的內容貼出來,希望可以給更多的人帶來幫助~先貼一下,之前的vue面經文章。
  • 想寫作卻不知道寫什麼?這篇文章能幫你
    這是堅持寫作的第五篇,昨天還在想,不知道寫什麼好了。我的那點墨水,在前四篇好像都寫完了。我該寫點什麼?我想這應該是剛剛開始寫作的人都會面對的問題。不知道寫什麼。遇到問題總要想辦法解決,於是我就開始分析,我該怎麼辦?首先我看了其他一起寫作的朋友寫的文章。
  • JVM 解剖公園:JNI 臨界區與 GC Locker
    寫在前面「[JVM 解剖公園][1]」是一個持續更新的系列迷你博客,閱讀每篇文章一般需要5到10分鐘。限於篇幅,僅對某個主題按照問題、測試、基準程序、觀察結果深入講解。因此,這裡的數據和討論可以當軼事看,不做寫作風格、句法和語義錯誤、重複或一致性檢查。如果選擇採信文中內容,風險自負。
  • jvm規範之浮點數 - 你知道嗎,有個數字它和自身不相等
    你知道嗎,有個數字它和自身不相等,你知道嗎,有兩個數字他們是相等的,但是1.0除以他們得到的結果卻是大相逕庭。本文將為你揭開這神秘的面紗,並自此開啟 java jvm 之旅。關注我,我接下來將持續更新 java jvm 知識 、數據結構與算法,你若關注,我必不負所望!
  • 這一定是全網寫JVM最好的文章之一—JVM運行時數據區
    過程如下:Java文件->編譯器->字節碼->JVM->機器碼JVM,JRE,JDK的關係首先看一下這個圖,最表象的,就是JDK>JRE>JVM,也就是JDK包含JRE,JRE包含JVM,那麼這三個到底有什麼區別呢?
  • 看了這篇文章,你就知道了
    接下來的幾篇文章,本人會逐一向大家解讀到底什麼是財政,財政到底如何運行。今天,我來解答:財政部門如何把錢一步步撥下去?你的公司又能從中獲得哪些啟發?第一篇文章,我就告訴大家,財政部門沒有直接管理財政資金,大量的財政資金都是在人行和商行存放。估計不少朋友就奇怪了,又不用他們管資金,財政部門天天那麼忙,到底在忙什麼呢?
  • 性能測試沒你想的那麼難,看完這篇文章就懂了
    從聚類的觀點看,其實就是去掉離群點。錯誤率:即錯誤請求數與總請求數之比。隨著壓力增加,有可能出現處理請求處理不過來的情況,這時錯誤數會不斷增加。三者有極大的關聯,任何孤立的數據都不能說明問題。典型的關係是,吞吐量增加時,響應延遲有可能增加,錯誤率也有可能增加。因此,單拿出一個10w的TPS並不能說明問題。
  • 看了這篇文章,搞定視頻下載
    以後公眾號的發文間隔為:每兩天推一次文章,時間為晚上9點30分(太頻繁的推文可能大家也會反感,固定的時間可能不會讓大家錯過文章)。其實我們最看重的就是文章左下角的閱讀量和右下角的『在看』量,有人看文章可能就是對創作者最大的鼓勵吧,感恩理解!
  • 一篇文章搞清楚boolean到底佔幾個字節
    就是那些用爛了的手段    記得是很久很久以前,我印象中我是寫過的.就是學習java,往深處學,就是1.看源碼.2.看class文件. 3.就是javap -v . 經此三板斧, 再沒解決...,可以看到代碼對應的虛擬機指令,很多難以理解的概念,比如說synchronized,i++, 等都可以藉助javap來理解.
  • 當你想放棄的時候,來看這篇文章!
    為什麼想放棄呢?因為看不到進展,不知道自己在進步。而人不管幹什么正經事,都有個慢啟動過程,也就是一開始沒什麼效果。在慢啟動階段,確實看不到進展,也確實很容易放棄。比如,你報了很多寫作課,研究寫作的框架、邏輯,看了很多寫作的書,甚至拆解大V的文章。聽完課、看完書、拆解完文章,你信心滿滿:「學到了,學到了...」但寫出來的文字,只會親手把自己打回原形。
  • 看了這篇文章就知道了
    感謝您能閱讀本篇文章,下面故事開始!正是因為我父母開明的教育方式才讓我喜歡小孩,想生小孩,讓我對生活充滿熱情。我選擇了自己想讀的初中,高中,大學,專業。在這過程中他們出了給我提供指導意見,沒有半點反對。還有一點,在受教育的過程中,他倆是一點也沒有批評過我的學習。哪怕是我高中第一次月考,數學才43分,我很愧疚,但老排也只是笑笑: 才考這麼點分,怕是不能體現出你的水平哦。