4

I noticed hundreds and thousands of threads running while debugging my javafx MediaPlayer application in Netbeans.

So I went to this simple demo code, ran it in the debugger, and again saw hundreds of threads while playing a single track.

Many threads remain running after the media player is stopped, some threads die.

What is the reason for so many threads running and how to reduce the number of running threads?

public class MediaExample extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    public void start(Stage primaryStage) throws MalformedURLException {
        File mediaFile = new File("assets/media/Golden-48569.mp4");
        Media media = new Media(mediaFile.toURI().toURL().toString());

        MediaPlayer mediaPlayer = new MediaPlayer(media);

        MediaView mediaView = new MediaView(mediaPlayer);

        Scene scene = new Scene(new Pane(mediaView), 1024, 800);
        primaryStage.setScene(scene);
        primaryStage.show();

        mediaPlayer.play();
    }
}

Environment: MacOS 11.7.10, Java JDK 24, javafx versions 21,24,25, Apache NetBeans IDE 25

Here is command line to run my application in Netbeans "/Applications/Apache NetBeans.app/Contents/Resources/netbeans/java/maven/bin/mvn" "-Dexec.vmArgs=--module-path '/Users/dev/javafx-sdk-24/lib' --add-modules javafx.controls,javafx.media,javafx.swing" "-Dexec.args=${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}" -Dexec.appArgs= -Dexec.mainClass=my.Test -Dexec.executable=/Library/Java/JavaVirtualMachines/24.jdk/Contents/Home/bin/java --no-transfer-progress process-classes org.codehaus.mojo:exec-maven-plugin:3.1.0:exec

The print screens below show over 10 000 running threads while playing a 4 minutes video and High Idle Wake Ups time 400 ms because JVM is switching between threads.

Over 10 000 running threads while playing a 4 minutes video

High Idle Wake Ups time 400 ms

JConsole

7
  • 4
    How are you determining there are "hundreds and thousands" of threads? Running your test application with my own MP4 file, I only see ~15 application-related threads. I checked via a debugger, jstack, and VisualVM. Commented Jun 16 at 6:43
  • 2
    In addition to providing the thread dump as text in the question, please edit the question to provide info on: Java, JavaFX, and OS versions, as well as the command line you use to run the application. Commented Jun 16 at 20:00
  • @Slaw JConsole (shipped with the Java JDK) shows 30 threads running and thousands of threads started. Note the G1 GC. Please check this issue in MAC OS. Commented Jun 17 at 21:37
  • I cannot reproduce this: OS X x64 (15.5), OpenJDK 24.0.1, JavaFX 24.0.1, test file bbb_sunflower_2160p_60fps_normal.mp4 played at 3840x2160. As Slaw did, I only see 16 threads, six related to the JVM, and ten related to JavaFX and JavaFX media. Commented Jun 18 at 1:08
  • If you want to pursue this further, you could look into the info at: jeyzer on analyzing thread dumps and virtual threads (in case those are involved). If the threads you see are virtual, a high thread count may not be an issue. When I ran my tests and analyzed using jcmd, I didn't see virtual threads being used, but I was just taking a snapshot in time. Commented Jun 18 at 1:18

2 Answers 2

2

TL;DR: Regarding the thread issue, best I can tell, it only looks like thousands of threads are being created from Java's perspective. What's really happening is that the same native thread is being repeatedly attached and detached via Java Native Interface (JNI).


Idle Wake Ups

I don't know what's causing the high Idle Wake Ups. Though is this actually a problem? Based on what I know about Idle Wake Ups, it seems normal that playing a video would cause a lot of them. But I don't know enough to give a legitimate answer.


Why "Thousands" Of Threads?

I can mostly reproduce what you're seeing. Connecting jconsole to a JavaFX application playing a video shows the TotalStartedThreadCount continuously increasing. The ThreadCount and PeakThreadCount values, however, are pretty much constant (the former fluctuates by one or two).

What I believe is happening is that JavaFX is continuously attaching and detaching a native thread via Java Native Interface (JNI). From some testing, doing that gives the following behavior:

  • When a native thread is attached and then detached, the next time it's attached the Java Thread is not given the same ID as before. Even when it's the same native thread each time.

  • If a native thread is attached without specifying a name then the Java Thread will be given a generated name. This generated name will be Thread-N, where N is an incrementing integer. Again, when a native thread is attached, detached, and then attached again, the Java Thread will not have the same name as before (in this case).

  • Each time a native thread is attached, the ThreadMXBean implementation considers that to be a "new started thread" and thus increments the "TotalStartedThreadCount" value.

Dumping the threads seems to support this. Executing the following:

jcmd <pid> Thread.print

In a loop and looking for threads with a name matching "Thread-[0-9]{2,}" eventually caught the following thread:

"Thread-28028" #56093 [8952] daemon prio=5 os_prio=0 cpu=1250.00ms elapsed=0.00s tid=0x000001802219ac00 nid=8952 runnable  [0x00000060f3ffc000]
   java.lang.Thread.State: RUNNABLE
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal([email protected]/AbstractQueuedSynchronizer.java:1577)
        at java.util.concurrent.LinkedBlockingQueue.signalNotEmpty([email protected]/LinkedBlockingQueue.java:177)
        at java.util.concurrent.LinkedBlockingQueue.offer([email protected]/LinkedBlockingQueue.java:423)
        at com.sun.media.jfxmediaimpl.NativeMediaPlayer$EventQueueThread.postEvent([email protected]/NativeMediaPlayer.java:742)
        at com.sun.media.jfxmediaimpl.NativeMediaPlayer.sendPlayerEvent([email protected]/NativeMediaPlayer.java:1453)
        at com.sun.media.jfxmediaimpl.NativeMediaPlayer.sendNewFrameEvent([email protected]/NativeMediaPlayer.java:1500)

The top of that stack is NativeMediaPlayer::sendNewFrameEvent. That method is being invoked from native code. In order for JNI to invoke Java methods, the current thread must be attached to the JVM. If I'm not mistaken, the native code responsible for invoking the Java method is here:

bool CJavaPlayerEventDispatcher::SendNewFrameEvent(CVideoFrame* pVideoFrame)
{
    LOWLEVELPERF_EXECTIMESTART("CJavaPlayerEventDispatcher::SendNewFrameEvent()");
    bool bSucceeded = false;

    CJavaEnvironment jenv(m_PlayerVM);
    JNIEnv *pEnv = jenv.getEnvironment();
    if (pEnv) {
        jobject localPlayer = pEnv->NewLocalRef(m_PlayerInstance);
        if (localPlayer) {
            // SendNewFrameEvent will create the NativeVideoBuffer wrapper for the java side
            pEnv->CallVoidMethod(localPlayer, m_SendNewFrameEventMethod, ptr_to_jlong(pVideoFrame));
            pEnv->DeleteLocalRef(localPlayer);

            bSucceeded = !jenv.reportException();
        }
    }

    LOWLEVELPERF_EXECTIMESTOP("CJavaPlayerEventDispatcher::SendNewFrameEvent()");

    return bSucceeded;
}

That involves creating and destroying a CJavaEnvironment. And if you look at that class's implementation, you'll see the constructor attaches the calling thread and the destructor detaches the calling thread. And my guess is this thread has something to do with GStreamer (the native framework used by JavaFX Media behind the scenes).

So, I wrote a small agent to instrument NativeMediaPlayer::sendNewFrameEvent and make it log the calling thread. I included both the Java Thread ID and the native thread ID (acquired via native code). The results showed a new Java Thread each time the sendNewFrameEvent method was called, but it was always the same native thread (i.e., the native thread ID never changed). This further supports that the "problem" has to do with repeatedly attaching and detaching native threads.

In other words, it only looks like thousands are being created from Java's perspective. But really it's just the same native each time. Now, that may be a bug in and of itself. My understanding is that a native thread should only be detached after it's no longer needed. Perhaps that wasn't possible in this case for some reason.

NetBeans

NetBeans would appear to be in error. It should not continue to show each Thread created by attaching a native thread after that native thread is detached. Even if that native thread is then attached again later (which results in a different Thread object). VS Code and VisualVM, at least, don't seem to have this problem.

Note DetachCurrentThread says a detached thread is considered terminated by the JVM (even if it continues to run in native):

Detaches the current thread from a Java VM. A thread cannot detach itself if there are Java methods on the call stack.

