第二章 JVM內存分配

注意:本篇博客,主要參考自如下四本書html

《分佈式Java應用:基礎與實踐》java

《深刻理解Java虛擬機(第二版)》程序員

《突破程序員基本功的16課》算法

《實戰java虛擬機》數組

說明:關於JVM內存結構,查看《第一章 JVM內存結構》,下面所講的JVM內存分配主要是指在Hotspot JVM下新建對象在堆內存中分配的狀況。緩存

 

一、建立一個真正對象的基本過程併發

六步:分佈式

  • 1. 類加載機制檢查
    • JVM首先檢查一個new指令的參數是否能在常量池中定位到一個符號引用,而且檢查該符號引用表明的類是否已被加載、解析和初始化過(實際上就是在檢查new的對象所屬的類是否已經執行過類加載機制)。若是沒有,先進行類加載機制加載類。關於類加載機制,以後再說。
  • 2. 分配內存
    • 把一起肯定大小的內存從Java堆中劃分出來
  • 3. 初始化對象
    • 初始化零值(操做實例數據部分--對象內存佈局三部分之一
      • 對象的實例字段不須要賦初始值也能夠直接使用其默認零值,就是這裏起得做用
      • 每一種類型的對象都有不一樣的默認零值
    • 設置對象頭(操做對象頭部分--對象內存佈局三部分之一
    • 執行<init>
      • 爲對象的字段賦值(在第三步只是初始化了零值,這裏會根據所寫程序給實例賦值)
  • 4.將建立的對象指向分配的內存

注意:第三步和第四步可能發生指令重排序,這也是單例模式使用 volatile 修飾對象的緣由。ide

 

二、內存分配概念memcached

  • 在類加載完成後,一個對象所需的內存大小就能夠徹底肯定了,具體的狀況查看對象的內存佈局。
  • 爲對象分配空間,即把一起肯定大小(上述肯定下來的對象內存大小)的內存從Java堆中劃分出來

 

三、內存分配兩種方式

  • 指針碰撞
    • 適用場合:堆內存規整(即沒有內存碎片)的狀況下
    • 原理:用過的內存所有整合到一邊,沒有用過的內存放在另外一邊,中間有一個分界值指針,只須要向着沒用過的內存方向將該指針移動對象內存大小位置便可
    • GC收集器:Serial、ParNew
  • 空閒列表
    • 適用場合:堆內存不規整的狀況下
    • 原理:虛擬機會維護一個列表,該列表中會記錄哪些內存塊是可用的,在分配的時候,找一起足夠大的內存塊兒來劃分給對象實例(這一起能夠類比memcached的slab模型),最後更新列表記錄。關於memcached slab內存模型介紹查看《第六章 memcached剖析
    • GC收集器:CMS
  • 注意
    • 選擇以上兩種方式中的哪種,取決於Java堆內存是否規整
    • Java堆內存是否規整,取決於GC收集器的算法是"標記-清除",仍是"標記-整理"(也稱做"標記-壓縮"),值得注意的是,複製算法內存也是規整的

 

四、內存分配併發問題

堆內存是各個線程的共享區域,因此在操做堆內存的時候,須要處理併發問題。處理的方式有兩種:

  • CAS+失敗重試
  • TLAB(Thread Local Allocation Buffer)
    • 原理:爲每個線程預先在Eden區分配一起內存,JVM在給線程中的對象分配內存時,首先在TLAB分配,當對象大於TLAB中的剩餘內存或TLAB的內存已用盡時,再採用上述的CAS進行內存分配
    • -XX:+/-UseTLAB:是否使用TLAB
    • -XX:TLABWasteTargetPercent設置TLAB可佔用的Eden區的比率,默認爲1%
    • JVM會根據如下三個條件來給每一個線程分配合適大小的TLAB
      • -XX:TLABWasteTargetPercent
      • 線程數量
      • 線程是否頻繁分配對象
    • -XX:PrintTLAB:查看TLAB的使用狀況

 

五、總結

  • 儘可能少建立對象
    • 根據第一起所說,建立一個對象的過程比較複雜,耗時較多,因此儘可能減小對象的建立
    • 對象建立的少,未來垃圾收集器收集的垃圾也就少,提升效率
    • 對象建立的少,佔用內存也就少,那麼剩餘的系統內存也就相對多,系統運行也就快
    • 避免在常用的方法中或循環中建立對象
  • 多個小的對象比大對象分配起來更加高效
    • 這是根據TLAB得出來的,多個小對象能夠並行在各自的TLAB分配內存,而大對象的話,可能只能經過CAS同步來分配內存
  • 衡量上述兩點
  • 對於String
    • 儘可能使用直接量:eg. String str = "hello";//常量會直接存在"常量池",而非String str = new String("hello");//除了將"hello"存在"常量池"以外,還會建立一個char[]
    • 不要使用String去拼接字符串,會造成許多臨時字符串:以下,
          String s1 = "hello1";
          String s2 = "hello2";
          String s3 = "hello3";
          String s4 = s1+s2+s3;
      View Code

      實際上,咱們只想要字符串s1+s2+s3,可是在上述的拼接過程當中,會造成s1+s2的臨時字符串。拼接字符串,使用StringBuilder,該類相較於StringBuffer因爲不是同步類,其運行效果會更好。

    • 儘早釋放無用對象的引用(幫助垃圾回收)
          public void info(){
              Object obj = new Object();
              System.out.println(obj.hashCode());
              obj = null;//顯式釋放無用對象
          }
      View Code

      如上邊方法所示,其中的obj是一個局部變量,在方法執行結束後,棧幀就會出棧並被回收,棧幀中所存儲的局部變量一塊兒被回收掉了,因此這裏的"obj=null;"就沒用了,可是看下邊

          public void info(){
              Object obj = new Object();
              System.out.println(obj.hashCode());
              obj = null;//顯式釋放無用對象
              //下邊還有一些很耗時、很耗內存的操做,這些操做與obj無關
          }
      View Code

      這時候,若是咱們加上了"obj=null;"這一句,那麼就有可能在方法執行結束以前,obj被回收。

    • 儘可能少使用static變量,由於static變量屬於類變量,存儲於方法區,其所佔內存沒法被垃圾回收器回收掉,除非static所屬的類被卸載掉。
  • 經常使用的對象放入緩存或鏈接池(其實,鏈接池也能夠看作是一個緩存)
  • 考慮使用SoftReference(關於幾種引用方式,以後會說)
    • 當內存足夠時,功能等同於普通變量
    • 當內存不足時,釋放軟引用所引用的對象
    • 通常用於大數組、大對象
    • 經過軟引用所獲取的對象可能爲null(當內存不足時,釋放軟引用所引用的對象),在應用程序中須要顯示判斷對象是否爲null,若爲null,須要重建對象
相關文章
相關標籤/搜索