Threads RAM Problem

Kababär

Top Contributor
Hi,

ich will viele Bilder aus einem Internet laden und diese auf meine Festplatte speichern.
Allerdings habe ich das Problem, dass der Arbeitsspeicher langsam aber schnell voll wird.

jvm_entry_memory_leak.PNG

Ich benutze einen ThreadPoolExecutor, um mehrere Bilder gleichzeitig downloaden zu können.
Mein Verständnis vom TPE ist, dass man eine maximale Anzahl von Threads angeben kann, die zur gleichen Zeit arbeiten können. Beendete Threads werden entfernt.
Also habe ich das so implementiert

Code:
interpreteLines(list1, infos);
            interpreteLines(list2, infos);
            // ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREADS);
            int corePoolSize = 5;
            int maxPoolSize = 10;
            long keepAliveTime = 20;
            ExecutorService executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<>());
            for (int i = 0; i < infos.size(); i++) {
                Info inf = infos.get(i);
                if (!(inf.getUrl().toLowerCase().endsWith(".jpg") || inf.getUrl().toLowerCase().endsWith("jpeg"))) {
                    System.out.println(inf.getUrl() + " kein JP(E)G!");
                    continue;
                }
                DownloadRunner dr = new DownloadRunner(inf, i);
                executorService.execute(dr);
            }

            executorService.shutdown();
            while (!executorService.isTerminated()) {
            }
            System.out.println("\nAlles gedownloaded");

Ich öffne eine Textdatei, wo Informationen zu den Bildern gegeben sind. Diese lade ich über "interpreteLines". In der Schleife erstelle ich mir ein DownloadRunner, der das Bild von der URL (die im Info-Object "inf" steht) runterlädt und abschließend als BufferedImage auf die Festplatte schreibt. Danach wird das BufferedImage geflushed (mit flush()).
Geschrieben werden die Bilder über ImageIO.

Was ist hier falsch? Wie geht es richtig? Wie kriege ich den memory leak in den Griff?
Eine vergebliche Lösung war es, eine BlockingQueue zu definieren und diese an den TPE zu übergeben, statt einer neu initialisierten BQ. Dann in der Schleife die erstellen DownloaderRunner per "put(dr)" dem TPE anzureichern. Allerdings passiert dann gar nichts.

Wäre nett, wenn mir jemand helfen könnte.
 
Bist du mal mit nem Profiler durchgegangen?

Hohe RAM-Auslastung muss in Java kein Memory-Leak bedeuten.
Kann auch einfach sein, das der GC noch nicht aufgeräumt hat, weil genug frei ist, oder das irgendwann mal so viel Speicher brauchte, der GC den aber freigeräumt hat und der intern leer ist.

Wenn du das begrenzen willst, kannst du die Maximale Größe des Heaps setzten - Standardmäßig müsste das auf 1/4 deines RAMs begrenzt sein
 
Ne mit einem Profiler habe ich noch nie gearbeitet.. hab den VisualVM ausprobiert und der hängt sich bei der Kalibrierung mit einem Prozess auf.
Ein Eclipse Plug-in (VirtualJava) hat Probleme bei der Darstellung. In den Properties wird nichts angezeigt.

Der RAM nur für das Java Programm hat irgendwann 1,8 GB in Anspruch genommen bei vorhandenen 4GB. Da habe ich dann abgebrochen..
Mich wundert es, weil ich so gut wie nur lokale Variablen verwende.
 
ok, ich stelle soeben fest, dass das Problem auch beim sequentiellen Laufen passiert.

Der Code sieht circa so aus

Code:
class DownloadRunner implements Runnable {

    private Info inf;

    private int taskID;

    public DownloadRunner(Info inf, int taskID) {
        this.inf = inf;
        this.taskID = taskID;
    }