Any Java monitors still held by this thread are released (though in a correctly written program all monitors would have been released before this point). The thread is now considered to have terminated and is no longer alive [emphasis added]; all Java threads waiting for this thread to die are notified.

Sign up to request clarification or add additional context in comments.

17 Comments

A long Idle Wake Ups time in MAC OS Activity Monitor indicates a problem. I have never seen Idle Wake Ups within 200-400ms. This is probably caused by switching between threads. The strange threading issue also makes the Netbeans debugger unusable. The real problem will arise when the Player application is embedded in a network project that requires reasonable use of system resources and proper use of threads. I see defects in javafx that have not been solved in 10 years. I do not expect the development team to solve this threading issue right now, but R&D need to pay attention.
To double-check, I logged the ID (native and Java) of the thread calling sendNewFrameEvent. The native ID never changed, so I'm reasonably confident it only looks like thousands of threads are being created from Java's perspective, but they're all actually the same native thread. As for the issue you're seeing with Idle Wake Ups, I'm not sure about that. If you believe you found a bug with JavaFX then I suggest you submit a bug report. You can do the same for NetBeans.
I don't think filing a bug report makes sense given the 10 years fix lag. Anyway, I hope I helped a little.
The OpenJFX developers can't purposefully fix a problem they don't know about. You're correct that some bugs have existed for years. But some bugs are fixed every release. If you ever want the issue you found to be fixed, even if it takes a long time, then you should submit a bug report. Though note there's always the chance the OpenJFX developers don't think the issue is actually a problem and close the report as "won't fix". In that case, you can always contact the mailing list to provide your arguments.
By the way, Idle Wake Ups in VLC is 55-60, that's how it should be (the lower the better). It would be nice to know Idle Wake Ups in VLCj.
"A long Idle Wake Ups time in MAC OS Activity Monitor indicates a problem. I have never seen Idle Wake Ups within 200-400ms." -> "Idle Wake Ups" do not measure time. According to Google AI (I couldn't find Apple doc on this), it measures the number of wake-ups of the CPU from idle. On my system (OS X 15.5 x64), I have processes that regularly report hundreds or thousands of wake-ups, such as anti-virus scanners, kernel tasks, and development IDEs. A media player has to do something on each frame, so it may wake up, perform an action, and then sleep. That would seem a desirable feature.
I opened 5 media players in the program and Idle Wake Ups about 700+ ms. Look at it, it's a fundamental design problem.
Right now during the test the Idle Wake Ups value exceeded 900ms and the player app became unresponsive. Normal values ​​are 20-65, higher values ​​indicate a problem with threads management. Please look into it. Thank you
"the player app became unresponsive" -> that would likely be unrelated to idle wake-ups and due to some other issue. I don't know what the other issue is, but 5 media players may overload the system if playing back something like 5 4K streams simultaneously, though I'd expect it to drop frames rather than freeze the app). If you have issues with JavaFX media playback that you are unable to address, there are other options, such as vlcj-javafx bindings or launching the video in an external app (browser or other native media player).
|
0

I guess the reason is buffering. JavaFX uses a scene graph to manage the UI, which is not thread-safe. Any updates to the UI must be done on the JavaFX Application Thread. Background tasks (like media playback) may spawn additional threads to handle buffering and decoding media.

Use Platform.runLater() to update the UI from background threads safely.

Update to "The demo application above doesn't use threads":

MediaPlayer spawns its own threads to handle the media playback tasks. As well as JavaFX framework itself creates threads for internal operations. The garbage collector might create temporary threads.

Update, instead of merely using mediaPlayer.play(), use smth like following:

mediaPlayer.setAutoPlay(false);

mediaPlayer.setOnReady(() -> {
    mediaPlayer.play(); // start playback when media is ready
});

// handle media events
mediaPlayer.setOnEndOfMedia(() -> {
    // do smth
    mediaPlayer.stop(); // stop when the media ends
});

8 Comments

The demo application above doesn't use threads.
MediaPlayer spawns its own threads to handle the media playback tasks
Indeed, it does, see update
Creating hundreds or more threads is overkill, since no desktop or even server has that many hardware processors, and switching between threads takes much longer and performance degrades.
@SedJ601 sorry mate, i confused u with op, that is why deleted comment
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.