java記憶體分配與溢出

来源:https://www.cnblogs.com/jsnhdream/archive/2018/11/03/9894125.html
-Advertisement-
Play Games

Java程式而言,Java虛擬機有自動記憶體管理機制,不需要開發人員去手動釋放內空間,也不容易出現記憶體泄漏和溢出的問題,一切看起來都很完美。一旦出現記憶體泄漏和溢出方面的問題,如果不瞭解Java虛擬機是怎麼樣使用記憶體的,那麼排查起來將困難。以往對記憶體的理解僅僅停留在棧、堆這兩個部分,其實Java虛擬機的 ...


  Java程式而言,Java虛擬機有自動記憶體管理機制,不需要開發人員去手動釋放內空間,也不容易出現記憶體泄漏和溢出的問題,一切看起來都很完美。一旦出現記憶體泄漏和溢出方面的問題,如果不瞭解Java虛擬機是怎麼樣使用記憶體的,那麼排查起來將困難。以往對記憶體的理解僅僅停留在棧、堆這兩個部分,其實Java虛擬機的還有其他分區遠比這複雜。接下來將介紹Java虛擬機主要的幾個區域及其作用、記憶體溢出。

  java虛擬機在執行Java程式時會把其管理的記憶體劃分為若幹個不同的數據區域,這些區域都有各自的用途、創建和銷毀時間。線程共用區域的數據區域隨著虛擬機啟動而存在,線程隔離的數據區域依賴線程的啟動而創建、線程結束而銷毀。

Java虛擬機運行時數據區

程式計數器

  程式計數器是一塊較小的記憶體空間,它可以看成是當前線程執行的位元組碼的行號指示器。其實程式計數器就是一個寄存器用來存放當前正在被執行的指令,也可以存放下一個要被執行的指令。

  在虛擬機的概念模型中,位元組碼解釋器工作時就是通過改變這個計數器的值來選擇下一條需要執行的位元組碼指令,由於Java虛擬機的多線程是通過線程輪流切換並分配處理執行時間的方式實現的,在任何一個確定的時刻,一個處理器(一個內核)都只會執行一條線程中的指令。因此,為了線程切換後還能恢復到正確的執行位置,每條線程都需要擁有一個獨立的程式計數器,各條線程之間計數器互不影響,獨立存儲,所以這部份記憶體區域我們稱之為線程私有記憶體,即線程隔離。

Java虛擬機棧

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

  局部變數表存放了編譯期可知的各種基本數據類型(boolean-1、byte-1、char-2、short-2、int-4、float-4、long-8、double-8),對象引用(reference類型,可能是一個對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向一條位元組碼指令的地址)。局部變數表所需要的記憶體空間在編譯時期完成分配。進入一個方法時,這個方法需要在幀中分配多大的局部變數空間是完全確定的,在方法運行期間不會改變局部變數表的大小。

本地方法棧

  本地方法棧是與虛擬機棧所發揮的作用是非常相似的,他們之間的區別就是虛擬機棧為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。虛擬機規範中對本地方法棧中方法所使用的語音、使用方式以及數據結構 都沒有強制規定,因此具體的虛擬機可以自由地實現它。甚至在有的虛擬機中直接將虛擬機棧和本地方法棧合併為一個。和虛擬機棧一樣,本地方法棧區也會拋出StackOverflowError和OutOfMemory異常。

Java 堆

  對應大多數應用來說,Java堆是Java虛擬機所管理的記憶體中最大的一塊。Java堆是被所有線程共用的一塊記憶體區域,在虛擬機啟動時候創建。此記憶體區域的唯一目的就是存放對象實例,幾乎所有的記憶體實例都在這裡分配記憶體。Java虛擬機規範中的描述是:所有的對象實例以及數組都要在堆上分配,但隨著JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化 ,所喲的對象都分配在堆上也漸漸變得不是那麼“絕對”了。

  Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱為GC堆,現在收集器基本採用分代收集演算法,所以Java堆中還可以細分為:新生代和老年代。根據Java虛擬機規範的規定,Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可,就像我們的磁碟空間一樣。在實現時,可以固定大小,也可是可拓展的,主流的虛擬機都是按照可拓展來實現的(通過-Xmx和-Xms來控制)。如果在堆中沒有記憶體完成實例分配,並且堆也無法繼續拓展時,將會拋出OutOfMemortError異常。

