本篇內容主要講解“java中的new Object()到底占幾個字節(jié)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“java中的new Object()到底占幾個字節(jié)”吧!
企業(yè)建站必須是能夠以充分展現(xiàn)企業(yè)形象為主要目的,是企業(yè)文化與產品對外擴展宣傳的重要窗口,一個合格的網站不僅僅能為公司帶來巨大的互聯(lián)網上的收集和信息發(fā)布平臺,創(chuàng)新互聯(lián)建站面向各種領域:成都服務器租用等成都網站設計公司、成都全網營銷解決方案、網站設計等建站排名服務。
Java虛擬機棧是線程私有的,沒有數(shù)據(jù)安全問題,而堆相比較于Java虛擬機棧而言更為復雜。
因為堆是所有線程共享的一塊內存空間,會出現(xiàn)線程安全性問題,而垃圾回收也主要是回收堆內空間。
所以堆內的布局我們非常有必要深入去了解一下。現(xiàn)在就讓我們繼續(xù)來分析一下堆內布局以及Java對象在內存中的布局把。
先來看一段代碼:
package com.zwx.jvm;
public class HeapMemory {
    private Object obj1 = new Object();
    public static void main(String[] args) {
        Object obj2 = new Object();
    }
}
上面的代碼中,obj1 和obj2在內存中有什么區(qū)別?
方法區(qū)存儲每個類的結構,比如:運行時常量池、屬性和方法數(shù)據(jù),以及方法和構造函數(shù)等數(shù)據(jù)。
所以我們這個obj1是存在方法區(qū)的,而new會創(chuàng)建一個對象實例,對象實例是存儲在堆內的,于是就有了下面這幅圖(方法區(qū)指向堆):
 
 而obj2 是屬于方法內的局部變量,存儲在Java虛擬機棧內的棧幀中的局部變量表內,這就是經典的棧指向堆:
 
 這里我們再來思考一下,我們一個變量指向了堆,而堆內只是存儲了一個實例對象,那么堆內的示例對象是如何知道自己屬于哪個Class。
也就是說這個實例是如何知道自己所對應的類元信息的呢?這就涉及到了一個Java對象在內存中是如何布局的。
對象內存中可以分為三塊區(qū)域:
對象頭(Header)
實例數(shù)據(jù)(Instance Data)
對齊填充(Padding)
以64位操作系統(tǒng)為例(未開啟指針壓縮的情況), Java對象布局如下圖所示:
 
 上圖中的對齊填充不是一定有的,如果對象頭和實例數(shù)據(jù)加起來剛好是8字節(jié)的倍數(shù),那么就不需要對齊填充。
知道了Java內存布局,那么我們來看一個面試問題
這是網上很多人都會提到的一個問題,那么結合上面的Java內存布局,我們來分析下,以64位操作系統(tǒng)為例,new Object()占用大小分為兩種情況:
未開啟指針壓縮
占用大小為:8(Mark Word)+8(Class Pointer)=16字節(jié)
開啟了指針壓縮(默認是開啟的)
開啟指針壓縮后,Class Pointer會被壓縮為4字節(jié),最終大小為:
8(Mark Word) + 4(Class Pointer) + 4(對齊填充) = 16字節(jié)
結果到底是不是這個呢?我們來驗證一下。
首先引入一個pom依賴:
<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.10</version>
</dependency>
然后新建一個簡單的demo:
package com.zwx.jvm;
import org.openjdk.jol.info.ClassLayout;
public class HeapMemory {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
輸出結果如下:
 
 最后的結果是16字節(jié),沒有問題,這是因為默認開啟了指針壓縮,那我們現(xiàn)在把指針壓縮關閉之后再去試試。
-XX:+UseCompressedOops  開啟指針壓縮
-XX:-UseCompressedOops  關閉指針壓縮
 
 再次運行,得到如下結果:
 
