Tiêu thụ bộ nhớ của một đối tượng trong Java là gì?


216

Có phải không gian bộ nhớ được tiêu thụ bởi một đối tượng với 100 thuộc tính giống với 100 đối tượng, mỗi thuộc tính không?

Bao nhiêu bộ nhớ được phân bổ cho một đối tượng?
Bao nhiêu không gian bổ sung được sử dụng khi thêm một thuộc tính?

Câu trả lời:


180

Mindprod chỉ ra rằng đây không phải là một câu hỏi đơn giản để trả lời:

Một JVM có thể tự do lưu trữ dữ liệu theo bất kỳ cách nào nó làm hài lòng bên trong, endian lớn hay nhỏ, với bất kỳ số lượng đệm hoặc chi phí nào, mặc dù các nguyên thủy phải hành xử như thể chúng có kích thước chính thức.
Ví dụ, JVM hoặc trình biên dịch gốc có thể quyết định lưu trữ một boolean[]đoạn dài 64 bit như a BitSet. Nó không phải nói với bạn, miễn là chương trình đưa ra câu trả lời giống nhau.

  • Nó có thể phân bổ một số Đối tượng tạm thời trên ngăn xếp.
  • Nó có thể tối ưu hóa một số biến hoặc các cuộc gọi phương thức hoàn toàn không tồn tại thay thế chúng bằng hằng số.
  • Nó có thể phiên bản phương thức hoặc vòng lặp, tức là biên dịch hai phiên bản của một phương thức, mỗi phiên bản được tối ưu hóa cho một tình huống nhất định, sau đó quyết định trước cái nào sẽ gọi.

Sau đó, tất nhiên phần cứng và HĐH có bộ đệm đa lớp, trên bộ đệm chip, bộ đệm SRAM, bộ đệm DRAM, bộ làm việc RAM thông thường và lưu trữ sao lưu trên đĩa. Dữ liệu của bạn có thể được nhân đôi ở mọi cấp độ bộ đệm. Tất cả sự phức tạp này có nghĩa là bạn chỉ có thể dự đoán rất rõ mức tiêu thụ RAM.

Phương pháp đo lường

Bạn có thể dùng Instrumentation.getObjectSize() để có được ước tính dung lượng lưu trữ được sử dụng bởi một đối tượng.

Để trực quan hóa bố cục đối tượng, dấu chân và tham chiếu thực tế , bạn có thể sử dụng công cụ JOL (Bố cục đối tượng Java) .

Đối tượng tiêu đề và tham chiếu đối tượng

Trong JDK 64 bit hiện đại, một đối tượng có tiêu đề 12 byte, được đệm thành bội số của 8 byte, vì vậy kích thước đối tượng tối thiểu là 16 byte. Đối với các JVM 32 bit, tổng phí là 8 byte, được đệm thành bội số của 4 byte. (Từ câu trả lời Dmitry Spikhalskiy của , câu trả lời Jayen của , và JavaWorld .)

Thông thường, các tham chiếu là 4 byte trên nền tảng 32 bit hoặc trên nền tảng 64 bit trở lên -Xmx32G; và 8 byte trên 32Gb ( -Xmx32G). (Xem tài liệu tham khảo đối tượng nén .)

Kết quả là, một JVM 64 bit thường sẽ cần thêm không gian heap 30-50%. ( Tôi nên sử dụng JVM 32 hoặc 64 bit?, 2012, JDK 1.7)

Các loại, mảng và chuỗi đóng hộp

Các hàm bao đóng hộp có chi phí hoạt động so với các kiểu nguyên thủy (từ JavaWorld ):

  • Integer: Kết quả 16 byte kém hơn một chút so với tôi dự đoán vì một intgiá trị có thể vừa với 4 byte bổ sung. Việc sử dụng Integerchi phí cho tôi là 300% bộ nhớ so với khi tôi có thể lưu trữ giá trị dưới dạng nguyên thủy

  • Long: 16 byte cũng: Rõ ràng, kích thước đối tượng thực tế trên heap phải tuân theo căn chỉnh bộ nhớ mức thấp được thực hiện bởi một triển khai JVM cụ thể cho một loại CPU cụ thể. Dường như a Longlà 8 byte chi phí đối tượng, cộng thêm 8 byte cho giá trị dài thực tế. Ngược lại, Integercó một lỗ 4 byte không được sử dụng, rất có thể là do JVM I sử dụng lực lượng căn chỉnh đối tượng trên một ranh giới từ 8 byte.

