Skip to content

Instantly share code, notes, and snippets.

@ggeoffrey
Last active January 21, 2022 14:14
Show Gist options
  • Save ggeoffrey/478ffcdb18a4f52acbb1395f77c1b79d to your computer and use it in GitHub Desktop.
Save ggeoffrey/478ffcdb18a4f52acbb1395f77c1b79d to your computer and use it in GitHub Desktop.
How to find and kill a runaway JVM thread, at the REPL

How to find and kill a runaway JVM thread at the REPL

Problem

A thread I spawned is consuming resources, and I’d like to stop it, but I don’t have a reference to it.

Solution

(in-ns 'user)
(require 'clojure.pprint)

top-threads will list threads consuming the most cpu time.

(defn top-threads [& {:keys [order-by max]
                      :or   {order-by :cpuTime
                             max      10}}]
  (let [tmxb (java.lang.management.ManagementFactory/getThreadMXBean)]
    (->> (Thread/getAllStackTraces)
         (.keySet)
         (map (fn [thread] {:id       (.getId thread)
                            :cpuTime  (.getThreadCpuTime tmxb (.getId thread))
                            :userTime (.getThreadUserTime tmxb (.getId thread))
                            :name     (.getName thread)}))
         (sort-by order-by >)
         (take max)
         (clojure.pprint/print-table [:id :cpuTime :name]))))
#'user/top-threads

Run it:

(top-threads :order-by :cpuTime, :max 10)
| :id |    :cpuTime |                                              :name |
|-----+-------------+----------------------------------------------------|
|  90 | 16784392000 | nREPL-session-72e4b7b7-98de-4f27-ac66-dbdbb1fea918 |
|   1 |  9403514000 |                                               main |
|  44 |  5986613000 |                      clojure-agent-send-off-pool-1 |
|  54 |  3607202000 |           clojure.core.async.timers/timeout-daemon |
|  66 |  2686516000 |                               async-thread-macro-5 |
| 100 |  2441073000 |                                    pool-3-thread-1 |
| 111 |  2131574000 |                                   pool-3-thread-12 |
| 114 |  2070166000 |                                   pool-3-thread-15 |
| 112 |  1796734000 |                                   pool-3-thread-13 |
| 106 |  1741690000 |                                    pool-3-thread-7 |

You can get one with get-thread:

(defn get-thread [id]
  (some->> (.keySet (Thread/getAllStackTraces))
           (filter (fn [thread] (= id (.getId thread))))
           (first)))
#'user/get-thread

See what the thread is doing:

(clojure.pprint/pprint (.getStackTrace (get-thread 66)))
[[jdk.internal.misc.Unsafe park "Unsafe.java" -2],
 [java.util.concurrent.locks.LockSupport park "LockSupport.java" 211],
 [java.util.concurrent.locks.AbstractQueuedSynchronizer acquire "AbstractQueuedSynchronizer.java" 715],
 [java.util.concurrent.locks.AbstractQueuedSynchronizer acquireSharedInterruptibly "AbstractQueuedSynchronizer.java" 1047],
 [java.util.concurrent.CountDownLatch await "CountDownLatch.java" 230],
 [clojure.core$promise$reify__8524 deref "core.clj" 7111],
 [clojure.core$deref invokeStatic "core.clj" 2324],
 [clojure.core$deref invoke "core.clj" 2310],
 [clojure.core.async$fn__7156 invokeStatic "async.clj" 312],
 [clojure.core.async$fn__7156 doInvoke "async.clj" 301],
 [clojure.lang.RestFn invoke "RestFn.java" 410],
 [shadow.cljs.devtools.server.reload_classpath$watch_loop invokeStatic "reload_classpath.clj" 123],
 [shadow.cljs.devtools.server.reload_classpath$watch_loop invoke "reload_classpath.clj" 119],
 [shadow.cljs.devtools.server.reload_classpath$start$fn__19096 invoke "reload_classpath.clj" 148],
 [clojure.core.async$thread_call$fn__7239 invoke "async.clj" 484],
 [clojure.lang.AFn run "AFn.java" 22],
 [java.util.concurrent.ThreadPoolExecutor runWorker "ThreadPoolExecutor.java" 1136],
 [java.util.concurrent.ThreadPoolExecutor$Worker run "ThreadPoolExecutor.java" 635],
 [java.lang.Thread run "Thread.java" 833]]

And stop it if needed:

(.stop (get-thread 66))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment