jdk提供了接口用于获取所有线程的堆栈,
package thread;
import java.util.Map;
/**
* Created by lpmoon on 2019/4/21.
*/
public class ThreadT {
public static void main(String[] args) {
Map<Thread, StackTraceElement[]> threadStackTraceMap = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : threadStackTraceMap.entrySet()) {
System.out.println(entry.getKey());
for (StackTraceElement stackTraceElement : entry.getValue()) {
System.out.println(stackTraceElement);
}
System.out.println("\n");
}
}
}
运行上面的代码可以看到如下的输出,
Thread[main,5,main]
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Thread.java:1607)
thread.ThreadT.main(ThreadT.java:10)
Thread[Monitor Ctrl-Break,5,main]
java.util.Properties$LineReader.readLine(Properties.java:478)
java.util.Properties.load0(Properties.java:353)
java.util.Properties.load(Properties.java:341)
sun.net.NetProperties.loadDefaultProperties(NetProperties.java:70)
sun.net.NetProperties.access$000(NetProperties.java:41)
是不是跟jstack命令很像呢。getAllStackTraces通过调用native方法dumpThreads来获取堆栈数据。下面看一下这个方法是如何工作的(下面的内容基于Openjdk 8),
// Support for java.lang.Thread.getStackTrace() and getAllStackTraces() methods
// Return StackTraceElement[][], each element is the stack trace of a thread in
// the corresponding entry in the given threads array
JVM_ENTRY(jobjectArray, JVM_DumpThreads(JNIEnv *env, jclass threadClass, jobjectArray threads))
JVMWrapper("JVM_DumpThreads");
JvmtiVMObjectAllocEventCollector oam;
// Check if threads is null
if (threads == NULL) {
THROW_(vmSymbols::java_lang_NullPointerException(), 0);
}
objArrayOop a = objArrayOop(JNIHandles::resolve_non_null(threads));
objArrayHandle ah(THREAD, a);
int num_threads = ah->length();
// check if threads is non-empty array
if (num_threads == 0) {
THROW_(vmSymbols::java_lang_IllegalArgumentException(), 0);
}
// check if threads is not an array of objects of Thread class
Klass* k = ObjArrayKlass::cast(ah->klass())->element_klass();
if (k != SystemDictionary::Thread_klass()) {
THROW_(vmSymbols::java_lang_IllegalArgumentException(), 0);
}
ResourceMark rm(THREAD);
GrowableArray<instanceHandle>* thread_handle_array = new GrowableArray<instanceHandle>(num_threads);
for (int i = 0; i < num_threads; i++) {
oop thread_obj = ah->obj_at(i);
instanceHandle h(THREAD, (instanceOop) thread_obj);
thread_handle_array->append(h);
}
Handle stacktraces = ThreadService::dump_stack_traces(thread_handle_array, num_threads, CHECK_NULL);
return (jobjectArray)JNIHandles::make_local(env, stacktraces());
JVM_END
上面的代码看起来比较长,但是其实核心代码都在ThreadService::dump_stack_traces()。之前的代码主要做一些鲁棒性检查,比如线程数组不能为null、数组长度不能为0、数组内的数据必须是线程。
Handle ThreadService::dump_stack_traces(GrowableArray<instanceHandle>* threads,
int num_threads,
TRAPS) {
assert(num_threads > 0, "just checking");
ThreadDumpResult dump_result;
VM_ThreadDump op(&dump_result,
threads,
num_threads,
-1, /* entire stack */
false, /* with locked monitors */
false /* with locked synchronizers */);
VMThread::execute(&op);
// Allocate the resulting StackTraceElement[][] object
ResourceMark rm(THREAD);
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_StackTraceElement_array(), true, CHECK_NH);
ObjArrayKlass* ik = ObjArrayKlass::cast(k);
objArrayOop r = oopFactory::new_objArray(ik, num_threads, CHECK_NH);
objArrayHandle result_obj(THREAD, r);
int num_snapshots = dump_result.num_snapshots();
assert(num_snapshots == num_threads, "Must have num_threads thread snapshots");
int i = 0;
for (ThreadSnapshot* ts = dump_result.snapshots(); ts != NULL; i++, ts = ts->next()) {
ThreadStackTrace* stacktrace = ts->get_stack_trace();
if (stacktrace == NULL) {
// No stack trace
result_obj->obj_at_put(i, NULL);
} else {
// Construct an array of java/lang/StackTraceElement object
Handle backtrace_h = stacktrace->allocate_fill_stack_trace_element_array(CHECK_NH);
result_obj->obj_at_put(i, backtrace_h());
}
}
return result_obj;
}
jvm内部通过一个异步线程来完成堆栈的收集,收集到的数据存放在ThreadDumpResult中,拿到数据后封装成对应的返回值。异步任务的实现是VM_ThreadDump,下面我们看一下这个任务的定义,
class VM_ThreadDump : public VM_Operation {
private:
ThreadDumpResult* _result;
int _num_threads;
GrowableArray<instanceHandle>* _threads;
int _max_depth;
bool _with_locked_monitors;
bool _with_locked_synchronizers;
ThreadSnapshot* snapshot_thread(JavaThread* java_thread, ThreadConcurrentLocks* tcl);
public:
VMOp_Type type() const { return VMOp_ThreadDump; }
void doit();
bool doit_prologue();
void doit_epilogue();
};
VM_ThreadDump继承自VM_Operation。有一点需要注意的是evaluate_at_safepoint()返回的是true,这意味着执行这个任务的时候,所有其他线程需要进入到安全点,也就是说整个JVM会被hang住。该任务的逻辑在doit()方法中:
void VM_ThreadDump::doit() {
ResourceMark rm;
ConcurrentLocksDump concurrent_locks(true);
if (_with_locked_synchronizers) {
concurrent_locks.dump_at_safepoint();
}
if (_num_threads == 0) {
// Snapshot all live threads
for (JavaThread* jt = Threads::first(); jt != NULL; jt = jt->next()) {
if (jt->is_exiting() ||
jt->is_hidden_from_external_view()) {
// skip terminating threads and hidden threads
continue;
}
ThreadConcurrentLocks* tcl = NULL;
if (_with_locked_synchronizers) {
tcl = concurrent_locks.thread_concurrent_locks(jt);
}
ThreadSnapshot* ts = snapshot_thread(jt, tcl);
_result->add_thread_snapshot(ts);
}
} else {
// Snapshot threads in the given _threads array
// A dummy snapshot is created if a thread doesn't exist
for (int i = 0; i < _num_threads; i++) {
instanceHandle th = _threads->at(i);
if (th() == NULL) {
// skip if the thread doesn't exist
// Add a dummy snapshot
_result->add_thread_snapshot(new ThreadSnapshot());
continue;
}
// Dump thread stack only if the thread is alive and not exiting
// and not VM internal thread.
JavaThread* jt = java_lang_Thread::thread(th());
if (jt == NULL || /* thread not alive */
jt->is_exiting() ||
jt->is_hidden_from_external_view()) {
// add a NULL snapshot if skipped
_result->add_thread_snapshot(new ThreadSnapshot());
continue;
}
ThreadConcurrentLocks* tcl = NULL;
if (_with_locked_synchronizers) {
tcl = concurrent_locks.thread_concurrent_locks(jt);
}
ThreadSnapshot* ts = snapshot_thread(jt, tcl);
_result->add_thread_snapshot(ts);
}
}
}
doit方法会遍历所有线程,如果线程已经消失了,或者处于exiting状态,则会创建一个空的ThreadSnapshot放到返回值中。否则调用snapshot_thread获取对应线程的快照(堆栈信息)。
ThreadSnapshot* VM_ThreadDump::snapshot_thread(JavaThread* java_thread, ThreadConcurrentLocks* tcl) {
ThreadSnapshot* snapshot = new ThreadSnapshot(java_thread);
snapshot->dump_stack_at_safepoint(_max_depth, _with_locked_monitors);
snapshot->set_concurrent_locks(tcl);
return snapshot;
}
void ThreadSnapshot::dump_stack_at_safepoint(int max_depth, bool with_locked_monitors) {
_stack_trace = new ThreadStackTrace(_thread, with_locked_monitors);
_stack_trace->dump_stack_at_safepoint(max_depth);
}
void ThreadStackTrace::dump_stack_at_safepoint(int maxDepth) {
assert(SafepointSynchronize::is_at_safepoint(), "all threads are stopped");
if (_thread->has_last_Java_frame()) {
RegisterMap reg_map(_thread);
vframe* start_vf = _thread->last_java_vframe(®_map);
int count = 0;
for (vframe* f = start_vf; f; f = f->sender() ) {
if (f->is_java_frame()) {
javaVFrame* jvf = javaVFrame::cast(f);
add_stack_frame(jvf);
count++;
} else {
// Ignore non-Java frames
}
if (maxDepth > 0 && count == maxDepth) {
// Skip frames if more than maxDepth
break;
}
}
}
if (_with_locked_monitors) {
// Iterate inflated monitors and find monitors locked by this thread
// not found in the stack
InflatedMonitorsClosure imc(_thread, this);
ObjectSynchronizer::monitors_iterate(&imc);
}
}
snapshot_thread调用ThreadSnapshot的dump_stack_at_safepoint方法,ThreadSnapshot调用ThreadStackTrace的dump_stack_at_safepoint方法。ThreadStackTrace则会遍历当前线程的所有栈帧(javaVFrame)将其添加到StackFrameInfo中。
StackFrameInfo::StackFrameInfo(javaVFrame* jvf, bool with_lock_info) {
_method = jvf->method();
_bci = jvf->bci();
_class_holder = _method->method_holder()->klass_holder();
_locked_monitors = NULL;
if (with_lock_info) {
ResourceMark rm;
GrowableArray<MonitorInfo*>* list = jvf->locked_monitors();
int length = list->length();
if (length > 0) {
_locked_monitors = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<oop>(length, true);
for (int i = 0; i < length; i++) {
MonitorInfo* monitor = list->at(i);
assert(monitor->owner(), "This monitor must have an owning object");
_locked_monitors->append(monitor->owner());
}
}
}
}
StackFrameInfo主要记录了三个数据, 1、当前栈帧执行的方法 ->_method 2、对应的行数 -> _bci 3、该方法对应到类 -> _class_holder
有了StackFrameInfo我们就可以还原出对应的栈数据了。