作一個積極的人java
編碼、改bug、提高本身程序員
我有一個樂園,面向編程,春暖花開!編程
推薦閱讀設計模式
第一季
0、Java的線程安全、單例模式、JVM內存結構等知識梳理數組
二、Java內存管理-初始JVM和JVM啓動流程(二)數據結構
三、Java內存管理-JVM內存模型以及JDK7和JDK8內存模型對比總結(三)架構
七、Java內存管理-掌握自定義類加載器的實現(七)
第一季總結:由淺入深JAVA內存管理 Core Story第二季
九、Java內存管理-」一文掌握虛擬機建立對象的祕密」(九)
十、Java內存管理-你真的理解Java中的數據類型嗎(十)
十一、Java內存管理-Stackoverflow問答-Java是傳值仍是傳引用?(十一)
十二、Java內存管理-探索Java中字符串String(十二)
實戰
分享一位老師的人工智能教程。零基礎!通俗易懂!風趣幽默!你們能夠看看是否對本身有幫助,點擊這裏查看【人工智能教程】。接下來進入正文。
勿在流沙築高臺,出來混早晚要還的。
@[toc]上一篇分享了JVM及其啓動流程,今天介紹一下JVM內部的一些區域,以及具體的區域在運行過程當中會發生哪些異內存常! 其實也就對應了內存管理的第一篇中 JVM的第三個階段,程序運行內存溢出。
知識地圖:
Java的內存管理採用[自動內存管理]機制,由於這個自動管理機制,Java程序員就不須要去寫釋放內存的代碼,並且不容易出現內存泄漏問題(比C/C++程序員少一些煩惱)。可是因爲內存的申請和釋放都交給了Java虛擬機,一旦出現內存泄漏和溢出問題時,在不瞭解Java虛擬機內存結構和自動管理機制的狀況下,就很難排查問題的所在。因此若是想要成爲一個優秀的程序員或者進階爲一個牛逼的架構師,掌握Java虛擬機的自動內存管理機制那是必須的。
根據《Java虛擬機規範(Java SE 7版)》的規定,Java虛擬機所管理的內存將會包括如下幾個運行時的數據區域:程序計數器(Program Counter Register)、Java棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap)。
如圖所示:
注:上圖的虛擬機運行時數據區是Java虛擬機規範所規定的區域,不一樣的虛擬機有不一樣的實現。
上面圖片有線程共享和線程隔離的區域,下面在經過一張圖片來進行簡單說明,讓你更加清晰的理解什麼是線程共享和什麼是線程隔離。
經過上面的兩個圖,大概對JVM的內存模型有個初步的認識,下面咱們在看一下具體的每個區域究竟是什麼東東。
程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠是看做當前線程所執行的字節碼的行號指示器。說簡單一點就是一個計數器,當字節碼解釋器工做是可以經過改變這個計數器的值來選取下一條須要執行的字節碼指令。在說明一點,各條線程之間計數器互不影響,獨立存儲,程序計數器器內存區域爲 線程私有 的。
類比彙編語言中的程序計數器:在彙編語言中,程序計數器是指CPU中的寄存器,它保存的是程序當前執行的指令的地址(也能夠說保存下一條指令的所在存儲單元的地址),當CPU須要執行指令時,須要從程序計數器中獲得當前須要執行的指令所在存儲單元的地址,而後根據獲得的地址獲取到指令,在獲得指令以後,程序計數器便自動加1或者根據轉移指針獲得下一條指令的地址,如此循環,直至執行完全部的指令。
在JVM規範中規定,若是線程執行的是非native方法,則程序計數器中保存的是當前須要執行的指令的地址;若是線程執行的是native方法,則程序計數器中的值是undefined。因爲程序計數器中存儲的數據所佔空間的大小不會隨程序的執行而發生改變,所以,此內存區域是惟一一個在JVM規範中沒有規定任何OutOfMemoryError狀況的區域。
本地方法棧和虛擬機棧所發揮的做用是很類似的,它們之間的區別不過是 虛擬機棧爲虛擬機執行Java方法(字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。Sun HotSpot 直接就把本地方法棧和虛擬機棧合二爲一。本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。
Java棧也稱做虛擬機棧(Java Vitual Machine Stack),也是常說的棧。Java棧是Java方法執行的內存模型。Java棧中存放的是一個個的棧幀,每一個棧幀對應一個被調用的方法,在棧幀中包括局部變量表(Local Variables)、操做數棧(Operand Stack)、指向當前方法所屬的類的運行時常量池(運行時常量池的概念在方法區部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。棧也是線程私有的。
下圖表示了一個Java棧的模型
1)、局部變量表
就是用來存儲方法中的局部變量(包括在方法中聲明的非靜態變量以及函數形參)。對於基本數據類型的變量,則直接存儲它的值,對於引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就能夠肯定其大小了,所以在程序執行期間局部變量表的大小是不會改變的。
2)、操做數棧
想必學過數據結構中的棧的朋友想必對錶達式求值問題不會陌生,棧最典型的一個應用就是用來對錶達式求值。想一想一個線程執行方法的過程當中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。所以能夠這麼說,程序中的全部計算過程都是在藉助於操做數棧來完成的。
3)、指向運行時常量池的引用
由於在方法執行的過程當中有可能須要用到類中的常量,因此必需要有一個引用指向運行時常量。
4)、方法返回地址
當一個方法執行完畢以後,要返回以前調用它的地方,所以在棧幀中必須保存一個方法返回地址。因爲每一個線程正在執行的方法可能不一樣,所以每一個線程都會有一個本身的Java棧,互不干擾。也就解釋了棧是線程私有的。
當線程執行一個方法時,就會隨之建立一個對應的棧幀,並將創建的棧幀壓棧。當方法執行完畢以後,便會將棧幀出棧。所以可知,線程當前執行的方法所對應的棧幀一定位於Java棧的頂部。在這個區域規定了兩種異常情況:
堆是jvm內存管理的最大的一塊區域,此內存區域的惟一目的就是存放對象的實例,全部對象實例與數組都要在堆上分配內存。它也是垃圾收集器的主要管理區域。java對能夠處於物理上不連續的空間,只要邏輯上是連續的便可。線程共享的區域。若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將拋出OutOfMemoryError異常。
爲了支持垃圾收集,堆被分爲三個部分:
1) 堆是JVM中全部線程共享的,所以在其上進行對象內存的分配均須要進行加鎖,這也致使了new對象的開銷是比較大的(2) Sun Hotspot JVM爲了提高對象內存分配的效率,對於所建立的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的狀況計算而得,在TLAB上分配對象時不須要加鎖,所以JVM在給線程的對象分配內存時會盡可能的在TLAB上分配,在這種狀況下JVM中分配對象內存的性能和C基本是同樣高效的,但若是對象過大的話則仍然是直接使用堆空間分配(3) TLAB僅做用於新生代的Eden Space,所以在編寫Java程序時,一般多個小的對象比大的對象分配起來更加高效。
(4) 全部新建立的Object 都將會存儲在新生代Yong Generation中。若是Young Generation的數據在一次或屢次GC後存活下來,那麼將被轉移到OldGeneration。新的Object老是建立在Eden Space。
這些知識在後面學習GC和內存調優方面很是重要。
方法區在JVM中也是一個很是重要的區域,它與堆同樣,是被線程共享的區域。在方法區中,存儲了每一個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼等。方法區是堆的一個邏輯部分,爲了區分Java堆,它還有一個別名Non-Heap(非堆)。相對而言,GC對於這個區域的收集是不多出現的。當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。
在Java 7及以前版本,咱們也習慣稱方法區它爲「永久代」(Permanent Generation),更確切來講,應該是「HotSpot使用永久代實現了方法區」!
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池( Constant pool table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入運行時常量池中存放。運行時常量池相對於class文件常量池的另一個特性是具有動態性,java語言並不要求常量必定只有編譯器才產生,也就是並不是預置入class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中。
直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是JVM規範中定義的內存區域。但這部份內存也被頻繁的使用,並且也可能致使OutOfMemoryError異常出現。
JDK1.4中新引入了NIO機制,它是一種基於通道與緩衝區的新I/O方式,能夠直接從操做系統中分配直接內存,即直接堆外分配內存,這樣能在一些場景中提升性能,由於避免了在Java堆和Native堆中來回複製數據。
這裏介紹的是JDK1.8 JVM內存模型。1.8同1.7比,最大的差異就是:元數據區取代了永久代,就是JDK8沒有了PermSize相關的參數配置了。元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元數據空間並不在虛擬機中,而是使用本地內存。
1)方法區與永久代的區別?
方法區只是JVM規範定義,而永久代爲具體的實現,元空間也是方法區在jdk1.8中的一種實現。
2)爲何廢除永久代?
1.官方文檔:移除永久代是爲融合HotSpot JVM與 JRockit VM而作出的努力,由於JRockit沒有永久代,不須要配置永久代
2..PermGen很難調整,PermGen中類的元數據信息在每次FullGC的時候可能被收集,但成績很難使人滿意。
並且應該爲PermGen分配多大的空間很難肯定,由於PermSize的大小依賴於不少因素,好比JVM加載的class總數,常量池的大小,方法的大小等。
而且永久代內存常常不夠用發生內存泄露。
在近三個JDK版本(1.六、1.七、1.8)中, 運行時常量池(Runtime Constant Pool)的所處區域一直在不斷的變化,在JDK1.6時它是方法區的一部分;1.7又把他放到了堆內存中;1.8以後出現了元空間,它又回到了方法區。其實,這也說明了官方對「永久代」的優化從1.7就已經開始了。
貼一張 Java 8 的內存模型圖:
運行時區域大概瞭解後,咱們在來總結一下:
運行時區域 |
異常 |
主要緣由 |
---|---|---|
虛擬機棧和本地方法棧 | StackOverflowError、OutOfMemoryError | StackOverflowError:線程請求的棧深度大於虛擬機所容許的最大深度;OutOfMemoryError:虛擬機在擴展棧時沒法申請足夠的內存空間 |
程序計數器 |
無 |
無 |
堆 |
OutOfMemoryError |
對象數量到達最大堆的容量,內存泄漏、內存溢出 |
方法區和運行時常量池 | OutOfMemoryError |
反射,動態代理:CGLib、JSP、OSGI等 |
最後在說兩個概念:
內存泄露(Memory Leak):程序在申請內存後,對象沒有被GC所回收,它始終佔用內存,內存泄漏的堆積最終會形成內存溢出。
內存溢出(Memory Overflow):程序運行過程當中沒法申請到足夠的內存而致使的一種錯誤。內存溢出一般發生於OLD段或Perm段垃圾回收後,仍然無內存空間容納新的Java對象的狀況。一般都是因爲內存泄露致使堆棧內存不斷增大,從而引起內存溢出。
本文內容較多,也比較重要,由於本人也是在學習JVM中,若是文中有不對的地方,也請指出!謝謝!
當咱們瞭解了JVM和JVM裏面有什麼區域,對後面學習和理解JVM的一些規範或者排查JVM相關的問題也更加容易一點,當咱們知道了JVM哪些地方報什麼樣的錯誤的時候,在出現問題的時候,可以快速的定位和解決,這樣對於咱們的成長來講幫助也是很是大的,繼續學習JVM,深刻對JVM的認識,也瞭解更加有趣的Java世界。
備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。
《深刻理解Java虛擬機》
謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!