方法區

  方法區與Java堆一樣,是各個線程共用的記憶體區域,它用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯後的代碼等數據。雖然Java虛擬機將其描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆)。目的是與Java堆區分開來。

  Java虛擬機規範對方法區的限制非常松,除了和Java堆一樣,不需要連續的記憶體和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集在這個區域是比較少出現的,這個區域記憶體回收的主要目標是針對常量池的回收和對類型的卸載。根據Java規範的規定,當方法區無法滿足記憶體分配需要時,將拋出OutOfMemoryError異常。

運行時常量池

  運行常量池是方法區的一部分。class文件中除了有類的版本、欄位、方法、介面等描述信息外,還有一項信息是常量池,用於存放編譯時期生成的各種字面量和符號引用,這部份內容將在類載入後進入方法區的運行時常量池中存放。

  運行時常量池具備動態性,Java語音並不要求常量一定只有編譯期才能產生,也就是並非預置入class文件中常量池的內容才能進入方法區運行時常量池,運行時期也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。既然運行時常量池是方法區的一部分,自然受到方法區記憶體的限制,當常量池無法再申請到記憶體時會拋出OutOfMemoryError異常。

 

直接記憶體

  由於直接記憶體(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導致記憶體溢出異常(OutOfMemoryError)出現,所以也放到這部分進行介紹。顯然,本機直接記憶體的分配不會受到Java堆大小的限制。但是肯定還是會受到本機總記憶體大小以及處理器定址空間的限制。管理員在配置虛擬機參數時,會根據實際記憶體設置-Xmx等參數信息,但經常忽略直接記憶體,使得各個記憶體區域總和大於物理記憶體限制(包括物理的和操作系統級的限制),從而導致動態拓展時出現OutOfMemoryError異常。

 

對象的創建方式

  虛擬機遇到一條new指令時,首先去檢查這個指令的參數能否在常量池中定位到一個類的符合引用。並且檢查這個符號引用代表的類是否已經被載入、解析和初始化過。如果沒有,那就先執行載入過程。在類載入完成後,虛擬機將為新生對象分配記憶體。對象所需要的記憶體大小在類載入完成之後便可完全確定,為對象分配空間的任務等同於把一塊確定大小的記憶體從Java堆中劃分出來。

  為對象分配記憶體空間有兩種方式:

  指針碰撞:假設Java堆中記憶體是規整的,所有用過的記憶體都放在一邊,空閑的記憶體放在另一邊,中間放著一個指針作為分界點的指示器,那分配記憶體就是將指針往空間空間挪動一段與對象大小相等的距離,這種分配記憶體的方式就被稱為指針碰撞;

  空閑列表:如果Java堆中的記憶體並不是規整的,已經使用的記憶體和空閑記憶體相互交錯,那就沒有辦法簡單地使用指針碰撞的方法進行記憶體分配了。虛擬機此時必須維護一個列表用來記錄哪些記憶體塊是可用的,在分配的時候從列表中找到一塊足夠大的空間為分配給對象實例,並且更新列表上的記錄,這種分配方式就被稱為空閑列表。   選擇哪一種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。

 對象的記憶體佈局

  HotSpot虛擬機中,對象在記憶體中存儲的佈局分為3塊區域:對象頭、實例數據、對齊填充。

  對象頭由兩部分信息組成,第一部分用於存儲對象自身運行時的數據,如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例,並不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息並不一定要經過對象本身。另外,如果對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中卻無法確定數組的大小。

  實例數據是對象真正存儲的有效信息,也是在程式代碼中所定義的各種類型的欄位內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。這部分的存儲順序會受到虛擬機分配策略參數和欄位在Java源碼中定義順序的影響。

    對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由於HotSpot VM的自動記憶體管理系統要求對象起始地址必須是8個位元組的整數倍,換句話說,就是對象對象的大小必須是8位元組的整數倍。而對象頭部分正好是8位元組的整數倍,因此,當對象實例數據部分沒有對齊時,就需要通過補齊填充來補全。

對象的訪問定位

  建立對象是為了使用對象,我們的Java程式需要通過棧上的reference數據來操作堆上的具體對象。由於reference類型在Java虛擬機規範中只規定了一個指向對象的引用,並沒有定義這個引用應該通過何種方式去定位、訪問堆中的對象的具體位置,所以對象訪問方法也是取決於虛擬機的實現而決定的。目前主流的訪問方式有使用句柄和直接指針兩種。

  如果使用句柄的話,那麼Java堆中將會劃分一塊記憶體來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中存儲的就是對象實例數據與類型數據具體地址信息。優點:reference存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference本身不需要修改。缺點:增加了一次指針定位的時間開銷。

 

通過句柄訪問對象

  如果使用直接指針訪問方式最大的好處就是速度更快,它節省了一次指針定位的時間開銷,由於對象的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。缺點:在對象被移動時reference本身需要被修改。

通過直接指針訪問對象

記憶體溢出

  堆溢出

  Java堆唯一的作用就是存儲對象實例,只要保證不斷創建對象並且對象不被回收,那麼對象數量達到最大堆容量限制後就會產生記憶體溢出異常了。

  虛擬機棧和本地方法棧溢出

  Java虛擬機規範中描述瞭如果線程請求的棧深度太深(換句話說方法調用的深度太深),就會產生棧溢出了。那麼,我們只要寫一個無限調用自己的方法,自然就會出現方法調用的深度太深的場景了。

  如果線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StackOverflowError異常;

  如果虛擬機在擴展棧時無法申請到足夠的記憶體空間,則拋出OutOfMemoryError異常。

這裡把異常分為兩種情況,看似較為嚴謹,但卻存在著一些互相重疊的地方:當棧空間無法繼續分配時,到底是已使用的棧空間太大,還是記憶體太小,其本質上都只是對同一件事情的兩種描述而已。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JavaScript: 知識點回顧篇(七):js中的全局函數 ...
  • JavaScript:使用三種方法生成重覆的字元串,1.遞歸,三元表達式。2.數組的join()。3.ES6 repeat(). ...
  • 《大型網站技術架構:核心原理與案例分析》作者是擁有核心技術部門的一線工作經驗,直接體驗了大型網站構建與發展過程中的種種生與死,蛻與變,見證了一個網站架構從幼稚走向成熟穩定的歷程。 沒有晦澀難懂的術語,沒有詰屈聱牙的文句,沒有故弄玄虛的觀點…… 明明白白的語句,清清楚楚的文法,乾凈利落的建議——讓讀者 ...
  • 依賴註入與控制反轉 依賴註入與控制反轉是老生常談的問題。一般面試也會面試到這種問題。網上很多很多這方面的資料,搜索出來一大堆。下麵我們大話一下這些個定義。 DI依賴註入 依賴註入既依賴,又註入。依賴的是容器,註入的也是容器,把你的對象放入容器,並且依賴於容器。 IOC控制反轉 控制反轉,意思是對象的 ...
  • python3的下載與安裝 1、首先,從Python官方網站:http://python.org/getit/ ,下載Windows的安裝包 ython官網有幾個下載文件,有什麼區別?Python 3.6.0a1 - 2016-05-17Download Windows x86 web-based ...
  • views視圖函數屬於MTV中邏輯處理的部分視圖函數包含著兩個對象,HttpRequest對象和HttpResponse對象 一.HttpRequest對象 HttpRequest對象在Django中會預設傳到views函數中作為第一個參數 HttpRequest的屬性: 屬性說明 path 請求頁 ...
  • Part5:面向對象入門 @[toc] Example01:成員變數的初始化值 運行結果: 代碼實現: public class Example01 { //聲明變數 private byte b; private int i; private short s; private long l; pr ...
  • 註冊登錄 需求: 1.對賬號密碼的長度進行限制並不允許出現特殊字元 2.把賬號密碼儲存進文件中 3.密碼最多輸入錯誤三次 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...