Speicher und Speicherbelegung

Aus Byte-Welt Wiki
Version vom 8. März 2018, 19:56 Uhr von L-ectron-X (Diskussion | Beiträge)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springenZur Suche springen

Crashkurs Speicherbelegung

Hier mal eine kurze Einführung ins Thema Speicher bzw. Speicherbelegung in Java.

Version 1:

class MyInstance{

   private int var = 10;

}

Version 2:

class SharedVars{

   public static final int SHARED_VAR = 10;

}

class MyInstance{

   private int var = SharedVars.SHARED_VAR;

}

Variante 2 belegt nun (egal wieviele Instanzen von MyInstance du nun hast) nicht weniger Speicher als Variante 1, sondern mehr. Ist dir das bewusst?

Wenn wir über Speicherbedarf reden müssen wir nämlich erstmal unterscheiden zwischen den Werten, respektive den Typen, von denen wir reden: Es gibt primitve Typen (byte, short, int, long, float, double, char, boolean) und komplexe Typen (Alles andere, zB String).

Variablen eines primitiven Typs können gar nicht auf eine andere Variable verweisen! D.h. wenn du sowas hast wie int var = SharedVars.SHARED_VAR; dann wird der Wert von SHARED_VAR kopiert, und in die var-Variable gespeichert, welche je nach Typ nun x byte Speicher belegt, zusätzlich zu dem Speicher, den die Variable SHARED_VAR belegt.

Kurze Liste von Speicherbedarf für primitive Variablen:

  • byte = 1 byte (surprise ;))
  • short = 2 byte
  • int = 4 byte
  • long = 8 byte
  • float = 4 byte
  • double = 8 byte
  • char = 2 byte
  • boolean = nicht genau definiert (auf jeden Fall nicht 1 Bit, wie man annehmen könnte. Eher 1 byte)

Du hast also im Vergleich der obigen beiden Beispiele bei x Instanzen im ersten Fall x*4 byte Speicherbedarf, und im zweiten Fall (x*4 byte) + 1*4 byte (für SHARED_VAR) Wie gesagt: Nicht weniger Speicherbedarf, sondern mehr.

Wenn du dir nun Speicher einsparen willst, dann geht das nur wenn du lediglich Direkt-Zugriffe auf SHARED_VAR machst. D.h. dort wo der Wert verwendet werden muss, zB in nem Getter, musst du direkt SHARED_VAR ansprechen. Du darfst also keine Instanz-Variable dafür anlegen - entsprechend ist das zT etwas stranges Design, zB wenn wir von "Getter" reden, und es gar keine Instanz-Variable für den Wert gibt.

Das ist.. schwer, bzw komisch zu realisieren, aber da primitive Werte von Natur aus maximal 8 byte belegen können kann man da eigentlich nicht von Relevanz im Bezug auf den Speicher reden (Ein kleines Rechenbeispiel folgt am Ende).

Also das waren jetzt primitive Typen. Anders sieht es nun aus bei komplexen Typen:

Version 1:

class MyInstance{

   private Color var = new Color(0,0,0);

}

Version 2:

class MyInstance{

   public static final Color SHARED_COLOR = new Color(0,0,0);

}

class MyInstance{

   private Color var = SharedVars.SHARED_COLOR

}

Hier sparst du dir in Version 2 durchaus einiges an Speicher im Vergleich zu Version 1. Denn "var" speichert nun nur eine Referenz auf ein Objekt, das eigentliche Objekt gibt's nur einmal. Angenommen, das Objekt an sich belegt 20 byte.

Speicherbedarf wäre nun bei x Instanzen in Version 1: (x * 24/28 byte).

24/28 deshalb, weil die Referenz auf das Objekt an sich schon 4/8 byte belegt. (Das heißt die Variable). Ich bin mir hier nicht sicher welcher Wert stimmt, ich glaub es hängt vom Betriebssystem ab (32 oder 64 Bit System).

Version 2: (1 * 24/28 byte) + (x * 4/8 byte).