Các container khác cũng rất tốn kém:

  • Mảng nhiều chiều : nó cung cấp một bất ngờ khác.
    Các nhà phát triển thường sử dụng các cấu trúc nhưint[dim1][dim2] trong máy tính số và khoa học.

    Trong một int[dim1][dim2]trường hợp mảng, mỗi int[dim2]mảng lồng nhau là mộtObject quyền riêng của nó. Mỗi bổ sung thêm mảng 16 byte thông thường. Khi tôi không cần một mảng hình tam giác hoặc rách rưới, nó đại diện cho chi phí thuần túy. Tác động tăng lên khi kích thước mảng khác nhau rất nhiều.

    Ví dụ, một int[128][2]cá thể mất 3.600 byte. So với 1.040 byte mà một int[256]cá thể sử dụng (có cùng dung lượng), 3.600 byte đại diện cho chi phí hoạt động là 246 phần trăm. Trong trường hợp cực đoan byte[256][1], hệ số trên không là 19! So sánh điều đó với tình huống C / C ++ trong đó cùng một cú pháp không thêm bất kỳ chi phí lưu trữ nào.

  • String: Stringsự tăng trưởng bộ nhớ của a theo dõi sự tăng trưởng của mảng char bên trong của nó. Tuy nhiên,String lớp thêm 24 byte chi phí khác.

    Đối với số không Stringcó kích thước từ 10 ký tự trở xuống, chi phí phải trả thêm vào tương ứng với tải trọng hữu ích (2 byte cho mỗi char cộng với 4 byte cho chiều dài), dao động từ 100 đến 400 phần trăm.

Sắp xếp

Xem xét đối tượng ví dụ này :

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
}

Một khoản tiền ngây thơ sẽ gợi ý rằng một thể hiện Xsẽ sử dụng 17 byte. Tuy nhiên, do căn chỉnh (còn được gọi là phần đệm), JVM phân bổ bộ nhớ theo bội số 8 byte, vì vậy thay vì 17 byte, nó sẽ phân bổ 24 byte.


int [128] [6]: 128 mảng gồm 6 ints - tổng cộng 768 ints, 3072 byte dữ liệu + 2064 byte Đối tượng trên tổng = 5166 byte tổng. int [256]: Tổng cộng 256 ints - do đó không thể so sánh được. int [768]: 3072 byte dữ liệu + 16 byes phí - khoảng 3/5 không gian của mảng 2D - không quá chi phí 246%!
JeeBee

Ah, bài viết gốc đã sử dụng int [128] [2] không int [128] [6] - tự hỏi làm thế nào mà nó đã thay đổi. Cũng cho thấy các ví dụ cực đoan có thể kể một câu chuyện khác.
JeeBee

2
Chi phí hoạt động là 16 byte trong JVM 64 bit.
Tim Cooper

3
@AlexWien: Một số lược đồ thu gom rác có thể áp đặt kích thước đối tượng tối thiểu tách biệt với phần đệm. Trong quá trình thu gom rác, một khi một đối tượng được sao chép từ một vị trí cũ sang một vị trí mới, vị trí cũ có thể không cần giữ dữ liệu cho đối tượng đó nữa, nhưng nó sẽ cần giữ một tham chiếu đến vị trí mới; nó cũng có thể cần lưu trữ một tham chiếu đến vị trí cũ của đối tượng trong đó tham chiếu đầu tiên được phát hiện và phần bù của tham chiếu đó trong đối tượng cũ [vì đối tượng cũ vẫn có thể chứa các tham chiếu chưa được xử lý].
supercat

2
@AlexWien: Sử dụng bộ nhớ tại vị trí cũ của đối tượng để lưu giữ thông tin lưu giữ sách của người thu gom rác tránh việc phải cấp phát bộ nhớ khác cho mục đích đó, nhưng có thể áp dụng kích thước đối tượng tối thiểu lớn hơn yêu cầu. Tôi nghĩ rằng ít nhất một phiên bản của trình thu gom rác .NET sử dụng phương pháp đó; một số trình thu gom rác Java cũng có thể làm như vậy.
supercat

34

