4

I have an ImageWrapper class that saves images to temporary files in disk in order to free heap memory, and allows reloading them when needed.

class ImageWrapper {
    File tempFile;
    public ImageWrapper(BufferedImage img) {
        // save image to tempFile and gc()
    }
    public BufferedImage getImage() {
        // read the image from tempFile and return it.
    }
    public void delete() {
        // delete image from disk.
    }
}

My concern is, how to make sure that files gets deleted when such ImageWrapper's instance is garbage collected (otherwise I risk filling the disk with unneeded images). This must be done while the application is still running (as opposed to during-termination cleanup suggestions) due to the fact that it is likely to run for long periods.

I'm not fully familiar with java's GC concept, and I was wondering if finalize() is what I'm looking for. My idea was to call delete() (on a separate Thread, for that matters) from an overriden finalize() method. Is that the right way to do it?

UPDATE:

I don't think I can close() the object as suggested by many users, due to the fact that each such image is fetched to a list of listeners which I don't control, and might save a reference to the object. the only time when I'm certain to be able to delete the file is when no references are held, hence I thought finalize() is the right way. Any suggestions?

UPDATE 2:

What are the scenarios where finalize() will not be called? If the only possibles are exiting the program (in an expected/unexpected way), I can take it, because it means I risk only one unneeded temp file left un deleted (the one that was processed during exiting).

Community
  • 1
  • 1
Elist
  • 5,313
  • 3
  • 35
  • 73
  • Don't go with the `finalize` approach; it really doesn't buy you anything the already mentioned in the answers below and comes with its own set of headaches. The simplest solution would be to have a separate directory in the `TMP` directory for your app. When your app starts up, clean up that directory. This will only be required if you JVM dies out in a non-clean fashion. In all other cases (JVM exits normally), `deleteOnExit` should work out fine. – Sanjay T. Sharma Aug 04 '13 at 17:03
  • The application is likely to run for long periods (days), and save images to disk all the time. Deleting these files must be done *during runtime*. – Elist Aug 04 '13 at 17:13
  • I think that's an important detail which you missed out when framing your original question. Anyways, in that case, you should go with the `PhantomReference` approach as mentioned in some of the answers below. – Sanjay T. Sharma Aug 04 '13 at 17:20
  • I edited my question to note that detail. – Elist Aug 04 '13 at 17:35

6 Answers6

6

Another approach is to use File.deleteOnExit() which marks a file for the JVM to delete upon exit. I realise it's not quite what you're looking for, but may be of interest.

To be clear, if your JVM dies unexpectedly, it won't clear those files. As such, you may want to architect your solution to clear up cache files on startup, such that you don't build up a mass of unused cache files over time.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • That's actually interesting. I think of a combination of the two (for cases where `finalize` was skipped for some reason). I couldn't understand from the link - is it guaranteed for unexpected terminations? – Elist Aug 03 '13 at 21:30
  • Of course, as you said yourself, it is alone not enough, because I want to clean the disk while the application is still running. I will have to inspect both ways (`finalize` and `deleteOnExit`) further, as I don't feel I got proper details regarding them... – Elist Aug 03 '13 at 22:45
3

It is not recommended to use finalize().The problem is that you can't count on the garbage collector to ever delete an object. So, any code that you put into your class's overridden finalize() method is not guaranteed to run.

Java Panter
  • 307
  • 2
  • 12
  • Can you be more specific about case where `finalize` might not be called? Do you think it should be a real concern in my case? – Elist Aug 03 '13 at 22:41
  • 2
    It is not somthing that works in some case only it is that you can't never know if the GC will run before you get out of memory.The finalize() method for any given object might run, but you can't count on it, so don't put any essential code into your finalize() method. In fact, it is recommend that in general you don't override finalize() at all. – Java Panter Aug 04 '13 at 07:41
3

An good alternative to finalize is the PhantomReference. the best way to use it is:

public class FileReference extends PhantomReference<CachedImage> {
  private final File _file;

  public FileReference(CachedImage img, ReferenceQueue<CachedImage> q, File f) {
    super(img, q);
    _file = f;
  }

  public File getFile() {
    _file;
  }
}

Then use it like:

public class CachedImage {

    private static final ReferenceQueue<CachedImage> 
            refQue = new ReferenceQueue<CachedImage>();

