歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java內存區域

一、概述

  Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為若干不同的數據區域,這些區域都有各自的用途以及創建和銷毀的時間。Java虛擬機所管理的內存將會包括以下幾個運行時數據區域,如下圖所示:

  下面就每一個區域進行闡述。

二、運行時數據區域

程序計數器

  程序計數器,可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裡,字節碼解釋器工作就是通過改變程序計數器的值來選擇下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都要依賴這個計數器來完成。

  多線程中,為了讓線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間互不影響、獨立存儲,因此這塊內存是線程私有的。

  當線程正在執行的是一個Java方法,這個計數器記錄的是在正在執行的虛擬機字節碼指令的地址;當執行的是Native方法,這個計數器值為空。

  此內存區域是唯一一個沒有規定任何OutOfMemoryError情況的區域。

Java虛擬機棧

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

  局部變量表中存放了編譯器可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型(指向了一條字節碼指令的地址)。

  如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。

本地方法棧

  本地方法棧與虛擬機的作用相似,不同之處在於虛擬機棧為虛擬機執行的Java方法服務,而本地方法棧則為虛擬機使用到的Native方法服務。有的虛擬機直接把本地方法棧和虛擬機棧合二為一。

  會拋出stackOverflowError和OutOfMemoryError異常。

Java堆

  Java堆是所有線程共享的一塊內存區域,在虛擬機啟動時創建,此內存區域的唯一目的就是存放對象實例。

  Java堆是垃圾收集器管理的主要區域。由於現在收集器基本采用分代回收算法,所以Java堆還可細分為:新生代和老年代。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(TLAB)。

  Java堆可以處於物理上不連續的內存空間,只要邏輯上連續的即可。在實現上,既可以實現固定大小的,也可以是擴展的。

  如果堆中沒有內存完成實例分配,並且堆也無法完成擴展時,將會拋出OutOfMemoryError異常。

方法區

  方法區是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

  相對而言,垃圾收集行為在這個區域比較少出現,但並非數據進了方法區就永久的存在了,這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,

  當方法區無法滿足內存分配需要時,將拋出OutOfMemoryError異常。

  運行時常量池:

  是方法區的一部分,它用於存放編譯期生成的各種字面量和符號引用。

直接內存

  直接內存不是虛擬機運行時數據區的一部分,在NIO類中引入一種基於通道與緩沖區的IO方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。

  直接內存的分配不會受到Java堆大小的限制,但是會受到本機內存大小的限制,所有也可能會拋OutOfMemoryError異常。

三、對象的創建、布局和訪問過程

對象的創建

  創建一個對象通常是需要new關鍵字,當虛擬機遇到一條new指令時,首先檢查這個指令的參數是否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果那麼執行相應的類加載過程。

  類加載檢查通過後,虛擬機將為新生對象分配內存。為對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來。分配的方式有兩種:一種叫指針碰撞,假設Java堆中內存是絕對規整的,用過的和空閒的內存各在一邊,中間放著一個指針作為分界點的指示器,分配內存就是把那個指針向空閒空間的那邊挪動一段與對象大小相等的距離。另一種叫空閒列表:如果Java堆中的內存不是規整的,虛擬機就需要維護一個列表,記錄哪個內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。采用哪種分配方式是由Java堆是否規整決定的,而Java堆是否規整是由所采用的垃圾收集器是否帶有壓縮整理功能決定的。另外一個需要考慮的問題就是對象創建時的線程安全問題,有兩種解決方案:一是對分配內存空間的動作進行同步處理;另一種是吧內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存(TLAB),哪個線程要分配內存就在哪個線程的TLAB上分配,只有TLAB用完並分配新的TLAB時才需要同步鎖定。

  內存分配完成後,虛擬機需要將分配到的內存空間初始化為零值。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就可以直接使用。

  接下來虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息等,這些信息存放在對象的對象頭中。

  上面的工作都完成以後,從虛擬機的角度來看一個新的對象已經產生了。但是從Java程序的角度,還需要執行init方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象才算完全產生出來。

 對象的內存布局

  在HotSpot虛擬機中,對象在內存中存儲的布局可分為三個部分:對象頭、實例數據和對齊填充。

  對象頭包括兩個部分:第一部分用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、線程所持有的鎖等。官方稱之為“Mark Word”。第二個部分為是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

  實例數據是對象真正存儲的有效信息,也是程序代碼中所定義的各種類型的字段內容。

  對齊填充並不是必然存在的,僅僅起著占位符的作用。、Hotpot VM要求對象起始地址必須是8字節的整數倍,對象頭部分正好是8字節的倍數,所以當實例數據部分沒有對齊時,需要通過對齊填充來對齊。

對象的訪問定位

  Java程序通過棧上的reference數據來操作堆上的具體對象。主要的訪問方式有使用句柄和直接指針兩種:

  句柄:Java堆將會劃出一塊內存來作為句柄池,引用中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。如圖所示:

  直接指針:Java堆對象的布局要考慮如何放置訪問類型數據的相關信息,引用中存儲的就是對象地址。如圖所示:

   兩個方式各有優點,使用句柄最大的好處是引用中存儲的是穩定的句柄地址,對象被移動時只會改變句柄中實例的地址,引用不需要修改、使用直接指針訪問的好處是速度更快,它節省了一次指針定位的時間開銷。

Copyright © Linux教程網 All Rights Reserved