JVM模型及內存溢出
一、JVM截圖及概念
圖1:JVM虛擬機運行時數據區域概念模型
1、程序計數器:內存空間中的一塊小區域,作為當前線程所執行的字節碼的行號指示器,註:如果是native方法,計數器為空
2、虛擬機棧:線程私有,生命周期與線程相同,虛擬機棧描述的是Java方法執行的內存模型:創建棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息
3、本地方法棧:和虛擬機棧功能類似,虛擬機使用本地Native方法服務
4、Java堆:線程共享,用於存放對象,是GC的主要管理區域
5、方法區:線程共享,用於存儲已被虛擬機加載的類信息,變量,靜態變量,即時編譯器編譯後的代碼等數據
6、運行時常量池:編譯期生成的各種字面量和符號引用
7、直接內存:直接內存不屬於虛擬機運行時數據區的一部分,但是這部分內存也會被頻繁的使用,而且也會導致OutOfMemoryError的異常
在JDK1.4中引入的NIO類中,有一種基於通道和緩存區的I/O方式,可以使用native函數直接調用堆外內存,然後使用Java堆中的DirectByteBuffer對象來引用操作,可以提高JVM的性能
二、JVM中對象的創建
當JVM解析,遇到new指令時,JVM首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個類是否被加載、解析和初始化過。
如果沒有,那必須先執行相應的類加載過程(加載,驗證,準備,解析,初始化),在類加載檢查通過後,JVM會對新生對象在Java堆中分配內存,內存的大小在類加載完成後才能夠完全確定。Java內存的分配方式有:指針碰撞
對象在內存中的布局可以分為3塊區域:對象頭(運行時數據【哈希碼,GC分代年齡,鎖狀態標誌,線程持有的鎖,偏向線程ID,偏向時間戳】,類型指針【指向類元數據指針】)、實例數據和填充
三、JVM中對象的訪問
在JVM中對象的訪問主要使用:句柄和指針
圖2:使用句柄訪問對象
圖3:使用指針訪問對象
兩種對象訪問方式各有優勢,使用句柄來訪問的最大好處是reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference本身不需要修改。使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷。HotSpot虛擬機使用的是直接指針訪問的方式。句柄來訪問的情況也十分常見。
四、Java中出現OutOfMemoryError的情況
在圖1的JVM虛擬機運行時數據區域概念模型中存在程序計數器、虛擬機棧、本地方法棧、Java堆、方法區、運行時常量池、直接內存,除了程序計數器,其他的部分如果操作不當都會產生運行時內存溢出的問題,下面將對虛擬機棧、本地方法棧、Java堆、方法區、運行時常量池、直接內存的溢出情況作闡述:
1、Java堆溢出
一般創建的對象在堆中容納不下就會溢出,當拋出內存溢出問題時,需要進一步確定到底是內存泄露還是內存溢出,如果是內存泄露,那麽進一步通過工具查看泄露對象到GC Roots的引用鏈,找到泄露代碼的位置;如果是內存溢出,那麽可以在物體機器中調大堆參數(-Xmx和-Xms),也需要檢查代碼中是否存在某些對象生命期過長、持有狀態時間過程的情況,嘗試減少程序運行期的內存消耗
2、虛擬機棧和本地方法棧溢出
虛擬機棧和本地方法棧溢出原因可能有以下兩種:StackOverflowError異常和OutOfMemory異常
註:在單線程條件下,無論是請求的棧深度大於虛擬機所允許的最大深度,還是內存溢出,虛擬機都會拋出StackOverflowError異常,多線程會出現內存溢出異常
故:在多線程導致內存溢出,在不能減少線程數量和更換虛擬機的情況下,只能通過減少最大堆和減少棧容量來換取更多的線程
3、方法區和運行時常量池溢出
方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述,所以在產生大量動態類時,方法區就會出現溢出。例如CGLib字節碼增強和動態語言以外,還有大量JSP或者動態產生JSP文件的應用和基於OSGI的應用
4、直接內存溢出:略
JVM模型及內存溢出