Caprica Software

vlcj 3.x Tutorial

Garbage Collection

Your application must keep hard references to the media player instances that you create. If you do not do this, your media player will become unexpectedly garbage collected at some future indterminate time and you will see a fatal crash eventually or immediately depending on how lucky you are.


Why?

A fatal crash occurs because in the background the media player that you created is running a native thread outside of your Java application and that native thread calls back in to a vlcj media player. If the vlcj media player has been garbage collected that native thread has nowhere to go so crashes.

You have to keep the hard reference to the media player because there is nothing else that keeps your media player reference alive - the native code knows nothing about Java applications.


When?

Consider this code:

BadCode.java
public class BadCode {

public static void main(String[] args) {
new BadCode(args[0]);

try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}

public BadCode(String mrl) {
AudioMediaPlayerComponent mediaPlayerComponent = new AudioMediaPlayerComponent();

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}

@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});

mediaPlayerComponent.getMediaPlayer().playMedia(mrl);
}

private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}

You don't need to understand the whole thing right now, but look at where the mediaPlayerComponent instance is created. It is a local variable inside the constructor. This code will run and the media will indeed start playing. The constructor will exit and the media player will keep playing for a while.

However, when the garbage collector runs there is nothing keeping the mediaPlayerComponent reference alive - it is no longer "reachable" so the media player instance is discarded and garbage collected even though it is still running!


How to Fix?

BetterCode.java
public class BetterCode {

private final AudioMediaPlayerComponent mediaPlayerComponent;

public static void main(String[] args) {
new BetterCode(args[0]);

try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}

public BetterCode(String mrl) {
mediaPlayerComponent = new AudioMediaPlayerComponent();

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}
@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});

mediaPlayerComponent().getMediaPlayer().playMedia(mrl);
}

private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}

Here we have moved the mediaPlayerComponent from being a method-local variable to being a class field variable. This means that the media player instance will still be reachable after the constructor exits, and seemingly our problem is solved.

But there is still a, less obvious, flaw with this code...

Look where the BetterCode instance is created - the instance is never actually assigned to a variable. As before, the media will play for a while but eventually (or maybe even shortly) the garbage collector will run and find that the class instance itself is no longer reachable so will discard it - when that happens, the mediaPlayerComponent instance in the class field variable also becomes no longer reachable and so it too is discarded. The end result is a fatal Java Virtual Machine crash.


How To Really Fix?

GoodCode.java
public class GoodCode {

private final AudioMediaPlayerComponent mediaPlayerComponent;

public static void main(String[] args) {
GoodCode goodCode = new GoodCode(args[0]);

try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}

public GoodCode(String mrl) {
mediaPlayerComponent = new AudioMediaPlayerComponent();

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}

@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});

mediaPlayerComponent.getMediaPlayer().playMedia(mrl);
}

private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}

We must keep a reference to the class instance too, here the goodCode variable retains a hard reference to our class instance.


Summary

This tutorial has explained the need for 'pinning' your media player instances in memory to prevent unwanted garbage collection.

There are different ways to achieve the same effect, do whatever works for your application, but the point is you must be aware of this.

Depending on your application you may not even have to do anything - for the common case of using an EmbeddedMediaPlayerComponent (as discussed in this series of tutorials so far) you usually have a JFrame instance that is keeping a reference to the media player component instance (you added the media player component to the frame) and this is enough to keep the instance alive and prevent it from being garbage collected.

Depending on a number of factors, like your operating system, your Java Virtual Machine version, your application Java Virtual Machine memory configuration and garbage collection configuration you may encounter this problem immediately, a short time later, or some considerable time later lulling you into a false sense of security. You must be aware of this issue.