Garbage Collection causes : MediaPlayer finalized without being released

After a lot of debugging I finally found what is causing this error! Garbage Collection!

I have a video playing in media view and in the background I am looking for new videos from a Rest API.

Every now and then I see the Garbage collection running:

02-22 13:14:57.969: D/dalvikvm(16888): GC_EXPLICIT freed 152K, 4% free 6746K/6979K, paused 2ms+2ms

And the straight after that:

02-22 13:14:57.969: W/MediaPlayer-JNI(16888): MediaPlayer finalized without being released

So I tested it by calling System.gc() every 5 seconds.

As soon as the first GC is called it happens!

02-22 13:19:47.813: D/dalvikvm(17060): GC_EXPLICIT freed 167K, 5% free 6745K/7047K, paused 2ms+2ms ---- I call GC
02-22 13:19:47.813: W/MediaPlayer-JNI(17060): MediaPlayer finalized without being released ---- VIDEO PLAY INTERRUPTED

Why does this happen? Can I prevent it?

Playing the video:

private void playMedia(int playListIndex) throws IOException {
        File mediadir = getDir("tvr", Context.MODE_PRIVATE);
        filelist = mediadir.listFiles();
        Log.i("media player", "play media!");
        String path = filelist[playListIndex].getAbsolutePath();
        FileInputStream fileInputStream = new FileInputStream(path);
        final Uri uri = Uri.parse(path);
        String filename = filelist[playListIndex].getName();
        if (filename.contains("image")) {
            imageView = (ImageView)findViewById(R.id.imageView);
            imageView.setVisibility(View.VISIBLE);
            imageView.setImageURI(uri);
            mHandler.postDelayed(new Runnable() {
                public void run() {
                    imageView.setVisibility(View.GONE);
                    imageView.setImageURI(uri);
                    onCompletion(null);
                }
            }, 4000);
        } else if (filename.contains("video")) {

            MediaPlayer pl = new MediaPlayer();
            pl.setOnCompletionListener(this);
            pl.setDisplay(holder);
            pl.setDataSource(fileInputStream.getFD());
            pl.prepare();
            pl.start();
        }
    }

And when it is done:

@Override
    public void onCompletion(MediaPlayer mp) {
        Log.i("media player", "play next please!");
        if (mp != null) {
            mp.release();
        }
//      play next video
        currentMedia++;
        if (currentMedia > playList.size() - 1) {
            currentMedia = 0;
        }
        try {
            playMedia(currentMedia);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

Solution 1:

I think this is because you create the media player within the scope of the method, therefore, when the method completes, it goes out of scope. This means there are no references, so is ok for garbage collection.

This means, it can be free'd by the GC before it has even called onCompletion, hence won't release before cleared. Instead, you need to store a reference to the media player as a member variable in your class.

Solution 2:

Java GC only manages memory. So when other resources are used (e.g. files, sockets, etc), you need to manually manage them. In the case of MediaPlayer, the documentation mentions that:

It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether.

So, when you are done with a MediaPlayer instance, you need to make sure you explicitly call release() on that instance. A good place to do this could be in a lifecycle method of the containing Activity, e.g. onDestroy(). Otherwise, when that MediaPlayer instance is eventually garbage collected (at some arbitrary time after you no longer reference it), the finalizer will notice that you never called release() and will output the warning you are seeing.