20

In short: I have a thread which is finished running, but not garbage collected.

In long: See following example code:

public void saveSomething() {
    Thread thread = new Thread(new Runnable() {

         @Override
         public void run() {
             // heavy memory access (~400 MB), finishes after ~10sec
         }
    });
    thread.start();
}

Okay, so this thread gets started and finished after some time. During this time a lot memory is used, which is okay. But my problem is:

After the thread finished, the memory is not set free

And I don't know how I can ensure that. Finished threads, which are not used anymore, should get garbage collected as far as I'm informed; but this doesn't seem to happen here :(

See this screenshot from jvisualVM:

enter image description here

1: I trigger the thread start by calling saveSomething()

2: Thread was finished long ago (I saw it by debugging), and I pressed "Perform GC" in jvisualvm

As you can see, after I force the GC, everything is working like I want. But this has to happen automatically. How can I do that, what am I doing wrong?

If you need more infos, please ask. Note: It seems that after a day (hopefully shorter) the memory is back to normal, maybe the GC is just "slow" or rather not very frequently timed?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
BAERUS
  • 4,009
  • 3
  • 24
  • 39
  • 13
    It's not the *thread* that takes that much space - it's presumably whatever happens in `run`... and we have no idea what that is, or where you're keeping any references. Please show a short but complete program demonstrating the problem. It's not even clear that there *is* a problem, to be honest - just because the thread has completed doesn't mean there's a GC immediately... – Jon Skeet May 26 '15 at 08:41
  • 1
    What's wrong with `System.gc()`? – Bubletan May 26 '15 at 08:41
  • 6
    @Bubletan Calling `System.gc()` is widely regarded a bad practice - as there are no guarantees whatsoever that this call has any effect. It is nothing else but a "recommendation" to the JVM to consider running the GC now. So, relying on it to have a certain meaning is a bad idea. – GhostCat May 26 '15 at 08:44
  • Usually this means you still have active references to large region(s) of memory kicking about after each thread has finished, it might be useful to dump a couple of memory snapshots in VisualVM over a period of time and watch for objects that are retaining large amounts of memory. Also check any singletons you might have for member variables. – seanhodges May 26 '15 at 08:46
  • 1
    @Jägermeister Yes, I know that, but it still suggests a garbage collection to be done. If it doesn't do it for some reason, then it'll be done later automatically. – Bubletan May 26 '15 at 08:51
  • 1
    Thanks for all the helpful comments. I realized, that I was after a pseudo problem and actually everything is just fine. – BAERUS May 26 '15 at 09:15
  • @Bubletan: it can actually introduce huge (performance) problems... – Axel May 27 '15 at 09:32
  • @Axel Yes, if you use it wrongly without a good reason. – Bubletan May 27 '15 at 10:15
  • 1
    @Bubletan: Yes, and "I don't see GC happening when monitoring in jvisualvm" is not a good reason IMHO... – Axel May 27 '15 at 11:02

6 Answers6

34

Could it be that you are asking the wrong question? What I mean: is the fact that garbage collection is not happening "immediately" a problem for you?

I am pretty sure - when you start another such thread that needs a lot of memory, the GC will kick in all by itself.

If that "delay" is actually a problem for you, you might consider doing the "very deep dive" in order to understand how GC actually works for your version of the JVM; to then start using the many many command line options that exist to fine-tune GC behavior to your needs.

But if a "delay" in freeing up memory (to other Java objects) is not an issue; then don't start fixing it.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
22

Garbage collection doesn't happen as soon as the thread is finished. Garbage collection will happen when it happens. The fact that when you force garbage collection via visualvm, the thread and its resources are collected correctly implies everything is ok. If you wait long enough or do more things which consume the heap then eventually your thread will be GCed.

EDIT The only caveat to this is that a running thread, even with no references, will not be garbage collected.

SilentICE
  • 700
  • 8
  • 25
18

I agree with the previous answers. This program demonstrates the fact that the "problem" is not specific to thread creation. In general, gc is done when needed or when specifically requested.

public class Test {

  public static long memInUse(){
    Runtime r = Runtime.getRuntime();
    return r.totalMemory()-r.freeMemory();
  }