Nó phụ thuộc vào kiến ​​trúc / jdk. Đối với kiến ​​trúc JDK và 64 bit hiện đại, một đối tượng có tiêu đề 12 byte và đệm thêm 8 byte - vì vậy kích thước đối tượng tối thiểu là 16 byte. Bạn có thể sử dụng một công cụ gọi là Java Object Layout để xác định kích thước và nhận chi tiết về bố cục đối tượng và cấu trúc bên trong của bất kỳ thực thể nào hoặc đoán thông tin này bằng cách tham chiếu lớp. Ví dụ về đầu ra cho Integer trên môi trường của tôi:

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

Vì vậy, đối với Integer, kích thước cá thể là 16 byte, vì int 4 byte được nén tại chỗ ngay sau tiêu đề và trước ranh giới đệm.

Mẫu mã:

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());
}

Nếu bạn sử dụng maven, để lấy JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>

28

Mỗi đối tượng có một chi phí nhất định cho màn hình liên quan và thông tin loại, cũng như chính các trường. Ngoài ra, các trường có thể được trình bày khá nhiều tuy nhiên JVM thấy phù hợp (tôi tin) - nhưng như trong câu trả lời khác , ít nhất một số JVM sẽ đóng gói khá chặt chẽ. Hãy xem xét một lớp học như thế này:

public class SingleByte
{
    private byte b;
}

đấu với

public class OneHundredBytes
{
    private byte b00, b01, ..., b99;
}

Trên JVM 32 bit, tôi mong đợi 100 trường hợp SingleBytesẽ nhận 1200 byte (8 byte trên cao + 4 byte cho trường do đệm / căn chỉnh). Tôi mong đợi một phiên bản OneHundredByteslấy 108 byte - chi phí chung, và sau đó 100 byte, được đóng gói. Nó chắc chắn có thể thay đổi theo JVM - một triển khai có thể quyết định không đóng gói các trường trongOneHundredBytes , dẫn đến nó mất 408 byte (= 8 byte trên đầu + 4 * 100 byte được liên kết / đệm). Trên JVM 64 bit, chi phí cũng có thể lớn hơn (không chắc chắn).

EDIT: Xem bình luận dưới đây; rõ ràng các miếng đệm HotSpot đến ranh giới 8 byte thay vì 32, vì vậy mỗi phiên bản SingleBytesẽ lấy 16 byte.

Dù bằng cách nào, "đối tượng lớn duy nhất" sẽ ít nhất có hiệu quả như nhiều đối tượng nhỏ - đối với các trường hợp đơn giản như thế này.


9
Trên thực tế, một phiên bản của SingleByte sẽ lấy 16 byte trên Sun JVM, tức là 8 byte trên không, 4 byte cho trường và sau đó là 4 byte cho phần đệm đối tượng, vì trình biên dịch HotSpot làm tròn mọi thứ thành bội số của 8.
Paul Wagland

6

Tổng bộ nhớ đã sử dụng / miễn phí của một chương trình có thể được lấy trong chương trình thông qua

java.lang.Runtime.getRuntime();

Thời gian chạy có một số phương thức liên quan đến bộ nhớ. Ví dụ mã hóa sau đây chứng minh việc sử dụng nó.

package test;

 import java.util.ArrayList;
 import java.util.List;

 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 a object Person yourself...
         List < Person > list = new ArrayList < Person > ();
         for (int i = 0; i <= 100000; 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));
     }
 }

6

Dường như mọi đối tượng đều có tổng phí 16 byte trên các hệ thống 32 bit (và 24 byte trên hệ thống 64 bit).

http://algs4.cs.princeton.edu/14analysis/ là một nguồn thông tin tốt. Một ví dụ trong số nhiều người tốt là sau đây.

nhập mô tả hình ảnh ở đây

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-ffic-java-tutorial.pdf cũng rất nhiều thông tin, ví dụ:

nhập mô tả hình ảnh ở đây


"Dường như mọi đối tượng đều có tổng phí 16 byte trên các hệ thống 32 bit (và 24 byte trên các hệ thống 64 bit)." Điều đó không đúng, ít nhất là đối với các JDK hiện tại. Hãy xem câu trả lời của tôi cho ví dụ Integer. Chi phí đối tượng tối thiểu là 12 byte cho tiêu đề cho hệ thống 64 bit và JDK hiện đại. Có thể nhiều hơn vì phần đệm, phụ thuộc vào cách bố trí thực tế của các trường trong đối tượng.
Dmitry Spikhalskiy

Liên kết thứ hai với hướng dẫn Java hiệu quả về bộ nhớ dường như đã chết. Tôi nhận được "Cấm".
tsleyon

6

