[英]What is the memory consumption of an object in Java?
一個有100個屬性的對象消耗的內存空間和100個對象每個有一個屬性消耗的內存空間是一樣的嗎?
為一個對象分配了多少內存?
添加屬性時使用了多少額外空間?
Mindprod指出,這不是一個可以直接回答的問題:
JVM 可以自由地在內部以任何它喜歡的方式存儲數據,大端或小端,具有任意數量的填充或開銷,但原語必須表現得好像它們具有官方大小一樣。
例如,JVM 或本機編譯器可能決定將boolean[]
存儲在 64 位長塊中,如BitSet
。 它不必告訴您,只要程序給出相同的答案即可。
- 它可能會在堆棧上分配一些臨時對象。
- 它可能會優化一些完全不存在的變量或方法調用,用常量替換它們。
- 它可能會版本方法或循環,即編譯一個方法的兩個版本,每個版本針對特定情況進行優化,然后預先決定調用哪個版本。
當然,硬件和操作系統有多層緩存,片上緩存、SRAM 緩存、DRAM 緩存、普通 RAM 工作集和磁盤后備存儲。 您的數據可能會在每個緩存級別重復。 所有這些復雜性意味着您只能非常粗略地預測 RAM 消耗。
您可以使用Instrumentation.getObjectSize()
獲取對象消耗的存儲空間的估計值。
要可視化實際的對象布局、封裝和引用,您可以使用JOL(Java 對象布局)工具。
在現代 64 位 JDK 中,對象具有 12 字節的標頭,填充為 8 字節的倍數,因此最小對象大小為 16 字節。 對於 32 位 JVM,開銷為 8 字節,填充為 4 字節的倍數。 (從梅德Spikhalskiy的回答, Jayen的回答,和JavaWorld的。)
通常,引用在 32 位平台或 64 位平台上為 4 個字節,最高可達-Xmx32G
; 和 8 字節以上 32Gb ( -Xmx32G
)。 (請參閱 壓縮對象引用。)
因此,64 位 JVM 通常需要多 30-50% 的堆空間。 ( 我應該使用 32 位還是 64 位 JVM? ,2012,JDK 1.7)
與原始類型(來自JavaWorld )相比,盒裝包裝器具有開銷:
Integer
:16 字節的結果比我預期的要差一點,因為int
值只能容納 4 個額外的字節。 與可以將值存儲為原始類型相比,使用Integer
花費了 300% 的內存開銷
Long
:16 字節也:顯然,堆上的實際對象大小受制於由特定 CPU 類型的特定 JVM 實現完成的低級內存對齊。 看起來Long
是 8 個字節的 Object 開銷,加上實際 long 值的 8 個字節。 相比之下,Integer
有一個未使用的 4 字節空洞,很可能是因為我使用的 JVM 強制在 8 字節字邊界上對齊對象。
其他容器也很昂貴:
多維數組:它提供了另一個驚喜。
開發人員通常在數值和科學計算中使用像int[dim1][dim2]
。在
int[dim1][dim2]
數組實例中,每個嵌套的int[dim2]
數組本身就是一個Object
。 每個都會增加通常的 16 字節數組開銷。 當我不需要三角形或參差不齊的數組時,這表示純粹的開銷。 當數組維度差異很大時,影響會增加。例如,一個
int[128][2]
實例占用 3,600 字節。 與int[256]
實例使用的 1,040 字節(具有相同容量)相比,3,600 字節代表 246% 的開銷。 在byte[256][1]
的極端情況下,開銷因子幾乎是 19! 將其與相同語法不增加任何存儲開銷的 C/C++ 情況進行比較。
String
:String
的內存增長跟蹤其內部字符數組的增長。 但是,String
類又增加了 24 個字節的開銷。對於大小為 10 個字符或更少的非空
String
,相對於有用負載(每個字符 2 個字節加上長度為 4 個字節)的額外開銷成本在 100% 到 400% 之間。
考慮這個示例對象:
class X { // 8 bytes for reference to the class definition
int a; // 4 bytes
byte b; // 1 byte
Integer c = new Integer(); // 4 bytes for a reference
}
一個簡單的總和表明X
的實例將使用 17 個字節。 但是,由於對齊(也稱為填充),JVM 以 8 字節的倍數分配內存,因此它會分配 24 字節而不是 17 字節。
這取決於架構/jdk。 對於現代 JDK 和 64 位體系結構,對象具有 12 字節的標頭和 8 字節的填充 - 因此最小對象大小為 16 字節。 您可以使用稱為Java 對象布局的工具來確定大小並獲取有關任何實體的對象布局和內部結構的詳細信息,或者通過類引用來猜測此信息。 我的環境中 Integer 的輸出示例:
Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
java.lang.Integer object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int Integer.value N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
因此,對於 Integer,實例大小為 16 字節,因為 4 字節 int 緊接在標頭之后和填充邊界之前壓縮到位。
代碼示例:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;
public static void main(String[] args) {
System.out.println(VMSupport.vmDetails());
System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}
如果您使用 maven,要獲得 JOL:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.3.2</version>
</dependency>
每個對象對其關聯的監視器和類型信息以及字段本身都有一定的開銷。 除此之外,幾乎可以按照 JVM 認為合適的方式布置字段(我相信) - 但如另一個答案所示,至少一些JVM 會打包得相當緊。 考慮這樣一個類:
public class SingleByte
{
private byte b;
}
對比
public class OneHundredBytes
{
private byte b00, b01, ..., b99;
}
在 32 位 JVM 上,我希望SingleByte
100 個實例占用 1200 個字節(由於填充/對齊,8 個字節的開銷 + 4 個字段用於該字段)。 我希望OneHundredBytes
一個實例占用 108 個字節 - 開銷,然后是 100 個字節,打包。 不過它肯定會因 JVM 而異——一種實現可能決定不將字段打包在OneHundredBytes
,導致它占用 408 字節(= 8 字節開銷 + 4 * 100 對齊/填充字節)。 在 64 位 JVM 上,開銷也可能更大(不確定)。
編輯:請參閱下面的評論; 顯然 HotSpot SingleByte
到 8 個字節的邊界而不是 32 個,因此SingleByte
每個實例將占用 16 個字節。
無論哪種方式,“單個大對象”至少與多個小對象一樣有效 - 對於像這樣的簡單情況。
程序的總已用/空閑內存可以通過以下方式在程序中獲得
java.lang.Runtime.getRuntime();
運行時有幾個與內存相關的方法。 下面的編碼示例演示了它的用法。
public class PerformanceTest {
private static final long MEGABYTE = 1024L * 1024L;
public static long bytesToMegabytes(long bytes) {
return bytes / MEGABYTE;
}
public static void main(String[] args) {
// I assume you will know how to create an object Person yourself...
List <Person> list = new ArrayList <Person> ();
for (int i = 0; i <= 100_000; i++) {
list.add(new Person("Jim", "Knopf"));
}
// Get the Java runtime
Runtime runtime = Runtime.getRuntime();
// Run the garbage collector
runtime.gc();
// Calculate the used memory
long memory = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used memory is bytes: " + memory);
System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
}
}
似乎每個對象在 32 位系統上都有 16 字節的開銷(在 64 位系統上是 24 字節)。
http://algs4.cs.princeton.edu/14analysis/是一個很好的信息來源。 以下是眾多優秀示例中的一個示例。
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf也非常有用,例如:
一個有100個屬性的對象消耗的內存空間和100個對象每個有一個屬性消耗的內存空間是一樣的嗎?
不。
為一個對象分配了多少內存?
添加屬性時使用了多少額外空間?
這個問題將是一個非常廣泛的問題。
這取決於類變量,或者您可以在 java 中調用作為狀態內存使用情況。
它還對標頭和引用有一些額外的內存要求。
Java 對象使用的堆內存包括
原始字段的內存,根據它們的大小(參見下面的原始類型的大小);
用於參考字段的內存(每個 4 個字節);
一個對象頭,由幾個字節的“內務處理”信息組成;
Java 中的對象還需要一些“內務處理”信息,例如記錄對象的類、ID 和狀態標志,例如對象當前是否可達、當前是否同步鎖定等。
Java 對象標頭大小在 32 位和 64 位 jvm 上有所不同。
雖然這些是主要的內存消費者,但 jvm 有時也需要額外的字段,例如代碼對齊等
原始類型的大小
布爾值和字節-- 1
字符和短- 2
整數和浮點數-- 4
長雙倍-- 8
不,注冊一個對象也需要一些內存。 具有 1 個屬性的 100 個對象將占用更多內存。
我從另一個答案中提到的java.lang.instrument.Instrumentation方法中獲得了非常好的結果。 有關其使用的良好示例,請參閱 JavaSpecialists' Newsletter 中的條目Instrumentation Memory Counter和 SourceForge 上的java.sizeOf庫。
如果它對任何人有用,您可以從我的網站下載一個用於查詢對象內存使用情況的小型Java 代理。 它還可以讓您查詢“深度”內存使用情況。
不,100 個小物體比一個大物體需要更多的信息(內存)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.