Can't release PImage memory!

edited October 2016 in Questions about Code

For some reason every image i use on my sketch stays in memory untill i close the sketch. Even if i null-reference all of the variables, the Garbage Collection can't release them.

I've came across with a lot of forum posts about it and all suggested g.removeCache( PImage) but this doesn't work. Also, even if i don't use image() in my sketch, it still consumes a lot of memory. Just comment it out and you will see that loadImage() puts the image on the Heap Memory and even if you null reference the img variable it will not make a difference in the memory usage.

(for this sketch i'm using an 40mb .png i've created in mspaint)

`PImage img;

void setup() {
    size(200, 200);
    img = loadImage("big.png");
}

void draw() {
    clear();

    if(img!=null){
      image(img, 0, 0);
      g.removeCache( img );
      img = null;
      println("cleaned");
    };
    System.gc();

}

void keyPressed(){
   if(key=='q'){
     g.removeCache( img );
     img = null;
   }
} `

Also, System.gc() doesn't work. How can i release those images from the memory?

Answers

  • edited October 2016

    @Henri -- Interesting. Thank you for documenting clearly. If nobody has a solution, perhaps report this as an issue to Processing Issues....

  • edited October 2016 Answer ✓

    I'm not sure what you're describing is a bug. Consider this program:

    void setup() {
      size(200, 200);
      println(totalMem());
    }
    
    void draw() {
      PImage img = loadImage("Astronaut-EVA.jpg");
      image(img, 0, 0, width, height);
    
    
      if(frameCount % 10 == 0){
        int percent = (int)(100*(double)usedMem()/totalMem());
        println(percent + "%");
      }
    
    }
    
    public long totalMem() {
      return Runtime.getRuntime().totalMemory();
    }
    
    public long usedMem() {
      return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }
    

    Here I'm loading a 7MB file every single frame, then measuring the memory consumption. If there really was a bug where PImage memory was never reclaimed, I would expect this to run out of memory pretty fast. However, I end up with the "sawtooth" usage that is perfectly normal for a Java application:

    If I add a call to g.removeCache(img); and System.gc(); at the end of the draw() function, then memory usage is 41% for the entire duration of the program.

    Keep in mind that System.gc() only suggests that garbage collection be done. If you aren't running out of memory, then this isn't a bug, and Java is functioning as expected.

    Also note that according to the Processing source code, the image cache is a WeakHashMap, so you don't need to do anything special to clear the cache. It will be cleared if you're running low on memory. If you aren't running low on memory, then it won't be cleared. This is exactly the behavior I would expect.

    If you can create a program that runs out of memory (which it should do at 256 MB), then that's probably a bug and we can go from there.

  • edited October 2016

    Indeed KevinWorkman, this sketch doesn't run out of memory.

    I have been using windows Task Manager to keep track of memory. I tried adding totalMem() and usedMem() on my code and indeed it shows a different information than Task Manager.

    Processing sketch: prints 90%, then after img = null it prints 32%.

    Task Manager: Goes up to 1.465 MB of Ram and program start and stays there until sketch is closed.

        `PImage img;
    
        void setup() {
            size(200, 200);
            img = loadImage("big.png");
        }
    
        void draw() {
            clear();
            if(img!=null){
              image(img, 0, 0);
            }
    
            //after 20 seconds, clear the image.
            //If your computer takes too long to load the image, you might want to increase this time
            if(millis()>20000 && img!=null){
              g.removeCache( img ); 
              //System.gc(); //if you use this here, memory goes from 90% to 61%
              img = null;
              println("cleaned");
            };
    
           System.gc(); //if you use this here, memory goes from 90% to 32% !
    
            int percent = (int)(100*(double)usedMem()/totalMem());
            println(percent + "%");
    
        }
    
        public long totalMem() {
          return Runtime.getRuntime().totalMemory();
        }
    
        public long usedMem() {
          return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        }`
    

    Is this expected behaviour? I use mostly Processing and i've never used "vanilla" Java.

    Also, indeed without both g.removeCache() and System.gc()the sketch keeps printing 91%. And curously enough if i use System.gc() only once memory only drops to 61%. I must use it outside the for(){} in order for memory to drop to 31%. (see code, Lines 18 and 23). Is this also expected behaviour?

  • edited October 2016

    As mentioned, Java language doesn't guarantee gc() to work all the time.
    But in my experience, gc() does an "honest" effort to get rid of all orphan objects when called. [-O<

  • Java hides a lot of the underlying memory management from you, and for the most part that's a good thing. But that can make it hard to answer questions like "is this expected behavior?"

    Basically, if you aren't running out of memory, then Java is working as expected. Looking too closely at how much memory is being used at any particular time can be a bit of a wild goose chase.

    Java will run the garbage collector whenever it damn well pleases at regular intervals and whenever it detects that you're about to run out of memory. You don't have any control over it, and you can't make any predictions about it other than it'll run the garbage collector before it runs out of memory.

    That being said, it's not like Java will just eat up all your memory. By default, Java is limited to taking up 256 MB of memory, and whenever it starts to reach that limit, it will run the garbage collector. Worrying about a few MB of memory in either direction isn't really worth your time.

    I think everything is working exactly as intended in this case.

  • If I remember correctly System. gc() does not perform garbage collection it simply requests the Java runtime to do it when convenient.

  • As mentioned, my experience w/ gc() is that it simply works all the time.
    Of course the docs insist it's not guaranteed. But in practice it does! \m/

  • From the Java API, emphasis mine:

    Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

    A lot of factors go into garbage collection, and at the end of the day we can't be absolutely positive what it's doing. Notice that in my dumb tests, calling System.gc() actually causes the overall program to use more memory than just letting Java handle it. So it's not as simple as saying that this method definitely does or does not run garbage collection.

    At the end of the day, the moral of the story is the same: if you aren't running out of memory, don't worry about it.

  • I've did some tests and indeed i'm not running out of memory. But out of curiosity

    • why Window's task manager shows a different memory consumption value? If i, for ex, open another app like Photoshop that uses a lot of memory, would it be able to use the released memory?

    • why the difference between using System.gc() in lines 18 and 23 gives different results?

  • why Window's task manager shows a different memory consumption value? If i, for ex, open another app like Photoshop that uses a lot of memory, would it be able to use the released memory?

    It depends, and honestly "the Java answer" is that you shouldn't really care about this low-level stuff. What exactly do you mean that the task manager shows a different memory consumption value? What is the value, and what is it different from?

    If I understand the question, then the answer is yes, once memory is freed up via garbage collection then the computer can use that memory for other stuff. It's a little more complicated than that, but basically garbage collection is doing its job and you shouldn't have to worry about it.

    why the difference between using System.gc() in lines 18 and 23 gives different results?

    Again, System.gc() only makes a suggestion, so you shouldn't worry too much about exact numbers. But I will point out that line 18 happens before you set img to null, so img can't be garbage collected yet.

  • Using 2.2.1 and getting out of memory when i run sketch. basically it asks the user to grab an image. The image is then loaded with loadImage() After 1000's of draw loops , i get out of memory and sketch halts with error messages in console. Is the auto memory management in Java the same in this version or better in later versions of Processing? The program uses an event counter to allow only one iteration of each condition in the draw loop. Step 1 - wait for user to request image grab Step 2 - loadImage and display with image() Step 3- perform image processing Step 4- report numeric results ( count all pixels below threshold) Reset event counter to 1

    I inserted a wait time of 1 second to auto initiate the loop and it runs for hours and finally gives out of memory So is the garbage collector not working in theis version or what?

    Thanks

  • So this is NOT true?

    My initial reasoning was that :

    PImage screenImage;

    was defined as a global variable, and so each time I used:

    screenImage = loadImage("screen.png"); inside draw

    the new image would be loaded into the screenImage variable memory space, not take new memory!

  • edited April 2018

    ... the new image would be loaded into the screenImage variable memory space, ...

    Variables (also fields & parameters) AREN'T objects! Non-primitive variables simply store a value (4 or 8 bytes) which represents the 1st memory address of a contiguous block of memory (an object).

    More than 1 variable (or even array slots) can point to the same object, becoming aliases to it.
    That is, they'd all store the same memory address value.

    If an object doesn't have any variable, field or array slot storing its memory address value, it becomes eligible to be garbage-collected at some point in time later.

Sign In or Register to comment.