Jakie jest zużycie pamięci przez obiekt w Javie?


216

Czy przestrzeń pamięci jest zużywana przez jeden obiekt o 100 atrybutach taki sam jak 100 obiektów, z których każdy ma jeden atrybut?

Ile pamięci jest przydzielone dla obiektu?
Ile dodatkowego miejsca jest używane podczas dodawania atrybutu?

Odpowiedzi:


180

Mindprod zwraca uwagę, że nie jest to proste pytanie, na które należy odpowiedzieć:

JVM może swobodnie przechowywać dane w dowolny sposób wewnętrzny, duży lub mały endian, z dowolną ilością dopełnienia lub narzutu, chociaż prymitywy muszą zachowywać się tak, jakby miały oficjalne rozmiary.
Na przykład JVM lub natywny kompilator może zdecydować o przechowywaniu boolean[]64-bitowych porcji, takich jak BitSet. Nie musi ci to mówić, o ile program daje te same odpowiedzi.

  • Może przydzielić niektóre tymczasowe obiekty na stosie.
  • Może zoptymalizować niektóre zmienne lub wywołania metod całkowicie nieistniejące, zastępując je stałymi.
  • Może wersjonować metody lub pętle, tj. Kompilować dwie wersje metody, każda zoptymalizowana dla określonej sytuacji, a następnie zdecydować z góry, którą z nich wywołać.

Potem oczywiście sprzęt i system operacyjny mają wielowarstwowe pamięci podręczne, w pamięci podręcznej, SRAM, DRAM, zwykłym zestawie roboczym RAM i magazynie kopii zapasowych na dysku. Twoje dane mogą być powielane na każdym poziomie pamięci podręcznej. Cała ta złożoność oznacza, że ​​możesz tylko z grubsza przewidzieć zużycie pamięci RAM.

Metody pomiaru

Możesz użyć, Instrumentation.getObjectSize()aby uzyskać oszacowanie ilości miejsca zajmowanego przez obiekt.

Aby zwizualizować rzeczywisty układ obiektu, powierzchnię i odniesienia, możesz użyć narzędzia JOL (Java Object Layout) .

Nagłówki i odwołania do obiektów

W nowoczesnym 64-bitowym JDK obiekt ma 12-bajtowy nagłówek wypełniony wielokrotnością 8 bajtów, więc minimalny rozmiar obiektu to 16 bajtów. W przypadku 32-bitowych maszyn JVM narzut wynosi 8 bajtów i jest uzupełniony wielokrotnością 4 bajtów. (Z odpowiedzi Dmitrija Spikhalskiy jest , odpowiedź Jayen za i JavaWorld ).

Zazwyczaj odniesienia to 4 bajty na platformach 32-bitowych lub 64-bitowych -Xmx32G; i 8 bajtów powyżej 32 Gb ( -Xmx32G). (Zobacz odwołania do obiektów skompresowanych ).

W rezultacie 64-bitowa maszyna JVM zwykle wymaga o 30-50% więcej miejsca na sterty. ( Czy powinienem używać 32- lub 64-bitowej maszyny JVM ? , 2012, JDK 1.7)

Typy, tablice i ciągi pudełkowe

Opakowania w pudełkach mają narzut w porównaniu do typów pierwotnych (z JavaWorld ):

  • Integer: 16-bajtowy wynik jest nieco gorszy niż się spodziewałem, ponieważ intwartość może zmieścić się w zaledwie 4 dodatkowych bajtach. Korzystanie z Integerkosztuje mnie 300 procent narzutu pamięci w porównaniu do tego, kiedy mogę zapisać wartość jako typ pierwotny

  • Long: 16 bajtów również: Oczywiście rzeczywisty rozmiar obiektu na stercie podlega wyrównaniu pamięci niskiego poziomu, wykonywanemu przez określoną implementację JVM dla określonego typu procesora. Wygląda na to, że a Longto 8 bajtów narzutu obiektu, plus 8 bajtów więcej dla faktycznej długiej wartości. W przeciwieństwie do tego Integermiał nieużywany 4-bajtowy otwór, najprawdopodobniej dlatego, że JVM I używał siły wyrównania obiektu na 8-bajtowej granicy słowa.

Inne pojemniki też są kosztowne:

  • Tablice wielowymiarowe : oferuje kolejną niespodziankę.
    Deweloperzy często stosują konstrukcje, takie jak int[dim1][dim2]w obliczeniach numerycznych i naukowych.

    W int[dim1][dim2]instancji tablicy każda zagnieżdżona int[dim2]tablica jest Objectodrębna. Każdy z nich dodaje zwykły narzut 16-bajtowy. Kiedy nie potrzebuję tablicy trójkątnej lub poszarpanej, oznacza to czysty narzut. Wpływ rośnie, gdy wymiary tablicy znacznie się różnią.

    Na przykład int[128][2]instancja zajmuje 3600 bajtów. W porównaniu do 1 040 bajtów, z których int[256]korzysta instancja (która ma taką samą pojemność), 3600 bajtów stanowi narzut 246 procent. W skrajnym przypadku byte[256][1]narzut wynosi prawie 19! Porównaj to z sytuacją C / C ++, w której ta sama składnia nie powoduje narzutu pamięci.

  • String: Stringwzrost pamięci a śledzi wzrost wewnętrznej tablicy znaków. Jednak Stringklasa dodaje kolejne 24 bajty narzutu.

    W przypadku niepustych Stringznaków o rozmiarze 10 lub mniejszym, dodatkowy koszt narzutu w stosunku do użytecznego ładunku (2 bajty na każdy znak plus 4 bajty na długość), wynosi od 100 do 400 procent.

Wyrównanie

Rozważ ten przykładowy obiekt :

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
}

Naiwna suma sugerowałaby, że instancja Xużyłaby 17 bajtów. Jednak z powodu wyrównania (zwanego również wypełnianiem) JVM przydziela pamięć w wielokrotnościach 8 bajtów, więc zamiast 17 bajtów przydzieli 24 bajty.


int [128] [6]: 128 tablic 6 ints - w sumie 768 ints, 3072 bajty danych + 2064 bajty Narzut obiektu = łącznie 5166 bajtów. int [256]: 256 ints ogółem - dlatego nieporównywalne. int [768]: 3072 bajtów danych + 16 bajtów narzutu - około 3/5 miejsca w tablicy 2D - nie całkiem 246% narzutu!
JeeBee,

Ach, w oryginalnym artykule użyto int [128] [2] nie int [128] [6] - zastanawiam się, jak to się zmieniło. Pokazuje również, że skrajne przykłady mogą opowiedzieć inną historię.
JeeBee,

2
Narzut wynosi 16 bajtów w 64-bitowych maszynach JVM.
Tim Cooper

3
@AlexWien: Niektóre schematy wyrzucania elementów bezużytecznych mogą nakładać minimalny rozmiar obiektu, który jest niezależny od wypełnienia. Podczas wyrzucania elementów bezużytecznych, gdy obiekt zostanie skopiowany ze starej lokalizacji do nowej, stara lokalizacja może nie wymagać już przechowywania danych dla obiektu, ale będzie musiała zawierać odniesienie do nowej lokalizacji; może także wymagać zapisania odwołania do starej lokalizacji obiektu, w którym odkryto pierwsze odniesienie, i przesunięcia tego odniesienia w starym obiekcie [ponieważ stary obiekt może nadal zawierać odwołania, które nie zostały jeszcze przetworzone].
supercat

2
@AlexWien: Używanie pamięci w starym miejscu obiektu do przechowywania informacji o księgowości śmieciarza pozwala uniknąć konieczności przydzielania innej pamięci do tego celu, ale może narzucić minimalny rozmiar obiektu, który byłby większy niż byłby wymagany. Myślę, że co najmniej jedna wersja programu do czyszczenia pamięci .NET używa tego podejścia; z pewnością byłoby to możliwe również dla niektórych zbieraczy śmieci Java.
supercat