  public static void main(String[] args){
    System.out.println("Initial memory: "+memInUse());
    long[] bigArray = new long[1000000];
    System.out.println("Memory after array allocation: "+memInUse());
    long endTime = System.currentTimeMillis()+10000;
    while(System.currentTimeMillis() < endTime){
      System.out.println("While array exists: "+memInUse());
      try{
        Thread.sleep(1000);
      }catch(InterruptedException e){
        // Deliberately ignore the exception.
      }
    }
    bigArray = null;
    System.out.println("After null assignment: "+memInUse());
    endTime = System.currentTimeMillis()+10000;
    while(System.currentTimeMillis() < endTime){
      System.out.println("While array reference null: "+memInUse());
      try{
        Thread.sleep(1000);
      }catch(InterruptedException e){
        // Deliberately ignore the exception.
      }
    }
    System.gc();
    System.out.println("After gc: "+memInUse());
  }
}

Output:

Initial memory: 2684536
Memory after array allocation: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
While array exists: 10684552
After null assignment: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
While array reference null: 10684552
After gc: 1622584
Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
8

It's entirely possible that the JVM simply doesn't need to do a GC after the thread finishes as it has enough free heap anyway.

It'll only do a GC when it NEEDS to free up memory.

Personally, I wouldn't worry about it. As long as you know the memory will be cleared when a GC does happen, then it's fine.

If you really want to force it though, you'll have to do an explicit System.GC -either after the thread has finished, or as the last line in the run() method (assuming there are no further references to the large data at that stage).

Phil Anderson
  • 3,146
  • 13
  • 24
3
  1. Free memory is wasted memory. If you have, say, 2 GB of free RAM, then you could as well have 2 GB less. Freeing up memory isn't beneficial by itself, it won't automagically speed things up.

  2. Garbage collection is not free. Actually it can be quite CPU-intensive. The less often it happens, the better (from CPU load perspective).

With that in mind, you usually don't want garbage collector to kick in immediately when the thread is finished. If you don't need more RAM at the very moment, then there's no point to burn CPU cycles on GC. Most of the time you can trust JVM to run GC when it's needed.


Bonus: RAM that isn't used by programs can be used for cache by your OS. On Windows this part of RAM is shown as free, in Linux it appears as used, but actually it's somewhere in between. Cache is not necessary, but it can improve performance. OS will manage it automatically: when more free RAM is available, then more can be allocated for cache, and when you're short on memory, cache size is reduced. So sometimes having more free RAM can be good for performance, but most of the time you don't have to worry about it.

gronostaj
  • 2,231
  • 2
  • 23
  • 43
  • 2
    I have come across your #1 blanket statement quite often, but there is a significant qualifier to be made: *Wasted memory is wasted memory as well.* Your application very likely is not the only one running on a machine, so you *should* discipline your memory usage to reasonable levels. – DevSolar May 27 '15 at 07:41
  • 1
    It is important to distinguish between virtual memory and physical RAM, especially when discussing memory that is allocated but not being referenced. I am not sure which you are talking about in each use of the word "memory" in this answer. – Patricia Shanahan May 27 '15 at 07:49
1

Generally an object becomes eligible for garbage collection in Java on following cases:

1) All references of that object explicitly set to null e.g. object = null

2) Object is created inside a block and reference goes out scope once control exit that block.

3) Parent object set to null, if an object holds reference of another object and when you set container object's reference null, child or contained object automatically becomes eligible for garbage collection.

4) If an object has only live weak references via WeakHashMap it will be eligible for garbage collection.

There are methods like System.gc() and Runtime.gc() which is used to send request of Garbage collection to JVM but it’s not guaranteed that garbage collection will happen.

To answer your question. you need to force garbage collection.

System.gc ();
System.runFinalization ();

Other Example

public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        A a = new A("white");
        a = null;

        Runtime.getRuntime().gc();
    }
}

class A {
    private String color;

    public A(String color) {
        this.color = color;
    }

    @Override
    public void finalize() {
        System.out.println(this.color + " cleaned");
    }
}

Warning it is a terrible practice to use the garbage collector because the use of it could introduce an over load to the software that may be even worst than on the memory, the garbage collector has his own thread which is not possible to control plus depending on the algorithm used by the gc could take more time and is consider very inefficient, you should check your software if it worst with the help of the gc because it is definitely broke, a good solution must not depend on the gc.

BhandariS
  • 606
  • 8
  • 20