Frage wg. Speicherplatz in JAVA

  • Themenstarter Themenstarter Shlyakh
  • Beginndatum Beginndatum
S

Shlyakh

Guest
Ich habe wieder eine JAVA-Frage, an dieser Stelle möcte ich mich auch sehr herzlich nochmal für alle Tipps bedanken, die mir bis jetzt hier zuteil wurden. Nun habe ich folgenden Beispiecode:


Code:
class Beispielklasse{

}

public class Main{

 public static void main(String[] args){

for(int i=0;i<11;i++){

    new Beispielklasse(); 

}

for(int i=0;i<11;i++){

Beispielklasse m = new Beispielklasse();

} 

}
  
}
Ich habe zwei for-Schleifen und diese werden jeweils 10mal durchlaufen, und in der ersten Schleife erzeuge ich 10 Instanzen von Beispielklasse und weise sie als Referenz einem Objekt"Beispielklasse" zu.

In der 2. for-Schleife erzeuge ich keine Referenz(en).

Was mich einmal interessieren würde: Ist das 2. Speicherplatzschonender? Also wenn ich die beiden for-Schleifen nicht 10mal sondern n mal durchlaufen würde (und n ist eine sehr sehr hohe natürliche Zahl), würde ich dann wegen der zweiten eher einen OutOfMemoryError bekommen?
 
  1. Die Schleifen werden 11-mal durchlaufen
  2. In der ersten Schleife weist du die neuen Objekt nicht irgendwo zu, kannst sie also auch nicht verwenden. Ich vermute mal ganz stark, dass da der garbage collector zuschlägt und deine Instanzen in die ewigen Jagdgründe schickt
  3. In der zweiten Schleife wird die Variable zwar zugewiesen, allerdings ständig überschrieben bzw. verlässt du den Gültigkeitsbereich der Variable -> und schon wieder schlägt der böse garbage collector zu
 
Danke - also was die obere der beiden Schleifen angeht, sehe ich das auch so. Im zweiten Fall weiß ich nicht genau, ob es nicht sein kann, dass die Referenzen, die (wie Du richtig bemerktest 11x gebildet werden) nicht eventuell an unterschiedlichen Stellen im Speicher abgelegt werden können (bevor der GC kommt), sodass sich (wenn ich mich nicht irre) sagen lässt, dass die 2. Schleife zu der Zeit, da sie durchlaufen wird, dann mehr Heap benötigt.
 
Im zweiten Fall weiß ich nicht genau, ob es nicht sein kann, dass die Referenzen, die (wie Du richtig bemerktest 11x gebildet werden) nicht eventuell an unterschiedlichen Stellen im Speicher abgelegt werden können (bevor der GC kommt), sodass sich (wenn ich mich nicht irre) sagen lässt, dass die 2. Schleife zu der Zeit, da sie durchlaufen wird, dann mehr Heap benötigt.

Nach der zweiten Runde schon existiert im Speicher ein Objekt, auf das es keine Referenz mehr gibt (nämlich das Objekt Beispielklasse aus Runde 1). Der Standard Garbage Collector wird diesen Speicher dann wieder freigeben, da du in Java nun nicht mehr auf dieses Objekt zugreifen kannst, weil die Referenz fehlt. Anders sieht es in folgendem Fall aus:

Code:
ArrayList<Beispielklasse> l = new ArrayList<Beispielsklasse>();
for(int i=0;i<10;i++) {
    l.add(new Beispielklasse());
}
sowie

Code:
public class Beispielklasse {
    private BigObject _bO; 
    public static main() {
        this._bO = new BigObject();
    }
    public void doSomethingWithBO() {
        // ....
    }
    public void doSomethingWithoutBO() {
        // ....
    }
}
Mit diesem Code füllst du den Speicher mit BigObjects an, bis du irgendwann an die Speichergrenzen stößt - auch, wenn du die BigObjects vielleicht garnich brauchst. Doch aufgrund der Strong References in diesem Code wird der GC die Objekte niemals entfernen.

Aus diesem Grund gibt es in Java WeakReferences, mit denen du z.B. dem den GC ein Stück weit steuern kannst, d.h. er daf auch Objekte auch dann entfernen, wenn du auf dieses Objekt nicht mehr zugreifst und obwohl noch eine Referenz darauf besteht. Z.B.:

Code:
public class Beispielklasse {
    private WeakReference<BigObject> _bO; 
    public static main() {
        this._bO = new WeakReference<BigObject>();
    }
    public void doSomethingWithBO() {
        BigObject bo = this._bO.get(); // could be null!
    }
    public void doSomethingWithoutBO() {
        // ....
    }
}

Wann der Garbage Collector den Speicher frei gibt, ist dir - sofern du den GC nichts selbst implementierst - nicht bekannt, was letzten Endes dein Vorteil ist, da du dich nun auch nicht darum kümmern musst.
 
Auch wenn 1 Std 'rumprobieren 10 Min. Manuallesen spart, würde ich erst mal einen Blick auf Java Garbage Collection Basics empfehlen ;)

PS:
Warum man möglichst auf solche spezifischen Annahmen über GC-Verhalten verzichten sollte ;) :
Chapter*2.*The Structure of the Java Virtual Machine
The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
Comparison of Java virtual machines - Wikipedia, the free encyclopedia (es gibt also auch nicht _die_ JVM mit _der_ GC-Implementierung ;) )
 
Stimmt, man kann ja in Linux auch mit dem -Xmx Flag den Heapspace beliebig erweitern, aber der Tipp mit den Weka References war Gold wert.
 
ggf. passt da SoftReference (Java Platform SE 7 ) noch besser - diese werden erst bei Speicherknappheit entfernt.

