JVM相關概念和異常類型

轉載請註明出處 http://www.paraller.com
原文排版地址 點擊獲取更好閱讀體驗java

概述

  • 基本類型、對象的引用在棧內存中操做,
  • 對象類型在堆內存中操做,
  • 特色:棧容量小,速度快;堆是容量大,速度慢

內存區域分爲六塊(jvm所管理的內存)

方法區 & 永久代 (method area & PermGen ):
  • 線程共享,用於儲存已被虛擬機加載的類信息,常量,靜態變量,及時編譯期編譯後的代碼等數據
  • 規範中將其描述爲堆的一個邏輯部分,英文名NOn-heap ,與java heap區分開來
  • 不規範別名(permanent gerneration)"永久代",除hotspot虛擬機外沒有這個概念
  • 和java heap同樣不須要連續的內存及能夠選擇固定大小或擴展,還能夠選擇不進行垃圾收集
  • 但垃圾收集效果難以使人滿意:針對常量池的回收以及類的卸載(十分苛刻)web

堆(Heap):
  • jvm管理的內存最大的一塊,被全部線程共享,啓動時建立
  • 目的:存放對象實例,全部實例對象和數組在這裏分配內存
  • 隨着JIT編譯期的發展和逃逸分析技術的成熟.全部對象都分配在堆上已經不是那麼"絕對"了,(不懂)
  • 該區域是垃圾收集器的主要管理區域,因此也稱爲GC堆
  • 既能夠是固定大小,也能夠是擴展的,通常都是擴展(-Xms -Xmx)
  • heap可分爲young heap & old heap
  • Young區保存絕大多數剛實例化的對象,當該區被填滿時,觸發局部GC,局部GC會將Young+區清空,仍被引用的對象將被移到Old區。
  • 當Old區再被塞滿,就會觸發FullGC,回收最後能回收的空間算法

虛擬機棧(VM stack):
  • 線程私有;爲執行java方法服務,java方法執行的內存模型,每一個方法被執行時候同時建立一個Stack frame,
  • 用於儲存局部變量表,操做數棧 ; 動態連接,方法出口等信息,方法被執行完結對應入棧出棧
  • 局部變量表:用於儲存基本數據類型,對象引用(不等同於對象自己,多是指向對象的引用指針)reference和returnAddress類型
  • 64位的long和double會佔據2個局部變量空間,其他數據類型只佔用一個
  • 局部變量表的內存空間在編譯期就已經分配完成sql

本地方法棧(native method stack):
  • 發揮的做用與jvm stack相同,可是爲虛擬機使用到的Native方法服務,hotspot虛擬機將兩個合而爲一數據庫

程序計數器(program counter register):
  • 當前線程 所執行的字節碼的 行號指示器,線程私有內存,互不影響,獨立儲存
運行時常量池:
  • 是方法區 & 永久代的一部分;具有動態性,不要求常量必定只有編譯器才能產生

對象的訪問定位

java程序經過棧上的reference數據操做 堆上的 具體對象windows

虛擬機棧中reference數據稱爲引用
reference類型的數據中儲存的數值表明的是另一塊內存的起始地址,就稱這塊內存是表明着引用
除了 被引用和不被引用兩種狀態 ;JDK1.2以後引入了四種新的概念:強引用、軟引用、弱引用、虛引用數組

主流的方式有兩種,直接指針和使用句柄服務器

  • 直接指針: java堆中劃出一塊內存做爲句柄池,reference保存句柄地址;句柄中保存了對象實例數據與類型數據各自的具體地址信息。優勢是reference中儲存的是穩定的句柄地址
  • 訪問句柄: reference中保存着對象的具體地址。優勢是快速。多線程

方法區 & 永久代:

主要存放類信息,經過CGLib動態構造類會形成OOM異常,好比jsp的構造oracle

判斷對象死活

引用計數法

每當有一個地方引用+1 ,計數器爲0說明沒有對象引用它 , 存在兩個無用對象相互引用的狀況

可達性分析算法