34

To zależy od architektury / jdk. W nowoczesnej architekturze JDK i 64-bitowej obiekt ma 12-bajtowy nagłówek i dopełnienie o 8 bajtów, więc minimalny rozmiar obiektu to 16 bajtów. Możesz użyć narzędzia o nazwie Java Object Layout, aby określić rozmiar i uzyskać szczegółowe informacje na temat układu obiektu i wewnętrznej struktury dowolnej encji lub odgadnąć te informacje na podstawie odwołania do klasy. Przykład danych wyjściowych dla liczby całkowitej w moim środowisku:

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

Zatem dla liczby całkowitej wielkość instancji wynosi 16 bajtów, ponieważ 4-bajtowe int są spakowane na miejscu tuż za nagłówkiem i przed granicą wypełnienia.

Przykładowy kod:

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

Jeśli używasz maven, aby uzyskać JOL:

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

28

Każdy obiekt ma pewien narzut związany z powiązanymi informacjami o monitorze i typie, a także z samymi polami. Poza tym pola można ułożyć praktycznie w dowolny sposób, jednak JVM uzna to za stosowne (uważam) - ale jak pokazano w innej odpowiedzi , przynajmniej niektóre maszyny JVM spakują się dość ciasno. Rozważ taką klasę:

public class SingleByte
{
    private byte b;
}

vs

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

W 32-bitowej maszynie JVM oczekiwałbym, że 100 wystąpień SingleBytezajmie 1200 bajtów (8 bajtów narzutu + 4 bajty dla pola z powodu wypełnienia / wyrównania). Spodziewałbym się, że jeden przypadek OneHundredByteszajmie 108 bajtów - narzut, a następnie 100 bajtów spakowanych. Z pewnością może się różnić w zależności od maszyny JVM - jedna implementacja może zdecydować, aby nie pakować pól OneHundredBytes, co powoduje, że zajmuje 408 bajtów (= 8 bajtów narzut + 4 * 100 wyrównanych / wypełnionych bajtów). Na 64-bitowej maszynie JVM narzut może być również większy (nie jestem pewien).

EDYCJA: Zobacz komentarz poniżej; najwyraźniej HotSpot uzupełnia 8 bajtowymi granicami zamiast 32, więc każde wystąpienie SingleBytezajmie 16 bajtów.

Tak czy inaczej „pojedynczy duży obiekt” będzie co najmniej tak samo wydajny jak wiele małych obiektów - w takich prostych przypadkach.


9
Właściwie jedno wystąpienie SingleByte zajmie 16 bajtów na Sun JVM, czyli 8 bajtów narzutu, 4 bajty na pole, a następnie 4 bajty na wypełnienie obiektu, ponieważ kompilator HotSpot zaokrągla wszystko do wielokrotności 8.
Paul Wagland

6

Całkowitą wykorzystaną / wolną pamięć programu można uzyskać w programie za pośrednictwem

java.lang.Runtime.getRuntime();

Środowisko wykonawcze ma kilka metod związanych z pamięcią. Poniższy przykład kodowania pokazuje jego użycie.

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

Wygląda na to, że każdy obiekt ma narzut 16 bajtów w systemach 32-bitowych (i 24-bajtowy w systemach 64-bitowych).

http://algs4.cs.princeton.edu/14analysis/ jest dobrym źródłem informacji. Jednym z wielu dobrych przykładów jest następujący.

wprowadź opis zdjęcia tutaj

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf jest również bardzo pouczający, na przykład:

wprowadź opis zdjęcia tutaj


„Wygląda na to, że każdy obiekt ma narzut 16 bajtów w systemach 32-bitowych (i 24-bajtowy w systemach 64-bitowych)”. To nie jest poprawne, przynajmniej dla obecnych JDK. Spójrz na moją odpowiedź na przykład liczby całkowitej. Narzut obiektu wynosi co najmniej 12 bajtów dla nagłówka dla systemu 64-bitowego i nowoczesnego JDK. Może być bardziej ze względu na wypełnienie, zależy od faktycznego układu pól w obiekcie.
Dmitry Spikhalskiy,