 可以看到,這時候已經沒有了對齊填充部分了,但是占用大小還是16位。
下面我們再來演示一下如果一個對象中帶有屬性之后的大小。
新建一個類,內部只有一個byte屬性:
package com.zwx.jvm;
public class MyItem {
    byte i = 0;
}
然后分別在開啟指針壓縮和關閉指針壓縮的場景下分別輸出這個類的大小。
package com.zwx.jvm;
import org.openjdk.jol.info.ClassLayout;
public class HeapMemory {
    public static void main(String[] args) {
        MyItem myItem = new MyItem();
        System.out.println(ClassLayout.parseInstance(myItem).toPrintable());
    }
}
開啟指針壓縮,占用16字節(jié):
 
 關閉指針壓縮,占用24字節(jié):
 
 這個時候就能看出來開啟了指針壓縮的優(yōu)勢了,如果不斷創(chuàng)建大量對象,指針壓縮對性能還是有一定優(yōu)化的。
創(chuàng)建好一個對象之后,當然需要去訪問它,那么當我們需要訪問一個對象的時候,是如何定位到對象的呢?
目前最主流的訪問對象方式有兩種:
句柄訪問
直接指針訪問。
使用句柄訪問的話,Java虛擬機會在堆內劃分出一塊內存來存儲句柄池,那么對象當中存儲的就是句柄地址,然后句柄池中才會存儲對象實例數(shù)據(jù)和對象類型數(shù)據(jù)地址。
 
 直接指針訪問的話對象中就會直接存儲對象類型數(shù)據(jù)。
 
 上面圖形中我們很容易對比,就是如果使用句柄訪問的時候,會多了一次指針定位。
但是他也有一個好處就是,假如一個對象被移動(地址改變了),那么只需要改變句柄池的指向就可以了,不需要修改reference對象內的指向。
而如果使用直接指針訪問,就還需要到局部變量表內修改reference指向。
上面我們提到,在Java對象頭當中的Mark Word存儲了對象的分代年齡,那么什么是分代年齡呢?
一個對象的分代年齡可以理解為垃圾回收次數(shù),當一個對象經過一次垃圾回收之后還存在,那么分代年齡就會加1。
在64位的虛擬機中,分代年齡占了4位,最大值為15。分代年齡默認為0000,隨著垃圾回收次數(shù),會逐漸遞增。
Java堆內存中按照分代年齡來劃分,分為Young區(qū)和Old區(qū),對象分配首先會到Young區(qū)。
達到一定分代年齡(-XX:MaxTenuringThreshold可以設置大小,默認為15)就會進入Old區(qū)(注意:如果一個對象太大,那么就會直接進入Old區(qū))。
之所以會這么劃分是因為如果整個堆只有一個區(qū)的話,那么垃圾回收的時候每次都需要把堆內所有對象都掃描一遍,浪費性能。
而其實大部分Java對象的生命周期都是很短的,一旦一個對象回收很多次都回收不掉,可以認為下一次垃圾回收的時候可能也回收不掉。
所以Young區(qū)和Old區(qū)的垃圾回收可以分開進行,只有當Young區(qū)在進行垃圾回收之后還是沒有騰出空間,那么再去觸發(fā)Old區(qū)的垃圾回收。
 
 現(xiàn)在拆分成了Young區(qū),那我們看下面一個場景,下面的Young是經過垃圾回收之后的一個概圖:
 
 假如說現(xiàn)在來了一個對象,要占用2個對象的大小,會發(fā)現(xiàn)放不下去了,這時候就會觸發(fā)GC(垃圾回收)。
但是一旦觸發(fā)了GC(垃圾回收),對用戶線程是有影響的,因為GC過程中為了確保對象引用不會不斷變化,需要停止所有用戶線程。
Sun把這個事件稱之為:Stop the World(STW)。
所以說一般是越少GC越好,而實際上上圖中可以看到至少還可以放入3個對象,只要按照對象都按照順序放好,那么是可以放得下的。
所以這就產生了問題了,明明有空間,但是因為空間不連續(xù),導致對象申請內存失敗,導致觸發(fā)GC了,那么如何解決這種問題呢?
解決的思路就是把Young區(qū)的對象按順序放好,所以就產生了一個方法,把Young區(qū)再次劃分一下,分為2個區(qū):Eden區(qū)和Survivor區(qū)。
 
 具體操作是:一個對象來了之后,先分配到Eden區(qū),Eden區(qū)滿了之后,觸發(fā)GC。
經過GC之后,為了防止空間不連續(xù),把幸存下來的對象復制到Survivor區(qū),然后Eden區(qū)就可以完整清理掉了。
當然這么做是有一個前提的,就是大部分對象都是生命周期極短的,基本一次垃圾回收就可以把Eden區(qū)大部分對象回收掉(這個前提是經過測試總結得到的)。
觸發(fā)GC的時候Survivor區(qū)也會一起回收,并不是說單獨只觸發(fā)Eden區(qū)。
但是這樣問題又來了,Eden區(qū)是保證空間基本連續(xù)了,但是Survivor區(qū)又可能產生空間碎片,導致不連續(xù)了。
所以就又把Survivor區(qū)給一分為二了。
 
 這個時候工作流程又變成這樣了:
首先還是在Eden區(qū)分配空間,Eden區(qū)滿了之后觸發(fā)GC,GC之后把幸存對象 復制到S0區(qū)(S1區(qū)是空的),然后繼續(xù)在Eden區(qū)分配對象。
再次觸發(fā)GC之后如果發(fā)現(xiàn)S0區(qū)放不下了(產生空間碎片,實際還有空間),那么就把S0區(qū)對象復制到S1區(qū),并把幸存對象也復制到S1區(qū),這時候S0區(qū)是空的了,并依次反復操作。
假如說S0區(qū)或者S1區(qū)空間對象復制移動了之后還是放不下,那就說明這時候是真的滿了,那就去老年區(qū)借點空間過來(這就是擔保機制,老年代需要提供這種空間分配擔保)。
假如說老年區(qū)空間也不夠了,那就會觸發(fā)Full GC,如果還是不夠,那就會拋出OutOfMemeoyError異常了。
注意:為了確保S0和S1兩個區(qū)域之間每次復制都能順利進行,S0和S1兩個區(qū)的大小必須要保持一致,而且同一時間有一個區(qū)域一定是空的。
雖然說這種做法是會導致了一小部分空間的浪費,但是綜合其他性能的提升來說,是值得的。
當Young區(qū)的對象達到設置的分代年齡之后,對象會進入Old區(qū),Old區(qū)滿了之后會觸發(fā)Full GC,如果還是清理不掉空間,那么就拋出OutOfMemeoyError異常。
上面提到了很多新的名詞,而實際上很多這種名詞還有其他叫法,這個還是覺得有必要了解一下。
垃圾回收:簡稱GC。
Minor GC:針對新生代的GC
Major GC:針對老年代的GC,一般老年代觸發(fā)GC的同時也會觸發(fā)Minor GC,也就等于觸發(fā)了Full GC。
Full GC:新生代+老年代同時發(fā)生GC。
Young區(qū):新生代
Old區(qū):老年代
Eden區(qū):暫時沒發(fā)現(xiàn)有什么中文翻譯(伊甸園?)
Surcivor區(qū):幸存區(qū)
S0和S1:也稱之為from區(qū)和to區(qū),注意from和to兩個區(qū)是不斷互換身份的,且S0和S1一定要相等,并且保證一塊區(qū)域是空的
從上面的介紹大家應該有一個大致的印象,一個對象會在Eden區(qū),S0區(qū),S1區(qū),Old區(qū)不斷流轉(當然,一開始就會被回收的短命對象除外)。
我們可以得到下面的一個流程圖:
 
 到此,相信大家對“java中的new Object()到底占幾個字節(jié)”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
                網站名稱:java中的newObject()到底占幾個字節(jié)
                
                分享路徑:http://www.chinadenli.net/article0/gpscio.html
            
成都網站建設公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、移動網站建設、定制開發(fā)、外貿建站、關鍵詞優(yōu)化、商城網站
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)