Btw, was möchtest Du überhaupt erreichen?
Ganz viele Objekte erstellen, ohne die wirklich zu brauchen - also quasi nur ganz oft den Konstruktor ausführen "schreit" förmlich nach "static" Method:
Method (computer programming) - Wikipedia, the free encyclopedia
Understanding Class Members (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

Außerdem ist die Sache mit "schnell mal das Verhalten mit einem Minimalbeispiel testen" nicht ganz so einfach - Stichwort JIT Compilation/Optimization ;):
Code:
class Bar{};

class Foo
{    
    public static void main(String[] args)
    {
	int n = Integer.parseInt(args[0]);
	for(int i=0;i<n;i++){
	    new Bar();
	}

	for(int i=0;i<n;i++){
	    Bar m = new Bar();
	} 
    }  
}
Code:
CDW@highlander-jr:~/projects/java % java -version
openjdk version "1.6.0_32"
OpenJDK Runtime Environment (build 1.6.0_32-b30)
OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
CDW@highlander-jr:~/projects/java % time -lh java Foo 2 |&  grep "size\|user"
	0.28s real		0.11s user		0.17s sys
     19164  maximum resident set size
       196  average shared memory size
        37  average unshared data size
       120  average unshared stack size
CDW@highlander-jr:~/projects/java % time -lh java Foo 2000 |&  grep "size\|user"
	0.28s real		0.11s user		0.18s sys
     19312  maximum resident set size
       119  average shared memory size
        19  average unshared data size
       128  average unshared stack size
CDW@highlander-jr:~/projects/java % time -lh java Foo 2147483647 |&  grep "size\|user"
	0.32s real		0.19s user		0.14s sys
     19768  maximum resident set size
       167  average shared memory size
        29  average unshared data size
       125  average unshared stack size
Sprich: es wird erkannt, dass der Code in der Schleife "nichts tut" und entsprechend optimiert. Bei "echtem", produktivem Code schaut's wieder ganz anders aus.
 
Nochmals vielen Dank für die Tipps. Wegen meinen Zielen - also ich habe ein Programm geschrieben, das eine mittlere menge an Dateien verarbeitet, wobei es diese zu diesem Zweck einlesen muss. Nun habe ich mir den Code mal durchgesehen, immer mit der Befürchutng im Hinterkopf, da könnte etwas sein, was bei einer größeren Datenmenge einen Memoryleak verursacht, also zum Beispiel wenn ich beim einlesen über einen BufferedReader zu viele Stringobjekte erzeuge oder nicht sparsam mit der substring()-Methode umgehe. Leider darf ich den Quellcode nicht posten.
 
Shlyakh hat gesagt.:
Leider darf ich den Quellcode nicht posten.
Quellcode muss auch nicht sein - aber ohne detailiertere Problembeschreibung kann man auch nur "allgemeine" Tipps geben ;)

- Nach Möglichkeit mehrmals mit realistischen Datenmengen laufen lassen und den (Peak)Verbrauch messen - das kann man je nach OS mit
mit time, top, ProcessExplorer (irgendwo unter Detailansicht: "Working set peak" Process Object: Core Services)
oder einfach nur "java -verbose:gc foo".

Wenn es ok ist, dann braucht man nichts weiter zu tun - schließlich sind kurzlebige Objekte nicht Neues und werden explizit berücksichtigt ;)
http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html#generations hat gesagt.:
generational collection exploits several empirically observed properties of most applications to minimize the work required to reclaim unused ("garbage") objects. The most important of these observed properties is the weak generational hypothesis, which states that most objects survive for only a short period of time.
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis hat gesagt.:
Escape analysis is a technique by which the Java Hotspot Server Compiler can analyze the scope of a new object's uses and decide whether to allocate it on the Java heap.

- ansonsten gilt:
Knuth hat gesagt.:
Premature optimization is the root of all evil
;)

D.h man sollte versuchen die Anwendung zu profilen, um festzustellen, was den Speicher "frisst":
VisualVM - Wikipedia, the free encyclopedia
Getting Started with VisualVM — Project Kenai
JProfiler (da sollte es eine Opensource-Lizenz geben: ej-technologies - Java Profiler, Java Installer Builder, Java Tools)
Eclipse + MAT
und NetBeans sollte das auch können:
https://netbeans.org/kb/articles/nb-profiler-uncoveringleaks_pt1.html

Das gibt einem zumindest eine grobe Richtung, wo man zuerst ansetzen sollte.

Und dann eben schauen, ob man es nicht geschickter lösen kann (die beste Optimierung ist natürlich ein geeigneteres Datenmodel/Algo - ein Blick auf Design Patterns - Wikipedia, the free encyclopedia lohnt sich auf jeden Fall).
Klassischer Ansatz wäre die Verwendung von Verweisen, statt Kopien (z.B statt eine Kopie der Zeile oder eins Substrings zurückzugeben, gibt man Start/Endposition zurück).
Ein Blick auf nio File I/O (Featuring NIO.2) (The Java™ Tutorials > Essential Classes > Basic I/O) lohnt sich auch.


Btw:
Apropos substring-Verwendung - da wird, je nach Version entweder nur ein Verweis auf den Originalstring samt Startposition + Länge oder eine Kopie zurückgegeben:
Changes to String in Java 7u6 - Java Performance Tuning Guide
Der Grund für die Änderungen war quasi der ungewollte Memoryleak - wegen dem Verweis im Substring konnte der Originalstring nicht freigegeben werden, was in den meisten Anwendungsfällen (wenige rel. kurze Teilstrings pro Zeile/Textblock) wohl kontraproduktiv war.
 
Zurück
Oben