    static {
        Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    while (true) {
                        FileReference ref = (FileReference)refQue.remove();
                        File f = ref.getFile();
                        f.delete();
                    }
                } catch (Throwable t) {
                    _log.error(t);
                }
            }
        };
        t.setDaemon(true);
        t.start();
    }

    private final FileReference _ref;

    public CachedImage(BufferedImage bi, File tempFile) {
        tempFile.deleteOnExit();
        saveAndFree(bi, tempFile);
        _ref = new FileReference<CachedImage>(this, refQue, tempFile);
    }
    ...
}
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • I'm struggling with that for hours.. Everything works perfect (`remove()` in `while(true)` rather then `poll()` in scheduled execution), but when I try to get rid of the `Map` and use my extended class, references just won't enqueue... Could there be a reason for this method creating extra strong references? Any other idea? – Elist Aug 04 '13 at 20:37
  • @Elist - you'd have to show your actual code. did you accidentally make your FileReference class an inner class? – jtahlborn Aug 04 '13 at 21:20
  • At first I did... but then I realized it's probably the problem. Anyway, re factoring it out didn't solve it – Elist Aug 04 '13 at 21:59
  • I don't want to post anymore code, because I don't think that's the place for it. thought you might have some insights about it. maybe I'll post anew question later. – Elist Aug 04 '13 at 22:01
  • As concluded in [another question I've asked](http://stackoverflow.com/questions/18065725/why-wont-my-objects-die?noredirect=1#comment26442262_18065725) about using `ReferenceQueue`, I do need some collection to keep the `PhantomReference`s from getting GC'ed themselves before having the chance to get enqueued. hence, the Map (discussed in your comment below) is necessary. – Elist Aug 06 '13 at 06:03
  • 1
    @Elist - no, you don't need another collection, you just need to keep the FileReference in the CachedImg. – jtahlborn Aug 06 '13 at 12:43
  • That I didn't think of. Cleaver. That (and the non inner class issue discussed before) is the kind of reasons I asked you to make your answer SSCCE to get excepted... – Elist Aug 06 '13 at 13:05
  • 1
    @Elist - yep. i thought that putting the pieces together would be pretty straightforward. anyway, here's the SSCCE. – jtahlborn Aug 06 '13 at 13:54
  • One more thing - is it somehow possible that Java compiler ignore _ref for being private with no use or getter? – Elist Aug 06 '13 at 14:04
  • @Elist - no, that's not possible. – jtahlborn Aug 06 '13 at 14:15
2

There's no guarantee that your finalize method will ever get called; in particular, any objects hanging around when the program exits are usually just thrown away with no cleanup. Closeable is a much better option.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
0

As an alternative to @Brian Agnew's answer, why not install a ShutdownHook that clears out your cache directory?

public class CleanCacheOnShutdown extends Thread {
  @Override
  public void run() { ... }
}

System.getRuntime().addShutdownHook(new CleanCacheOnShutdown());
Thorn G
  • 12,620
  • 2
  • 44
  • 56
  • It's possible, but I don't see any advantages. `deleteOnExit()` doesn't force me to maintain a list of used temporary files (I prefer using `File.createTempFile()` that makes sure that the files will be, at some point, deleted by the OS, then using my own cache). – Elist Aug 04 '13 at 14:52
0

I ended up using a combination of File.deleteOnExit() (thanks @Brian), and a ScheduledExecutorService that goes over a ReferenceQueue of PhantomReferences to my class instances, according to this post. I add this answer because no one suggested using ReferenceQueue (which I think is the ideal solution for my problem), and I think it will be helpful for future readers.

The (somewhat simplified) outcome is this (changed the class name to CachedImage):

public class CachedImage {
    private static Map<PhantomReference<CachedImage>, File> 
            refMap = new HashMap<PhantomReference<CachedImage >, File>();
    private static ReferenceQueue<CachedImage> 
            refQue = new ReferenceQueue<CachedImage>();

    static {
        Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(new Thread() {
            @Override
            public void run() {
                try {
                    Reference<? extends CachedImage> phanRef = 
                            refQue.poll();
                    while (phanRef != null) {
                        File f = refMap.get(phanRef);
                        f.delete();
                        phanRef = refQue.poll();

                    }
                } catch (Throwable t) {
                    _log.error(t);
                }
            }
        }, 1, 1, TimeUnit.MINUTES);
    }

    public CachedImage(BufferedImage bi, File tempFile) {
        tempFile.deleteOnExit();
        saveAndFree(bi, tempFile);
        PhantomReference<CachedImage> pref = 
                new PhantomReference<CachedImage>(this, refQue);
        refMap.put(pref, tempFile);
    }
    ...
}
Community
  • 1
  • 1
Elist
  • 5,313
  • 3
  • 35
  • 73
  • this version is not thread-safe. see my answer for a better usage of PhantomReference. – jtahlborn Aug 04 '13 at 16:56
  • Why would I be bothered of Thread safety issues with `PhantomReference`s? What can possibly happen if the object is long gone anyway? – Elist Aug 04 '13 at 17:19
  • i never said the issue was with the PhantomReference. the issue is with your shared HashMap (which is unnecessary anyway, as you can see in my answer). also, you have a memory leak, since you never clean out the HashMap either. – jtahlborn Aug 04 '13 at 17:21
  • +1 for pointing out the advantages in extending `PhantomReference`. and hence the redundancy of the `Map`. If you could only edit your answer as SSCCE (for sake of completeness), it will be accepted. – Elist Aug 04 '13 at 17:39