    public void run() {
        System.out.println(taskID + "is running " + inf.getUrl());
        try {
            String name = inf.getName() + "_" + inf.getFace_id() + "_" + inf.getImage_id() + ".png";
            // URL url = new URL(inf.getUrl());

            BufferedImage bi = readBuffImg(inf.getUrl());// ImageIO.read(url);
            if (bi == null) {
                System.out.println("no image ...");
                return;
            }
            Rectangle rectangle = bboxToRect(inf.getBbox());
            if (rectangle == null) {
                System.out.println("no rectangle");
                return;
            }
            BufferedImage biCropped = cropImage(bi, rectangle);
            if (biCropped != null) {
                String root = Downloader.root;

                createDirIfNotExists(root + Downloader.dest);
                File file = new File(root + Downloader.dest + name);
                if (file.exists())
                    file.delete();
               // paar Operationen auf das Bild anwenden und dann schreiben
              // try-catches, end of class
 
Ich vermute mal, der wird das BufferedImage noch nicht freigegeben haben.
Das sind unkomprimiert Breite x Höhe x 4 Byte --> bei einem einzigen HD Bild (1920x1080 Pixel) 8.294.400 Byte (= 8MB) an Payload. Dazu kommt Overhead für das Array, die einzelnen Integers, die Referenzen, whatever...
Das klingt jetzt nach wenig, ist es aber eig. gar nicht. Bei 100 Bildern hast du über einem GB verbraucht.
Und die JVM selbst braucht auch ein wenig RAM. Und da der Garbage Collector (du erzeugst ja ne Menge GC Pressure) auch nicht dauerhaft läuft, wird er wie @mrBrown sagte, einfach noch nicht aufgeräumt haben.

Außerdem cacht ImageIO meines Wissens Daten --> da wird auch einiges an RAM drauf gehen.
 
Der RAM nur für das Java Programm hat irgendwann 1,8 GB in Anspruch genommen bei vorhandenen 4GB.
Wenn die Theorien zum GC stimmen und es wirklich kein Memory-Leak ist sollte ihn eine Verkleinerung des zur Verfügung stehenden Speichers auf sagen wir 1 GByte ja zum Aufräumen zwingen und es sollte nicht zu einer ...OutOfMemory-Exception kommen. Hast du das schon mal probiert ?
 
Wenn die Theorien zum GC stimmen und es wirklich kein Memory-Leak ist sollte ihn eine Verkleinerung des zur Verfügung stehenden Speichers auf sagen wir 1 GByte ja zum Aufräumen zwingen und es sollte nicht zu einer ...OutOfMemory-Exception kommen. Hast du das schon mal probiert ?
OutOfMemoryException käme eh erst nach Freiräumen, die kann man nur mit mehr Speicher verhindern.
 
Nur so findet man von vorneherein schlechten Code.
Naja kommt auch ein bisschen aufs Gebiet an... wenn ich ein Programm für Flughäfen schreibe, darf die JVM doch bestimmt bisschen mehr als 512MB in Anspruch nehmen 😀

Also das Programm pendelt sich so bei 1,8GB bis 1,9GB ein. Hab das Programm auch mittlerweile fertig laufen lassen.. hab mich nur über die gigantische Zahl erschreckt.. Das ist fast mehr in Anspruch genommener Platz als bei einem aktuellen Videospiel..
Nach jedem Aufruf der obigen Methode rufe ich allerdings auch explizit "System.gc()" auf. Dachte eig. das wäre ein Appell an die JVM aufzuräumen. Scheint wohl so zuverlässig wie "Thread.yield()" zu sein, auch wenn das zwei total unterschiedliche Konzepte sind.
 
Tja da frag ich mich was du vor 10 Jahren gemacht hättest als 1GB RAM Standard war. Heutzutage wird einfach inflationär mit Speicher und CPU Speed 7mgegang3n einfach weil es da ist. Ganz ehrlich: eine Software die 2GB Speicher braucht muss für mich eine 3D Echtzeit Simulation mit AR und Ki sein. Jede andere Software kann ich so schreib3n das sie mit 512MB locker auskommt.

Gruß

Claus
 
Ist ja auch keine Kunst, wird dann halt nur langsamer...

Quak, wofür braucht man 512MB am Stück? Das sind 512 bildschirmfüllende JPEGs oder 512.000 Musiktitel oder oder oder. Auf jeden Fall ein hundertfaches von dem was ein Mensch, der den Computer immer noch bedient, aufnehmen kann. Ergo vollkommen sinnlos das alles gleichzeitig im RAM zu halten.
 
Naja kommt eben ganz auf das Anwendungsgebiet. Ein einfacher Taschenrechner wird nicht so viel RAM benötigen wie eine Anwendung zur Hautkrebs- oder Tumorklassifikation.
Ich denke bei letzterem sind mehr als 512MB im RAM zu verkraften 😀

Beim Machine Learning können ganz schnell mal eben die Kapazitäten privater Leistungsmaschinen aufgebraucht werden. Aber das ist ein anderes Thema.
Apropos, das eigentliche Thema ist zwar noch ungelöst, aber nicht mehr aktuell 😳
 
Nach jedem Aufruf der obigen Methode rufe ich allerdings auch explizit "System.gc()" auf. Dachte eig. das wäre ein Appell an die JVM aufzuräumen. Scheint wohl so zuverlässig wie "Thread.yield()" zu sein, auch wenn das zwei total unterschiedliche Konzepte sind.
Das ist nicht ohne Grund verpönt und richtet meist mehr schaden als Nutzen an😉

Quak, wofür braucht man 512MB am Stück? Das sind 512 bildschirmfüllende JPEGs oder 512.000 Musiktitel oder oder oder. Auf jeden Fall ein hundertfaches von dem was ein Mensch, der den Computer immer noch bedient, aufnehmen kann. Ergo vollkommen sinnlos das alles gleichzeitig im RAM zu halten.
Keine Ahnung was du für Musik hörst, aber ich bring in 512MB keine 100 Titel unter...

Ich find 512MB nicht sonderlich viel, n Programm darf durchaus mal mehr als 5% meines Speichers nutzen...
 
aber ich bring in 512MB keine 100 Titel unter.
Aber du kannst dir trotzdem immer nur einen anhören, oder eine begrenzte Anzahl von Bildern gleichzeitig anschauen.
n Programm darf durchaus mal mehr als 5% meines Speichers nutzen...
Ja weil du den grössten hast, du solltest aber bedenken dass dein Programm evtl. auch woanders laufen sollte ...
Und es deshalb durchaus sinnvoll ist sich über seinen Speicherverbrauch Gedanken zu machen. Und gerade du bist ja einer der an jedem Algorithmus noch ein paar Byte rausquetscht.
 
Nach jedem Aufruf der obigen Methode rufe ich allerdings auch explizit "System.gc()" auf. Dachte eig. das wäre ein Appell an die JVM aufzuräumen. Scheint wohl so zuverlässig wie "Thread.yield()" zu sein, auch wenn das zwei total unterschiedliche Konzepte sind.

Richtig! System.gc() schlägt der JVM lediglich vor, den GC aufzuräumen, erzwingt es aber nicht.
Letzendlich bestimmt die JVM immer noch selbst, wann der GC aktiv wird und wann nicht.
 

Zurück
Oben