主流方式;以GC Roots對象爲起點,根據是否有引用鏈可達判斷;哪些能夠稱爲引用鏈:

  • 虛擬機棧中引用的對象
  • 本地方法棧中 引用的對象
  • 方法區 & 永久代中 類靜態屬性引用的對象
  • 方法區 & 永久代中 常量引用的對象

PS:對象如何在被標記回收的時候逃脫: 覆寫finalize方法,將this賦值給某個類變量或者對象的成員變量

垃圾收集算法

  • 標記-清除算法:標記出全部須要回收的對象,而後統一回收 ; 缺點:效率和碎片空間多
  • 複製算法:將可用內存分爲大小相等的兩塊,一塊內存用完了以後,將存活的對象複製到另外一塊上,對原來的內存所有清理 ; 優勢:高效,少空間碎片 ; 缺點:內存縮小了一半
  • 標記-整理算法:標記存活的對象往一端移動,而後將邊界外的內存回收
  • 分代-收集算法:商業虛擬機主流算法,將java堆分紅新生代和老年代,而後用不一樣的收集算法,新生代用複製算法,老年代標記整理就好

異常時定位 哪些區域異常

內存泄露 (Memory Leaking):

指某些對象再也不被應用程序使用,而垃圾收集器(Garbage Collector)卻沒能識別它們是「再也不使用的」。若是那些不使用的對象佔用堆(heap)空間足夠大,使得應用程序沒法知足下一次內存分配需求,就會致使OutOfMemoryError錯誤

內存溢出 (OutOfMemory):

內存不夠用了,緣由有幾種,內存泄露只是其中一種。

堆區域異常

通常是 OutOfMemoryError :先肯定是內存溢出仍是內存泄露(垃圾回收處理機制)
內存泄露:經過工具查看泄露對象到GC Roots的引用鏈,找到沒法回收的緣由
內存溢出:調到物理內存,判斷是否對象生命週期過長

StackOverflowError:

線程請求的棧深度大於虛擬機棧所容許的最大深度;單線程的時候,不管是棧幀太大仍是虛擬機棧容量過小,當內存沒法分配的時候,虛擬機拋出的都是這個異常

OutOfMemoryError: PermGen space:
  • JVM須要加載一個新類的定義,而永久代(PermGen)的空間不足——已經有太多的類存儲在那裏了。一個可能的緣由是:你的應用程序或服務器使用了太多的類,當前的永久代(PermGen)大小沒法知足需求。
  • 多是內存泄漏(堆沒法分配空間)
  • 虛擬機在擴展棧時沒法申請到足夠的空間
  • 多線程致使的內存溢出,在不能減小線程數和更換64位虛擬機的狀況下(windows對每一個進程分配的內存是有限制的,32位的爲2G),只能經過減少最大堆和減少棧容量來換取更多的線程
Permanent Generation Leak - 永久代(PermGen)到底有沒有可能會內存泄漏?

它保存了Java類定義,而且這些類定義是不會變成「無用」的,是嗎?事實上,它們是能夠變成「無用」的。以一個部署到應用程序服務器的Java web程序來講,當該應用程序被卸載的時候,你的EAR/WAR包中的全部類都將變得無用。只要應用程序服務器還活着,JVM將繼續運行,可是一大堆的類定義將再也不使用,理應將它們從永久代(PermGen)中移除。若是不移除的話,咱們在永久代(PermGen)區域就會有內存泄漏。

Leaking Threads - 線程內存泄漏

類加載器(classloader)泄漏的一個可能的場景就是經過運行的線程(而內存泄漏)。當你的程序,或者你的程序使用的第三方庫(我常常遇到這種狀況,好比Quartz)開啓了一些長時間運行的線程。一個例子:一個用於週期性執行代碼的計時器(timer)線程。

若是不解決該線程預期的生命週期問題,咱們直接會遇到麻煩。當你程序的任何一部分啓動一個線程的時候,你要確保它不會比程序活得還要久。在典型的狀況下,開發者要麼不知道本身有責任處理好這個問題,或者忘了寫清理(clean-up)的代碼。

