jvm系列 (一) ---jvm內存區域與溢出


jvm內存區域與溢出

目錄

為什么學習jvm

  • 木板原理,最短的一塊板決定一個水的深度,當一個系統垃圾收集成為瓶頸的時候,那么就需要你對jvm的了解掌握。
  • 當一個系統出現內存溢出,內存泄露的時候,因為你懂jvm知識,可以更加快速定位錯誤,可以通過參數去合理設置各內存區域的內存容量。
  • 因為你對jvm的認識,寫代碼的時候會潛意識地讓你注意代碼質量,可能你會說是那是小小的性能提升,但是量變會導致質變的。

jvm內存區域

jvm內存划分

  • 方法區
  • 虛擬機棧
  • 本地方法棧
  • 程序計數器

程序計數器

  • 當前線程所執行的字節碼的行號指示器。
  • 因為cpu的每個核心只能同時運行一個線程,所以當一個線程執行完時間片后切換到另一個線程,切換時為了能恢復到正確的執行位置,所以需要程序計數器(學過計算機組成原理的應該比較熟悉)。
  • 如果是線程執行的是一個java方法那么此時計數器記錄的是正在執行的虛擬機字節碼指令的地址。如果是nativ方法(非java代碼),計數器值為空。

虛擬機棧

  • java執行的內存模型,棧由棧幀組成,線程調用一個java方法時,創建一個棧幀,方法返回時,棧幀彈出。棧幀入棧出棧的過程就是方法開始結束的過程
  • 棧幀存儲局部變量區(存放基本數據類型數據,對象指針),操作數棧(存放操作數,比如加法運算的時候操作數棧取值,計算后再壓入棧),動態鏈接(將符號引用(常量池指向方法的符號引用)轉換為調用方法的直接引用),方法出口。

本地方法棧

  • 和虛擬機棧作用類似,只不過服務對象不同,本地方法服務對象是非java方法,虛擬機棧服務對象是java方法

方法區

  • 存放類的信息、常量、靜態變量等
  • 運行時常量池是方法區一部分,主要存放字面量(如final修飾的變量)和符號引用量(編譯原理方面)

  • 存放對象,對象要在堆上分配內存
  • 堆分為年輕代和年老代
  • 年輕代分為伊甸區(Eden space)和幸存者區(Survivor space)
  • 幸存區分為from和to空間

總結

  • jvm的內存區域划分為程序計數器,虛擬機棧,本地方法棧,方法區,堆。
  • 程序計數器,虛擬機棧和本地方法棧都是線程獨享的,而方法區和堆是線程共享的

溢出

理清概念

  • 操作系統分配給進程的內存是有限的,而jvm的內存區域我們已經知道,當我們設置好堆和方法區的最大容量后,那么剩下的內存將分配給虛擬機棧和本地方法區和程序計數器(占用內存少)。
  • 兩種常見的溢出異常,一種OutOfMemoryError(OOM),一種StackOverflowError。

各區溢出情況

  • 堆溢出,當我們創建的對象占用的內存超過最大堆容量時候,會拋出OOM
  • 本地方法棧和虛擬機棧溢出:當請求的棧深度超過虛擬機所允許的深度的時候會拋出棧溢出,這種情況我們在使用遞歸出錯的時候經常遇到;還有就是出現OOM的情況,我們知道分配給這兩個棧的內存是有限,和線程數和線程的棧內存有關系,那么當我們其中任意一個過大的話,都有可能造成OOM
  • 方法區和運行時常量池溢出:方法區存放類的信息,有時我們用的框架在動態代理的時候會動態生成CLASS,有時候有很多jsp的時候,這個時候有可能會出現方法區的OOM;而常量池溢出我們可以用String的intern方法(如果常量池沒有與字符串相等的字符串,就將這個字符串存入方法區,注意在不同的版本是不同的,1.6之前會將字符串實例復制到永久代,而1.7的沒有復制實例,只是在常量池記錄首次出現的實例引用)進行模擬。

設置各區的jvm參數

  • 設置堆的最小值-Xms,堆的最大值-Xmx
  • 設置永久代(jdk8之前用永久代來實現方法區)的最小值-XX:PermSize ,最大值-XX:MaxPermSize
  • 設置棧容量-Xss

延伸

關於創建字符串

        String s2=new String("jiajun");
		String s6=new String("jiajun");
		System.out.println(s2==s6);
  • 結果為false,都存放在堆內存,但是兩個地方。
        String s6=new String("jiajun");
		String s1="jiajun";
		System.out.println(s1==s6);
  • 結果為false,s6存放在堆當中,而s1存放在常量池當中
        String s1="jiajun";
		String s7="jiajun";
		System.out.println(s1==s7);
  • 結果為true,都是存放在常量池
        String s4="jia";
		String s5=s4+"jun";
		String s1="jiajun";
		
		System.out.println(s1==s5);
  • 結果為false,變量的值在運行的時候才確定,所以此時s5實際上是new一個對象
        String s3="jia"+"jun";
		String s1="jiajun";
		System.out.println(s1==s3);
  • 結果為true,此時s1 s3都是指向常量池一個string
        String s1="jiajun";
		String s8=new String("jia")+"jun";
		System.out.println(s1==s8);
  • 結果為false,此時s8同樣是new出來一個對象

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM