Odpowiedzi:
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 przechowywaniuboolean[]
64-bitowych porcji, takich jakBitSet
. 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.
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) .
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)
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żint
wartość może zmieścić się w zaledwie 4 dodatkowych bajtach. Korzystanie zInteger
kosztuje 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 aLong
to 8 bajtów narzutu obiektu, plus 8 bajtów więcej dla faktycznej długiej wartości. W przeciwieństwie do tegoInteger
miał 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 jakint[dim1][dim2]
w obliczeniach numerycznych i naukowych.W
int[dim1][dim2]
instancji tablicy każda zagnieżdżonaint[dim2]
tablica jestObject
odrę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órychint[256]
korzysta instancja (która ma taką samą pojemność), 3600 bajtów stanowi narzut 246 procent. W skrajnym przypadkubyte[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
:String
wzrost pamięci a śledzi wzrost wewnętrznej tablicy znaków. JednakString
klasa dodaje kolejne 24 bajty narzutu.W przypadku niepustych
String
znakó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.
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 X
uż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.
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>
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ń SingleByte
zajmie 1200 bajtów (8 bajtów narzutu + 4 bajty dla pola z powodu wypełnienia / wyrównania). Spodziewałbym się, że jeden przypadek OneHundredBytes
zajmie 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 SingleByte
zajmie 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.
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));
}
}
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.
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf jest również bardzo pouczający, na przykład:
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?
Ile dodatkowego miejsca jest używane podczas dodawania atrybutu?
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
Otrzymałem bardzo dobre wyniki z metody java.lang.instrument.Instrumentation wspomnianej w innej odpowiedzi. Dobre przykłady użycia można znaleźć w artykule Instrumentation Memory Counter z biuletynu JavaSpecialists i bibliotece java.sizeOf na SourceForge.
Jeśli jest to przydatne dla kogokolwiek, możesz pobrać z mojej witryny małego agenta Java do sprawdzania wykorzystania pamięci przez obiekt . Pozwoli ci również zapytać o „głębokie” wykorzystanie pamięci.
(String, Integer)
używanej przez pamięć podręczną Guava na element. Dzięki!
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