Có phải không gian bộ nhớ được tiêu thụ bởi một đối tượng với 100 thuộc tính giống với 100 đối tượng, mỗi thuộc tính không?

Không.

Bao nhiêu bộ nhớ được phân bổ cho một đối tượng?

  • Chi phí hoạt động là 8 byte trên 32 bit, 12 byte trên 64 bit; và sau đó làm tròn thành bội số của 4 byte (32 bit) hoặc 8 byte (64 bit).

Bao nhiêu không gian bổ sung được sử dụng khi thêm một thuộc tính?

  • Thuộc tính dao động từ 1 byte (byte) tới 8 byte (dài / đôi), nhưng tài liệu tham khảo hoặc là 4 byte hoặc 8 byte tùy thuộc không vào việc nó 32bit hoặc 64bit, nhưng thay vì cho dù -Xmx là <32Gb hoặc> = 32Gb: điển hình 64 -bit JVM có một tối ưu hóa được gọi là "-UseCompressionOops", nén các tham chiếu đến 4 byte nếu heap dưới 32Gb.

1
một char là 16 bit, không phải 8 bit.
comonad

Bạn đúng. ai đó dường như đã chỉnh sửa câu trả lời ban đầu của tôi
Jayen

5

Không, đăng ký một đối tượng cũng mất một chút bộ nhớ. 100 đối tượng với 1 thuộc tính sẽ chiếm nhiều bộ nhớ hơn.


4

Câu hỏi sẽ là một câu hỏi rất rộng.

Nó phụ thuộc vào biến lớp hoặc bạn có thể gọi là trạng thái sử dụng bộ nhớ trong java.

Nó cũng có một số yêu cầu bộ nhớ bổ sung cho các tiêu đề và tham chiếu.

Bộ nhớ heap được sử dụng bởi một đối tượng Java bao gồm

  • bộ nhớ cho các trường nguyên thủy, theo kích thước của chúng (xem bên dưới để biết Kích cỡ của các kiểu nguyên thủy);

  • bộ nhớ cho các trường tham chiếu (mỗi 4 byte);

  • một tiêu đề đối tượng, bao gồm một vài byte thông tin "dọn phòng";

Các đối tượng trong java cũng yêu cầu một số thông tin "dọn phòng", chẳng hạn như ghi lại lớp, ID và các cờ trạng thái của đối tượng, chẳng hạn như liệu đối tượng có thể truy cập được không, hiện đang bị khóa đồng bộ hóa, v.v.

Kích thước tiêu đề đối tượng Java thay đổi trên jvm 32 và 64 bit.

Mặc dù đây là những bộ nhớ chính mà người tiêu dùng jvm cũng yêu cầu các trường bổ sung đôi khi như để căn chỉnh mã, v.v.

Kích cỡ của các loại nguyên thủy

boolean & byte - 1

char & ngắn - 2

int & float - 4

dài & đôi - 8


Độc giả cũng có thể tìm thấy bài viết này rất sáng: cs.virginia.edu/kim/publicity/pldi09tutorials/...
quellish


2

Trong trường hợp nó hữu ích với bất kỳ ai, bạn có thể tải xuống từ trang web của tôi một tác nhân Java nhỏ để truy vấn việc sử dụng bộ nhớ của một đối tượng . Nó cũng sẽ cho phép bạn truy vấn sử dụng bộ nhớ "sâu".


Điều này hoạt động rất tốt để có được ước tính sơ bộ về số lượng bộ nhớ mà (String, Integer)Guava Cache sử dụng, mỗi phần tử. Cảm ơn!
Steve K

1

không, 100 đối tượng nhỏ cần nhiều thông tin (bộ nhớ) hơn một đối tượng lớn.


0

Các quy tắc về lượng bộ nhớ được tiêu thụ phụ thuộc vào việc triển khai JVM và kiến ​​trúc CPU (ví dụ 32 bit so với 64 bit).

Để biết các quy tắc chi tiết cho SUN JVM, hãy kiểm tra blog cũ của tôi

Trân trọng, Markus


Tôi khá chắc chắn Sun Java 1.6 64bit, cần 12 byte cho một đối tượng đơn giản + 4 padding = 16; một đối tượng + một trường số nguyên = 12 + 4 = 16
AlexWien

Bạn đã tắt blog của bạn?
Johan Boulé

Không thực sự, Không chắc chắn các blog SAP bằng cách nào đó di chuyển. Hầu hết trong số đó có thể được tìm thấy ở đây kohlerm.blogspot.com
kohlerm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.