不然,若是應用程序卸載後,線程還在繼續運行,它一般將維持一個到web應用程序的classloader的引用,即咱們所說的contextclassloader。這也就意味着,全部卸載掉的應用程序仍然保存在內存中。怎麼解決?若是是你的程序開啓了新線程,那麼你就應該在卸載的時候關閉它們,這能夠經過使用一個servlet context listener來實現。若是是第三方庫開啓的新線程,你應該搜索它的關閉線程的接口,若是沒有的話,就上報一個bug吧。

Leaking Drivers - 驅動內存泄漏

另外一個典型的內存泄漏緣由是由數據庫驅動形成的。咱們在和Plumbr一塊兒發佈的demo程序中遇到了這種內存泄漏狀況。它是一個與Sprint MVC一塊兒發佈的、代碼稍微修改過的Pet Clinic程序。讓咱們關注一下當這個應用程序部署到服務器上的時候,發生了什麼:

  • The server creates a new instance of java.lang.Classloader and starts to load the application’s classes using it.
  • 服務器建立一個java.lang.Classloader的新實例,並用它來加載程序的類。
  • Since the PetClinic uses a HSQL database, it loads the corresponding JDBC driver,org.hsqldb.jdbcDriver
  • 因爲PetClinic使用了HSQL數據庫,因此它會加載相應的JDBC驅動,即org.hsqldb.jdbcDriver
  • This class, being a good-mannered JDBC driver, registers itself with java.sql.DriverManager during initialization, as required per JDBC specification. This registration includes storing inside a static field of DriverManager a reference to an instance of org.hsqldb.jdbcDriver.
  • 這個JDBC驅動類會在初始化的時候將它註冊到java.sql.DriverManager中(正如JDBC規範所要求的那樣)。這個註冊過程包括了存儲org.hsqldb.jdbcDriver的一個實例的引用到DriverManager的一個靜態域中

如今,當從服務器上卸載應用程序的時候,java.sql.DriverManager仍將持有那個引用,不管在HSQLDB庫,或者在Spring framework中,都沒有代碼能夠移除它!正如上面解釋的那樣,一個jdbcDriver對象將持有一個到org.hsqldb.jdbcDriver類的引用,從而持有用於加載應用程序的java.lang.Classloader的一個實例的引用。這個classloader如今仍然引用着應用程序的全部類。在咱們那特殊的demo應用程序中,在程序啓動的時候,須要加載將近2000個類,佔用約10MB永久代(PermGen)內存。這就意味着須要5~10次從新部署,纔會將默認大小的永久代(PermGen)塞滿,而後就會觸發java.lang.OutOfMemoryError: PermGen space錯誤並崩潰。

怎樣解決此問題?一個可能的辦法就是寫一個servlet content listener,用於在應用程序關閉的時候,從DriverManager反註冊HSQLDB驅動。這個方法很直接,可是請記住——你須要在使用該驅動的每個應用程序中都這麼寫。

Conclusion

你的應用程序遇到java.lang.OutOfMemoryError: PermGen space錯誤的緣由不少,究其根本緣由,大多數是因爲object或程序的class loader加載的類的引用已經無用了致使的。對此類問題,你須要採起的補救措施都很是類似,即,首先,找出引用在哪裏被持有;其次,給你的web應用程序添加一個關閉的hook,或者在應用程序卸載後移除引用。你要麼經過servlet context listener,要麼經過第三方庫提供的API來實現這一點。

擴展:直接內存:本機直接內存

非jvm運行時數據區;新加入了的NIO類,引入了"Channel"與"Buffer"的I/O方式

經過本地函數庫分配一個堆外內存,而後經過java堆的的對象實例buffer做爲這塊內存的引用進行操做,由於避免了再java heap and native heap之間

進行復制操做,顯著的提升性能,當對於jvm的各個內存總和大於直接內存會報錯

句柄:簡而言之數據的地址須要變更,變更之後就須要有人來記錄管理變更,(就好像戶籍管理同樣),所以系統用句柄來記載數據地址的變動

參考網站

Presenting the Permanent Generation

[[原創](翻譯)什麼是Java的永久代(PermGen)內存泄漏](http://ju.outofmemory.cn/entr...

相關文章
相關標籤/搜索