Question about System.gc();

I’ve been trying to figure out how to play .mid files without using a ton of RAM for the past two days and I’ve finally found some code that worked well. After a few tests and some profiling I noticed that the used RAM just kept going up and up so I decided that System.gc(); might fix this problem, it did.

My question is… Is it ok for me to be using System.gc(); like this? I’ve read that most people don’t know how to properly use it and that there shouldn’t be any need to use it so I’m not too sure if I should be using it. Using System.gc(); did take the used RAM from 60-130+mb before being reduced to a constant 35-40mb before being reduced.

Method that uses System.gc();

public void meta(MetaMessage event) {
    if (event.getType() == END_OF_TRACK_MESSAGE)
    {
      if(sequencer != null && sequencer.isOpen() && loop) 
      {
        sequencer.setMicrosecondPosition(0);
        sequencer.start();
        System.gc();
        System.out.println("Test1");
      }
      else
      {
          sequencer.stop();
          sequencer.close();
          System.out.println("Test");
      }
    }
  }

Whole class.

package Core;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;

public class MidiPlayer implements MetaEventListener {

  // Midi meta event
  public static final int END_OF_TRACK_MESSAGE = 47;

  private Sequencer sequencer;

  private boolean loop;

  private boolean paused;

  /**
   * Creates a new MidiPlayer object.
   */
  public MidiPlayer() {
    try {
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequencer.addMetaEventListener(this);
    } catch (MidiUnavailableException ex) {
      sequencer = null;
    }
  }

