本文共 23412 字,大约阅读时间需要 78 分钟。
JDK最新bug和任务领取:
参加OpenJDK社区:
openjdk源码地址:
如果国外网速不行,这里有下好放csdn上的:
线上源码:
如果官网很慢,可以直接CSDN下载:
JEP 0:JEP指数:
--------------
----------------
其他jdk文档地址:
----
扩展阅读:虽然是历史资源,但是还是闪烁着智慧的
【有参考价值】
【很有价值】
----
【有参考价值】:
【重要】 【很重要】 【有参考价值】
【重要】
和12类似
----
分析Jstack源码
这是起点>>>
\openjdk\jdk\src\share\classes\sun\tools目录下
常见的jvm命令jstack jmap jps都在这里
package sun.tools.jstack;import java.lang.reflect.Method;import java.lang.reflect.Constructor;import java.io.IOException;import java.io.InputStream;import com.sun.tools.attach.VirtualMachine;import com.sun.tools.attach.AttachNotSupportedException;import sun.tools.attach.HotSpotVirtualMachine;/* * This class is the main class for the JStack utility. It parses its arguments * and decides if the command should be executed by the SA JStack tool or by * obtained the thread dump from a target process using the VM attach mechanism */public class JStack { public static void main(String[] args) throws Exception { if (args.length == 0) { usage(1); // no arguments } boolean useSA = false; boolean mixed = false; boolean locks = false; // Parse the options (arguments starting with "-" ) int optionCount = 0; while (optionCount < args.length) { String arg = args[optionCount]; if (!arg.startsWith("-")) { break; } if (arg.equals("-help") || arg.equals("-h")) { usage(0); } else if (arg.equals("-F")) { useSA = true; } else { if (arg.equals("-m")) { mixed = true; } else { if (arg.equals("-l")) { locks = true; } else { usage(1); } } } optionCount++; } // mixed stack implies SA tool if (mixed) { useSA = true; } // Next we check the parameter count. If there are two parameters // we assume core file and executable so we use SA. int paramCount = args.length - optionCount; if (paramCount == 0 || paramCount > 2) { usage(1); } if (paramCount == 2) { useSA = true; } else { // If we can't parse it as a pid then it must be debug server if (!args[optionCount].matches("[0-9]+")) { useSA = true; } } // now execute using the SA JStack tool or the built-in thread dumper if (useSA) { // parameters (or String params[] = new String[paramCount]; for (int i=optionCount; i
根据传入参数的不同,有两种实现机制,一种是基于SA,一种是通过attach。
下面是jmap部分代码下面是用的最多的:
// Invoke SA tool with the given arguments private static void runTool(String option, String args[]) throws Exception { String[][] tools = { { "-pmap", "sun.jvm.hotspot.tools.PMap" }, { "-heap", "sun.jvm.hotspot.tools.HeapSummary" }, { "-heap:format=b", "sun.jvm.hotspot.tools.HeapDumper" }, { "-histo", "sun.jvm.hotspot.tools.ObjectHistogram" }, { "-clstats", "sun.jvm.hotspot.tools.ClassLoaderStats" }, { "-finalizerinfo", "sun.jvm.hotspot.tools.FinalizerInfo" }, };
-------------------
都是通过 executeCommand 来实现的,例如:datadump、threaddump、dumpheap、inspectheap、jcmd等,而最终的execute()在Linux上是由类LinuxVirtualMachine来完成。
public abstract class HotSpotVirtualMachine extends VirtualMachine {... // --- HotSpot specific methods --- // same as SIGQUIT public void localDataDump() throws IOException { executeCommand("datadump").close(); } // Remote ctrl-break. The output of the ctrl-break actions can // be read from the input stream. public InputStream remoteDataDump(Object ... args) throws IOException { return executeCommand("threaddump", args); } // Remote heap dump. The output (error message) can be read from the // returned input stream. public InputStream dumpHeap(Object ... args) throws IOException { return executeCommand("dumpheap", args); } // Heap histogram (heap inspection in HotSpot) public InputStream heapHisto(Object ... args) throws IOException { return executeCommand("inspectheap", args); } // set JVM command line flag public InputStream setFlag(String name, String value) throws IOException { return executeCommand("setflag", name, value); } // print command line flag public InputStream printFlag(String name) throws IOException { return executeCommand("printflag", name); } public InputStream executeJCmd(String command) throws IOException { return executeCommand("jcmd", command); } // -- Supporting methods
-----------------------------------
jstack命令首先会attach到目标jvm进程,产生VirtualMachine类;
linux系统下,其实现类为LinuxVirtualMachine,调用其remoteDataDump方法,打印堆栈信息;
VirtualMachine是如何连接到目标JVM进程的呢?
具体的实现逻辑在sun.tools.attach.LinuxVirtualMachine的构造函数:// The patch to the socket file created by the target VM String path; /** * Attaches to the target VM */ LinuxVirtualMachine(AttachProvider provider, String vmid) throws AttachNotSupportedException, IOException { super(provider, vmid); // This provider only understands pids int pid; try { pid = Integer.parseInt(vmid); } catch (NumberFormatException x) { throw new AttachNotSupportedException("Invalid process identifier"); } // Find the socket file. If not found then we attempt to start the // attach mechanism in the target VM by sending it a QUIT signal. // Then we attempt to find the socket file again. path = findSocketFile(pid); if (path == null) { File f = createAttachFile(pid); try { // On LinuxThreads each thread is a process and we don't have the // pid of the VMThread which has SIGQUIT unblocked. To workaround // this we get the pid of the "manager thread" that is created // by the first call to pthread_create. This is parent of all // threads (except the initial thread). if (isLinuxThreads) { int mpid; try { mpid = getLinuxThreadsManager(pid); } catch (IOException x) { throw new AttachNotSupportedException(x.getMessage()); } assert(mpid >= 1); sendQuitToChildrenOf(mpid); } else { sendQuitTo(pid); } // give the target VM time to start the attach mechanism int i = 0; long delay = 200; int retries = (int)(attachTimeout() / delay); do { try { Thread.sleep(delay); } catch (InterruptedException x) { } path = findSocketFile(pid); i++; } while (i <= retries && path == null); if (path == null) { throw new AttachNotSupportedException( "Unable to open socket file: target process not responding " + "or HotSpot VM not loaded"); } } finally { f.delete(); } } // Check that the file owner/permission to avoid attaching to // bogus process checkPermissions(path); // Check that we can connect to the process // - this ensures we throw the permission denied error now rather than // later when we attempt to enqueue a command. int s = socket(); try { connect(s, path); } finally { close(s); } } /** * Detach from the target VM */ public void detach() throws IOException { synchronized (this) { if (this.path != null) { this.path = null; } } }
- 查找/tmp目录下是否存在".java_pid"+pid文件;
- 如果文件不存在,则首先创建"/proc/" + pid + "/cwd/" + ".attach_pid" + pid文件,然后通过kill命令发送SIGQUIT信号给目标JVM进程;
- 目标JVM进程接收到信号之后,会在/tmp目录下创建".java_pid"+pid文件
- 当发现/tmp目录下存在".java_pid"+pid文件,LinuxVirtualMachine会通过connect系统调用连接到该文件描述符,后续通过该fd进行双方的通讯;
JVM接受SIGQUIT信号的相关逻辑在os.cpp文件的os::signal_init方法:
jstack是通过调用remoteDataDump方法实现的,该方法就是通过往前面提到的fd中写入threaddump指令,读取返回结果,从而得到目标JVM的堆栈信息。
----------------------------------
jstack等命令会与jvm进程建立socket连接,发送对应的指令(jstack发送了threaddump指令),然后再读取返回的数据。
/** * Execute the given command in the target VM. */ InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { assert args.length <= 3; // includes null // did we detach? String p; synchronized (this) { if (this.path == null) { throw new IOException("Detached from target VM"); } p = this.path; } // create UNIX socket int s = socket(); // connect to target VM try { connect(s, p); } catch (IOException x) { close(s); throw x; } IOException ioe = null; // connected - write request //try { writeString(s, PROTOCOL_VERSION); writeString(s, cmd); for (int i=0; i<3; i++) { if (i < args.length && args[i] != null) { writeString(s, (String)args[i]); } else { writeString(s, ""); } } } catch (IOException x) { ioe = x; } // Create an input stream to read reply SocketInputStream sis = new SocketInputStream(s); // Read the command completion status int completionStatus; try { completionStatus = readInt(sis); } catch (IOException x) { sis.close(); if (ioe != null) { throw ioe; } else { throw x; } } if (completionStatus != 0) { // read from the stream and use that as the error message String message = readErrorMessage(sis); sis.close(); // In the event of a protocol mismatch then the target VM // returns a known error so that we can throw a reasonable // error. if (completionStatus == ATTACH_ERROR_BADVERSION) { throw new IOException("Protocol mismatch with target VM"); } // Special-case the "load" command so that the right exception is // thrown. if (cmd.equals("load")) { throw new AgentLoadException("Failed to load agent library"); } else { if (message == null) { throw new AttachOperationFailedException("Command failed in target VM"); } else { throw new AttachOperationFailedException(message); } } } // Return the input stream so that the command output can be read return sis; }
-----------------
下面是C++部分
\openjdk\hotspot\src\share\vm\services\attachListener.hpp
// Table to map operation names to functions.// names must be of length <= AttachOperation::name_length_maxstatic AttachOperationFunctionInfo funcs[] = { { "agentProperties", get_agent_properties }, { "datadump", data_dump }, { "dumpheap", dump_heap }, { "load", JvmtiExport::load_agent_library }, { "properties", get_system_properties }, { "threaddump", thread_dump }, { "inspectheap", heap_inspection }, { "setflag", set_flag }, { "printflag", print_flag }, { "jcmd", jcmd }, { NULL, NULL }};
\openjdk\hotspot\src\os\linux\vm\attachListener_linux.cpp
侦听socket
// The attach mechanism on Linux uses a UNIX domain socket. An attach listener// thread is created at startup or is created on-demand via a signal from// the client tool. The attach listener creates a socket and binds it to a file// in the filesystem. The attach listener then acts as a simple (single-// threaded) server - it waits for a client to connect, reads the request,// executes it, and returns the response to the client via the socket// connection.//// As the socket is a UNIX domain socket it means that only clients on the// local machine can connect. In addition there are two other aspects to// the security:// 1. The well known file that the socket is bound to has permission 400// 2. When a client connect, the SO_PEERCRED socket option is used to// obtain the credentials of client. We check that the effective uid// of the client matches this process.....// Initialization - create a listener socket and bind it to a fileint LinuxAttachListener::init() { char path[UNIX_PATH_MAX]; // socket file char initial_path[UNIX_PATH_MAX]; // socket file during setup int listener; // listener socket (file descriptor) // register function to cleanup ::atexit(listener_cleanup); int n = snprintf(path, UNIX_PATH_MAX, "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); if (n < (int)UNIX_PATH_MAX) { n = snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path); } if (n >= (int)UNIX_PATH_MAX) { return -1; } // create the listener socket listener = ::socket(PF_UNIX, SOCK_STREAM, 0); if (listener == -1) { return -1; } // bind socket struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, initial_path); ::unlink(initial_path); int res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr)); if (res == -1) { ::close(listener); return -1; } // put in listen mode, set permissions, and rename into place res = ::listen(listener, 5); if (res == 0) { RESTARTABLE(::chmod(initial_path, S_IREAD|S_IWRITE), res); if (res == 0) { res = ::rename(initial_path, path); } } if (res == -1) { ::close(listener); ::unlink(initial_path); return -1; } set_path(path); set_listener(listener); return 0;}....
再就是一个个命令对应去看具体代码实现,以dumpheap为例:
\openjdk\hotspot\src\share\vm\services\heapDumper.cpp
// The VM operation that dumps the heap. The dump consists of the following// records://// HPROF_HEADER// [HPROF_UTF8]*// [HPROF_LOAD_CLASS]*// [[HPROF_FRAME]*|HPROF_TRACE]*// [HPROF_GC_CLASS_DUMP]*// HPROF_HEAP_DUMP//// The HPROF_TRACE records represent the stack traces where the heap dump// is generated and a "dummy trace" record which does not include// any frames. The dummy trace record is used to be referenced as the// unknown object alloc site.//// The HPROF_HEAP_DUMP record has a length following by sub-records. To allow// the heap dump be generated in a single pass we remember the position of// the dump length and fix it up after all sub-records have been written.// To generate the sub-records we iterate over the heap, writing// HPROF_GC_INSTANCE_DUMP, HPROF_GC_OBJ_ARRAY_DUMP, and HPROF_GC_PRIM_ARRAY_DUMP// records as we go. Once that is done we write records for some of the GC// roots.// HPROF_TRACE记录表示堆转储的堆栈跟踪//生成并且“虚拟跟踪”记录不包括//任何帧 虚拟跟踪记录用于引用//未知对象分配站点。//// HPROF_HEAP_DUMP记录的子记录长度如下。 允许//在一次传递中生成堆转储,我们记住了它的位置//转储长度并在写完所有子记录后修复它。//为了生成子记录,我们迭代堆,写// HPROF_GC_INSTANCE_DUMP,HPROF_GC_OBJ_ARRAY_DUMP和HPROF_GC_PRIM_ARRAY_DUMP//我们去的记录。 完成后,我们会为某些GC编写记录//根void VM_HeapDumper::doit() { HandleMark hm; CollectedHeap* ch = Universe::heap(); ch->ensure_parsability(false); // must happen, even if collection does // not happen (e.g. due to GC_locker) if (_gc_before_heap_dump) { if (GC_locker::is_active()) { warning("GC locker is held; pre-heapdump GC was skipped"); } else { ch->collect_as_vm_thread(GCCause::_heap_dump); } } // At this point we should be the only dumper active, so // the following should be safe. set_global_dumper(); set_global_writer(); // Write the file header - use 1.0.2 for large heaps, otherwise 1.0.1 size_t used = ch->used(); const char* header; if (used > (size_t)SegmentedHeapDumpThreshold) { set_segmented_dump(); header = "JAVA PROFILE 1.0.2"; } else { header = "JAVA PROFILE 1.0.1"; } // header is few bytes long - no chance to overflow int writer()->write_raw((void*)header, (int)strlen(header)); writer()->write_u1(0); // terminator writer()->write_u4(oopSize); writer()->write_u8(os::javaTimeMillis()); // HPROF_UTF8 records SymbolTableDumper sym_dumper(writer()); SymbolTable::symbols_do(&sym_dumper); // write HPROF_LOAD_CLASS records ClassLoaderDataGraph::classes_do(&do_load_class); Universe::basic_type_classes_do(&do_load_class); // write HPROF_FRAME and HPROF_TRACE records // this must be called after _klass_map is built when iterating the classes above. dump_stack_traces(); // write HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT write_dump_header(); // Writes HPROF_GC_CLASS_DUMP records ClassLoaderDataGraph::classes_do(&do_class_dump); Universe::basic_type_classes_do(&do_basic_type_array_class_dump); check_segment_length(); // writes HPROF_GC_INSTANCE_DUMP records. // After each sub-record is written check_segment_length will be invoked. When // generated a segmented heap dump this allows us to check if the current // segment exceeds a threshold and if so, then a new segment is started. // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk // of the heap dump. HeapObjectDumper obj_dumper(this, writer()); Universe::heap()->safe_object_iterate(&obj_dumper); // HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals do_threads(); check_segment_length(); // HPROF_GC_ROOT_MONITOR_USED MonitorUsedDumper mon_dumper(writer()); ObjectSynchronizer::oops_do(&mon_dumper); check_segment_length(); // HPROF_GC_ROOT_JNI_GLOBAL JNIGlobalsDumper jni_dumper(writer()); JNIHandles::oops_do(&jni_dumper); check_segment_length(); // HPROF_GC_ROOT_STICKY_CLASS StickyClassDumper class_dumper(writer()); SystemDictionary::always_strong_classes_do(&class_dumper); // fixes up the length of the dump record. In the case of a segmented // heap then the HPROF_HEAP_DUMP_END record is also written. end_of_dump(); // Now we clear the global variables, so that a future dumper might run. clear_global_dumper(); clear_global_writer();}
void VM_HeapDumper::dump_stack_traces() { // write a HPROF_TRACE record without any frames to be referenced as object alloc sites DumperSupport::write_header(writer(), HPROF_TRACE, 3*sizeof(u4)); writer()->write_u4((u4) STACK_TRACE_ID); writer()->write_u4(0); // thread number writer()->write_u4(0); // frame count _stack_traces = NEW_C_HEAP_ARRAY(ThreadStackTrace*, Threads::number_of_threads(), mtInternal); int frame_serial_num = 0; for (JavaThread* thread = Threads::first(); thread != NULL ; thread = thread->next()) { oop threadObj = thread->threadObj(); if (threadObj != NULL && !thread->is_exiting() && !thread->is_hidden_from_external_view()) { // dump thread stack trace ThreadStackTrace* stack_trace = new ThreadStackTrace(thread, false); stack_trace->dump_stack_at_safepoint(-1); _stack_traces[_num_threads++] = stack_trace; // write HPROF_FRAME records for this thread's stack trace int depth = stack_trace->get_stack_depth(); int thread_frame_start = frame_serial_num; int extra_frames = 0; // write fake frame that makes it look like the thread, which caused OOME, // is in the OutOfMemoryError zero-parameter constructor if (thread == _oome_thread && _oome_constructor != NULL) { int oome_serial_num = _klass_map->find(_oome_constructor->method_holder()); // the class serial number starts from 1 assert(oome_serial_num > 0, "OutOfMemoryError class not found"); DumperSupport::dump_stack_frame(writer(), ++frame_serial_num, oome_serial_num, _oome_constructor, 0); extra_frames++; } for (int j=0; j < depth; j++) { StackFrameInfo* frame = stack_trace->stack_frame_at(j); Method* m = frame->method(); int class_serial_num = _klass_map->find(m->method_holder()); // the class serial number starts from 1 assert(class_serial_num > 0, "class not found"); DumperSupport::dump_stack_frame(writer(), ++frame_serial_num, class_serial_num, m, frame->bci()); } depth += extra_frames; // write HPROF_TRACE record for one thread DumperSupport::write_header(writer(), HPROF_TRACE, 3*sizeof(u4) + depth*oopSize); int stack_serial_num = _num_threads + STACK_TRACE_ID; writer()->write_u4(stack_serial_num); // stack trace serial number writer()->write_u4((u4) _num_threads); // thread serial number writer()->write_u4(depth); // frame count for (int j=1; j <= depth; j++) { writer()->write_id(thread_frame_start + j); } } }}
// dump the heap to given path.PRAGMA_FORMAT_NONLITERAL_IGNORED_EXTERNALint HeapDumper::dump(const char* path) { assert(path != NULL && strlen(path) > 0, "path missing"); // print message in interactive case if (print_to_tty()) { tty->print_cr("Dumping heap to %s ...", path); timer()->start(); } // create the dump writer. If the file can be opened then bail DumpWriter writer(path); if (!writer.is_open()) { set_error(writer.error()); if (print_to_tty()) { tty->print_cr("Unable to create %s: %s", path, (error() != NULL) ? error() : "reason unknown"); } return -1; } // generate the dump VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome); if (Thread::current()->is_VM_thread()) { assert(SafepointSynchronize::is_at_safepoint(), "Expected to be called at a safepoint"); dumper.doit(); } else { VMThread::execute(&dumper); } // close dump file and record any error that the writer may have encountered writer.close(); set_error(writer.error()); // print message in interactive case if (print_to_tty()) { timer()->stop(); if (error() == NULL) { char msg[256]; sprintf(msg, "Heap dump file created [%s bytes in %3.3f secs]", JLONG_FORMAT, timer()->seconds());PRAGMA_DIAG_PUSHPRAGMA_FORMAT_NONLITERAL_IGNORED_INTERNAL tty->print_cr(msg, writer.bytes_written());PRAGMA_DIAG_POP } else { tty->print_cr("Dump file is incomplete: %s", writer.error()); } } return (writer.error() == NULL) ? 0 : -1;}
说明:本文参考了《》和《》这两篇都是java部分的缺少C++,然后C++部分是我加上的。
--------------------
《》
从整个加载本地库的流程来看,基本上还是调用和平台有关的函数来完成的,并在加载和卸载的时候分别调用了两个生命周期回调函数 JNI_OnLoad
和 JNI_OnUnLoad
。
以linux平台为例,简单总结一下整个so库的加载流程:
- 首先
System.loadLibrary()
被调用,开始整个加载过程。 - 其中调用
ClassLoader
对象来完成主要工作,将每个本地库封装成NativeLibrary
对象,并以静态变量存到已经加载过的栈中。 - 执行
NativeLibrary
类的load
native方法,来交给native层去指向具体的加载工作。 - native层
ClassLoader.c
中的Java_java_lang_ClassLoader_00024NativeLibrary_load
函数被调用。 - 在native load函数中首先使用
dlopen
来加载so本地库文件,并将返回的handle保存到NativeLibrary
对象中。 - 接着查找已经加载的so本地库中的
JNI_OnLoad
函数,并执行它。 - 整个so本地库的加载流程完毕。
只有在 NativeLibrary
对象被GC回收的时候,其 finalize
方法被调用了,对应加载的本地库才会被 unload 。这种情况一般来说并不会发生,因为 NativeLibrary
对象是以静态变量的形式被保存的,而静态变量是 GC roots,一般来说都不会被回收掉的。
转载地址:https://linuxstyle.blog.csdn.net/article/details/88528594 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!