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.



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).
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.
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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With