A new example script using JAJMX API. This script, named lsthreads, connects to a remote JVM using just the host adress and the used JMX port, and then list all active threads and theirs current states. Some java options (JAVA_OPTS) have been added to allow using this script against itself.

#!/bin/sh
JAVA_OPTS=""
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.port=9999"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.authenticate=false"
JAVA_OPTS=$JAVA_OPTS" -Dcom.sun.management.jmxremote.ssl=false"
exec java $JAVA_OPTS -jar jajmx.jar -nocompdaemon -usejavacp -savecompiled "$0" "$@"
!#
import jajmx._
import javax.management.openmbean.CompositeData

val host = if (args.size>=1) args(0) else "localhost"
val port = if (args.size>=2) args(1).toInt else 9999

case class ThreadLock(name:String, ownerId:Option[Long], ownerName:Option[String])

case class ThreadInfo(name:String, id:Long, state:String,lock:Option[ThreadLock],
blockedCount:Long, waitedCount:Long)

object ThreadInfo {
def apply(ti:CompositeData) :ThreadInfo = {
val id = ti.get("threadId").toString.toLong
val name = ti.get("threadName").toString
val state = ti.get("threadState").toString
val blockedCount = ti.get("blockedCount").toString.toLong
val waitedCount = ti.get("waitedCount").toString.toLong
val lock = Option(ti.get("lockName")) map {_.toString} filter {_.size>0} collect {
case lockName =>
val lockOwnerId = Option(ti.get("lockOwnerId")) map {_.asInstanceOf[Long]} filterNot {_ == -1}
val lockOwnerName = Option(ti.get("lockOwnerName")) map {_.toString.trim}
ThreadLock(lockName, lockOwnerId, lockOwnerName)
}
ThreadInfo(name, id, state, lock, blockedCount, waitedCount)
}
}

JMX.once(host,port) { jmx =>
for (threading <- jmx.threading) {
val ids = threading.get[Array[Long]]("AllThreadIds")
val rawThreadsInfos = threading.call[Array[CompositeData]]("getThreadInfo", ids).get
val threadsInfos = rawThreadsInfos map {ThreadInfo(_)} toList
val countByState = threadsInfos groupBy {_.state} map { case (s,l) => s -> l.size}
val countByStateStr = countByState map {case (s,l) => s+":"+l} mkString " "
println("Total %d %s".format(threadsInfos.size, countByStateStr))
for ( ti <- threadsInfos sortBy {_.id } ) {
println("%d - %s - %s".format(ti.id, ti.state, ti.name) )
}
}
}

Usage examples, with itself and then with a remote apache tomcat:

$ lsthreads
Total 13 TIMED_WAITING:4 WAITING:2 RUNNABLE:7
1 - RUNNABLE - main
2 - WAITING - Reference Handler
3 - WAITING - Finalizer
4 - RUNNABLE - Signal Dispatcher
9 - RUNNABLE - RMI TCP Accept-0
10 - RUNNABLE - RMI TCP Accept-9999
11 - RUNNABLE - RMI TCP Accept-0
12 - RUNNABLE - RMI TCP Connection(1)-127.0.0.1
13 - TIMED_WAITING - RMI Scheduler(0)
14 - TIMED_WAITING - RMI RenewClean-[127.0.0.1:45376]
15 - TIMED_WAITING - GC Daemon
16 - RUNNABLE - RMI TCP Connection(2)-127.0.0.1
17 - TIMED_WAITING - JMX server connection timeout 17

$ lsthreads 192.168.1.10 1099
Total 17 TIMED_WAITING:6 WAITING:2 RUNNABLE:9
1 - RUNNABLE - main
2 - WAITING - Reference Handler
3 - WAITING - Finalizer
4 - RUNNABLE - Signal Dispatcher
10 - RUNNABLE - RMI TCP Accept-0
11 - RUNNABLE - RMI TCP Accept-1099
12 - RUNNABLE - RMI TCP Accept-0
13 - TIMED_WAITING - GC Daemon
16 - TIMED_WAITING - ContainerBackgroundProcessor[StandardEngine[Catalina]]
17 - RUNNABLE - http-bio-8080-Acceptor-0
18 - TIMED_WAITING - http-bio-8080-AsyncTimeout
19 - RUNNABLE - ajp-bio-8009-Acceptor-0
20 - TIMED_WAITING - ajp-bio-8009-AsyncTimeout
22 - RUNNABLE - RMI TCP Connection(5)-127.0.0.1
23 - TIMED_WAITING - RMI Scheduler(0)
24 - RUNNABLE - RMI TCP Connection(6)-127.0.0.1
27 - TIMED_WAITING - JMX server connection timeout 27