  /**
   * Loads a sequence from the file system. Returns null if an error occurs.
   */
  public Sequence getSequence(String filename) {
    try {
      return getSequence(new FileInputStream(filename));
    } catch (IOException ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Loads a sequence from an input stream. Returns null if an error occurs.
   */
  public Sequence getSequence(InputStream is) {
    try {
      if (!is.markSupported()) {
        is = new BufferedInputStream(is);
      }
      Sequence s = MidiSystem.getSequence(is);
      is.close();
      return s;
    } catch (InvalidMidiDataException | IOException ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Plays a sequence, optionally looping. This method returns immediately.
   * The sequence is not played if it is invalid.
   */
  public void play(Sequence sequence, boolean loop) {
    if (sequencer != null && sequence != null && sequencer.isOpen()) {
      try {
        sequencer.setSequence(sequence);
        sequencer.start();
        this.loop = loop;
      } catch (InvalidMidiDataException ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * This method is called by the sound system when a meta event occurs. In
   * this case, when the end-of-track meta event is received, the sequence is
   * restarted if looping is on.
   */
  @Override
  public void meta(MetaMessage event) {
    if (event.getType() == END_OF_TRACK_MESSAGE)
    {
      if(sequencer != null && sequencer.isOpen() && loop) 
      {
        sequencer.setMicrosecondPosition(0);
        sequencer.start();
        System.gc();
        System.out.println("Test1");
      }
      else
      {
          sequencer.stop();
          sequencer.close();
          System.out.println("Test");
      }
    }
  }

  /**
   * Stops the sequencer and resets its position to 0.
   */
  public void stop() {
    if (sequencer != null && sequencer.isOpen()) {
      sequencer.stop();
      sequencer.setMicrosecondPosition(0);
    }
  }

  /**
   * Closes the sequencer.
   */
  public void close() {
    if(sequencer != null && sequencer.isOpen()) {
      sequencer.close();
    }
  }

  /**
   * Gets the sequencer.
   */
  public Sequencer getSequencer() {
    return sequencer;
  }

  /**
   * Sets the paused state. Music may not immediately pause.
   */
  public void setPaused(boolean paused) {
    if(this.paused != paused && sequencer != null && sequencer.isOpen()) {
      this.paused = paused;
      if (paused) {
        sequencer.stop();
      } else {
        sequencer.start();
      }
    }
  }

  /**
   * Returns the paused state.
   */
  public boolean isPaused() {
    return paused;
  }
}

Hm. I wouldn’t suggest using System.gc(). It’s horribly slow, since it simply forces to delete all garbage, which exists. This is bad since usually the GC puts all the garbage into several buckets (yeah, the GC is sorting the garbage ^^). So the garbage, which exists longer dies slowly. The objects which are allocated all the time again and again are garbaged frequently.

You can think of the JVM’s GC techneque like this: Take up as much memory as I can get. Do the least possible garbagecollections.
Don’t worry about your program taking 100mb+ memory. If the JVM hasn’t got that much disk space available, it simply doesn’t use so much.
I’ve noticed my programs to use up to 200 times as much memory then after GC’ing (not even talking about heap ^^).

The only thing you can do about it is probably reduce the heap space you give the JVM using ‘-Xmx’ and ‘-Xms’ vm parameters, though I wouldn’t suggest that either.

TL;DR: Don’t worry. And System.gc() is slow. Trade Memory for less CPU time.

System.gc() does not guarantee that a garbage collection will take place at that moment. It simply “suggests” it to the JVM, so the GC may or may not actually happen. From the api:

“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.”

Dang! You didn’t buy that RAM for it to actually be used by anything did you?! :stuck_out_tongue:

Seriously, unused RAM is a sign of an unoptimized system. Let the JVM and the OS manage the RAM for you - it will do a damn sight better job of it.

Take @matheus23’s suggestion for command line options to control heap size if you really feel you must. Just don’t use System.gc() - as well as the arguments already put, a full GC is likely to play havoc with MIDI / audio timing too.

You should read this thread I made once: So I use System.gc() manually, is it really that bad ?

The basic things were mentioned:

  • dont use it as a general rule
  • and its just a suggesting, dont a command per se

also the garbage will be collected automatically and if needed anyway.

That being said, I do use it in out big game. Whenever a new map is loaded, I call System.gc(). Because if you have map change / loading screen, that is really the best time to this, so I say “yeah go ahead and clean out the garbage now, if its convenient” aka System.gc() - because generally I would prefer it to happen during the map change and not during the game which can potentially cause a short lag… which I’m 99% sure wont happen, but you know shrug

so thats the only place to use this

if you feel that a certain place would be really nice for a gc() but its not necessary, and this line of code isnt reached too often - go ahead.

Be also aware that the use is “officially” discouraged, meaning “findBugs” and of course freaking “PMD” which are code analysis tools will warn about its usage.
I think findbugs says “should only be used in debug builds”

Where you know you’re discarding a lot of previous state and there’s a natural pause in your app like a loading screen, then and only then it does make some sense to call System.gc. As for System.gc() being just a suggestion, it’s a strong suggestion. Think of it more like a signal: unless it has really good reason to ignore it, such as a gc signal already pending, or not having promoted anything since the last gc, you can be pretty sure it’s going to run – just not synchronously, that is, when System.gc() returns, nothing has happened yet.

Calling it too often defeats the purpose of the GC though. If you’re finding gc pauses are taking too much time when they do happen, you’re better off tuning things like CMS initiating occupancy fraction.

Well, it looks like it is synchronoulsy, which is why I think it’s generally a bad Idea to use it. From the docs:

(Blah blah, I’m sure this was more than only 10% text per quote, blah blah)

If you want to keep garbage piles low and prefer short but frequent garbage collections, use the G1:
-XX:+UseG1GC
-XX:MaxGCPauseMillis=[n]

There might also be this parameter needed, depending on the JDK version:
-XX:+UnlockExperimentalVMOptions

Still not managed to get that to perform any better (or even as good as) -Xincgc for me. Must test again sometime though.

The max pause setting may work with other garbage collectors too.

First of all, trust gc.
Second of all, if the gc explicitly isn’t the problem, trust gc.

When indeed the gc gets to be a bottleneck (like say 60 second full collection and then another one), then there are a few different gc’s to try out. Everyone having some or lots of parameters to tweak it.
But gc tweaking should be done on already ready and released application, it is highly dependent on the application itself, it’s client side modification and can vary significantly between different versions of java.
It’s a science in itself.

Basically trust gc and write Your code :slight_smile:

Edit: typos, oh my

Aside… something rarely noticed but often the cause of serious strange pauses is use of Soft/Weak references and finalizers, which are orders of magnitude slower than plain GC.

Cas :slight_smile:

using finalize(), Soft References, or Weak References causes the referent object to always miss one gc cycle, and only be collected by slower the slower gc methods.

PhantomReferences don’t have this problem, but you do have to do extra work to maintain the association to the referent since the phantomref by design won’t.