Also durchaus ein Speichergewinn, denn das eigentliche Objekt, was den meisten Speicher belegt speicherst du nur einmal, und hast daneben nur noch die paar wenigen 4/8 byte für die Referenzen. Den Overhead der Referenzen (x*4/8 byte) könntest du nun weiter wegoptimieren in dem du - analog zu dem was ich zu den primitiven Typen gesagt habe - gar keine Instanz-Variable für die Farbe speicherst, sondern überall Direkt-Zugriffe auf SHARED_COLOR hast. Du hast dann also tatsächlich nur 1*24/28 Byte, egal wieviele Instanzen. Aber das ist dann echt schon wieder übertrieben, denn die paar Referenz-Bytes belegen ja quasi nix.

Soweit verstanden?

Nun zu einem Spezialfall, der bei dir soweit ich das verstanden habe nun auch zutrifft: Und zwar wenn's um Strings geht. Java managed einen separaten Speicherbereich für String-Konstanten, namentlich "String Constant Pool" oder auch "String Literal Pool". Jeder String, der ohne new-Aufruf erzeugt wird, sondern direkt mit der ""-Schreibweise (so macht man es in der Regel ja auch) landet in diesem Pool. Das Schöne: Wo immer im Programm ein String auf diese Weise erzeugt wird, checkt Java erstmal ob es diesen String (d.h. mit der identischen Zeichenkette - Vorsicht: Case sensitive!) im Pool gibt. Wenn ja, wird kein neues String Objekt erzeugt, sondern die Referenz auf diese Konstante im internen Pool returned.

Also angeommen du hast nen String "Hallo". Speicherbedarf dafür ist 5 * 2 (5 chars) byte = 10 byte + Overhead für den String (speichert das intern in nem Array) ist...puh weiß ich gar nicht genau, sagen wir mal 25 byte.

class MyInstance{

   private String var = "Hallo";

}

Obwohl wir hier das eigentliche String-Objekt nicht in eine SHARED_VAR auslagern, die von allen Instanzen gemeinsam referenziert wird, passiert eben dies halt doch - nur intern. D.h. im Speicher liegt einmal das String-Objekt (25 byte), und jede Instanz referenziert es. Du hast nur noch (x*4/8) byte zusätzliche Speicherbelegung bei x Instanzen.

Also: Wenn es sich hier bei deinem für alle Instanzen gleichbleibenden Wert um einen String handelt, ist das ganze Thema eh gegessen! Du musst nur aufpassen dass du Strings nicht mit new-Operator erzeugst - aber es gibt so gut wie keinen Fall wo das Sinn machen würde.

Im Endeffekt gilt:

Wenn du dir Sorgen um den Speicher machst, dann rechne dir erstmal aus ob du in deinem Anwendungsfall überhaupt etwas optimieren musst. In der Regel muss man das nicht, denn man muss schon SEHR viele Instanzen erzeugen, oder SEHR komplexe Instanzen haben, damit man da in größere Wertebereiche kommt.

Kleines Rechnungs-Beispiel: Angenommen du hast Instanzen die jeweils ein double sowie ein int-Array der Größe 10 speichern:

class MyInstance{

   private double d = 0.0;
   private int[] array = new int[10];

}

1 double = 8 byte int[10] = 10 * 4 (int) byte = 40 byte + Referenz der Variable (Arrays an sich sind auch komlplex!) 4/8 byte = 44/48 byte - sagen wir dein Einfachheit halber 50 byte.

Also 50 + 8 = 58 byte, nochmal krass aufgerundet auf 100 byte (sicherheitshalber, soviel ist es aber auf keinen Fall). Aber wir sagen 100 byte pro Instanz. Wir gehen davon aus dass du da gar nix optimierst, d.h. keine geteilten Variablen bzw. Werte hast, auch wenn die Datensätze für jede Instanz die selben sind. Du müsstest nun 100000 (einhunderttausend!) Instanzen anlegen, damit du auf knapp unter 10MB kommst. 10MB - wieviel RAM hat der billigste Schrott-Rechner den du dir auf ebay für nen Lolli abziehen kannst? Und damit das klar ist: Wir reden hier von 100000 Instanzen, die gleichzeitig zu einem Zeitpunkt existieren - wir reden nicht davon wieviel Instanzen vllt über mehrere Stunden der Laufzeit des Programms erzeugt werden. Denn oft werden nicht mehr gebrauchte Daten auch wieder gelöscht.

--hdi 09.01.2012