Drugi link do samouczka o wydajnej pamięci Java wydaje się martwy - otrzymuję komunikat „Zabronione”.
tsleyson

6

Czy przestrzeń pamięci jest zużywana przez jeden obiekt o 100 atrybutach taki sam jak 100 obiektów, z których każdy ma jeden atrybut?

Nie.

Ile pamięci jest przydzielone dla obiektu?

  • Narzut wynosi 8 bajtów w wersji 32-bitowej, 12 bajtów w wersji 64-bitowej; a następnie zaokrąglana w górę do wielokrotności 4 bajtów (32-bit) lub 8 bajtów (64-bit).

Ile dodatkowego miejsca jest używane podczas dodawania atrybutu?

  • Zakres atrybutów wynosi od 1 bajtu (bajt) do 8 bajtów (długi / podwójny), ale odwołania to albo 4 bajty, albo 8 bajtów, w zależności nie od tego, czy jest to 32-bitowy czy 64-bitowy, ale raczej czy -Xmx wynosi <32 Gb lub> = 32 Gb: typowo 64 -bitowe maszyny JVM mają optymalizację o nazwie „-UseCompressedOops”, która kompresuje odwołania do 4 bajtów, jeśli wielkość stosu jest mniejsza niż 32 Gb.

1
char jest 16-bitowy, a nie 8-bitowy.
comonad

masz rację. wydaje się, że ktoś zredagował moją oryginalną odpowiedź
Jayen

5

Nie, rejestracja obiektu również zajmuje trochę pamięci. 100 obiektów z 1 atrybutem zajmie więcej pamięci.


4

Pytanie będzie bardzo szerokie.

To zależy od zmiennej klasy lub możesz wywoływać jako użycie pamięci stanów w java.

Ma także dodatkowe wymagania dotyczące pamięci dla nagłówków i odwołań.

Zawiera pamięć sterty używana przez obiekt Java

  • pamięć pól prymitywnych według ich wielkości (patrz poniżej Rozmiary typów pierwotnych);

  • pamięć pól referencyjnych (po 4 bajty);

  • nagłówek obiektu, składający się z kilku bajtów informacji o „sprzątaniu”;

Obiekty w Javie wymagają również pewnych informacji o „sprzątaniu”, takich jak rejestracja klasy obiektu, identyfikatora i flag statusu, takich jak to, czy obiekt jest osiągalny, czy obecnie jest synchronizowany itp.

Rozmiar nagłówka obiektu Java zmienia się w 32 i 64-bitowym Jvm.

Chociaż są to główne pamięci konsumenckie, Jvm wymaga również dodatkowych pól, takich jak wyrównanie kodu itp

Rozmiary pierwotnych typów

wartość logiczna i bajt - 1

char & short - 2

int & float - 4

długie i podwójne - 8


Czytelnicy mogą również uznać ten artykuł za bardzo pouczający: cs.virginia.edu/kim/publicity/pldi09tutorials/...
quellish



1

nie, 100 małych obiektów potrzebuje więcej informacji (pamięci) niż jeden duży.


0

Reguły dotyczące zużycia pamięci zależą od implementacji JVM i architektury procesora (na przykład 32-bitowe i 64-bitowe).

Szczegółowe zasady SUN JVM znajdują się na moim starym blogu

Pozdrawiam Markus


Jestem prawie pewien, że Sun Java 1.6 64bit potrzebuje 12 bajtów dla zwykłego obiektu + 4 wypełnienia = 16; obiekt + jedna liczba całkowita = 12 + 4 = 16
AlexWien

Czy zamknąłeś swojego bloga?
Johan Boulé

Nie bardzo, nie jestem pewien, czy blogi SAP jakoś się poruszyły. Większość z nich można znaleźć tutaj kohlerm.blogspot.com
kohlerm
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.