Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

java-thread-killer

A replacement for Thread#stop() on JDK 20+, using JVMTI.

ThreadKiller.kill(thread) reproduces the same functionality. It asynchronously throws a ThreadDeath (or a Throwable of your choosing) into the target thread at its next safepoint; exactly what Thread#stop() did before JDK 20 removed the native plumbing behind it.

ThreadKiller.kill(someThread);                 // throws ThreadDeath, like the old stop()
ThreadKiller.kill(someThread, new MyException()); // or throw something custom

How it works

The removal of Thread#stop() took away the language-level API, but the underlying JVM capability to forcibly inject an exception into another thread is still very possible. It's just locked behind JVMTI's StopThread function and the can_signal_thread capability.

This means that a native JVMTI agent is required, not just a JNI library. Preferably, without JVM args being added. The process:

  1. On first use, ThreadKiller extracts its bundled native agent to a temp file.
  2. It attempts to self-attach this agent via the JDK's Attach API. This operation will fail unless the JVM was launched with -Djdk.attach.allowAttachSelf=true, which we don't want to require.
  3. If direct self-attach fails, a second, disposable JVM subprocess is spawned with that flag explicitly set. That subprocess performs an external attach against the original process - a different PID attaching to another PID isn't restricted at all.
  4. That attach loads the native library as a real JVMTI agent inside the original process, triggering Agent_OnAttach, where the library requests the can_signal_thread capability.
  5. From then on, ThreadKiller.kill() calls StopThread directly through the captured jvmtiEnv.

The native side is written in Rust, with JVMTI bindings generated via bindgen against real JDK header files.


Requirements

  • JDK 20+ at runtime. Thread#stop() still functions normally on JDK < 20, so if your runtime is below JDK 20, you should use it rather than attaching an agent to your runtime. ThreadKiller will not stop JDK < 20 from using it, but it will give a warning.

Supported Platforms

OS Architecture
Windows x86_64
Linux x86_64, aarch64
macOS x86_64, aarch64 (Apple Silicon)

A single jar ships with five native binaries; the correct one is selected and extracted automatically at runtime based on os.name and os.arch.


Installation

Maven

<repositories>
    <repository>
        <id>zenith-artifactory</id>
        <url>https://repo.zenithstudios.dev/artifactory/java-thread-killer</url>
    </repository>
</repositories>
 
<dependency>
    <groupId>me.redot</groupId>
    <artifactId>killer</artifactId>
    <version>1.0.1</version>
</dependency>

Gradle

repositories {
    maven { url = uri 'https://repo.zenithstudios.dev/artifactory/java-thread-killer' }
}
 
dependencies {
    implementation 'me.redot:killer:1.0.1'
}

Usage

import me.redot.killer.ThreadKiller;
 
Thread worker = new Thread(() -> {
    while (true) {
        // ...
    }
});
worker.start();
 
// Somewhere else, when you arbitrarily need it to stop:
ThreadKiller.kill(worker);

Known Limitations

  • A thread stuck in a tight, allocation-free native loop with no safepoints will not die until it happens to reach one.
  • The disposable-JVM attach fallback adds a one-time startup cost the first time ThreadKiller is touched in a process.
  • This is not a forward-compatible API. It relies on JVMTI agent behavior that, like Thread#stop() itself, could change or be restricted in a future JDK release.

Why Reintroduce Dangerous API?

Personally, I wanted to better-support a library of mine (TweakSuite), so I built this out. Aside from that, I'd argue that not all programs must be "safe". Sometimes it's fun, or makes perfect sense, for a program to be able to arbitrarily kill its subprocesses. Sometimes, Thread#interrupt or other intermittent-exception-throwing mechanics are not available.

In the case of TweakSuite, a user could run System.exit(0); and end the runtime. Comparatively, I'm not afraid of Thread#stop(). That said, TweakSuite has a safe implementation for stopping threads, which it calls before ever referencing ThreadKiller. The idea there, is to never have to reference ThreadKiller or load its agent. But it is still there if the user needs it.

About

A tool to arbitrarily kill Java threads on JDK 20+

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages