From 15f24c94026554fa6e2eb8db0241668c0e3b2a19 Mon Sep 17 00:00:00 2001 From: tjw378335 Date: Fri, 7 Jul 2023 11:05:47 +0800 Subject: [PATCH] [Runtime] Dumphooks for QuickStart framework Summary: Add dumphooks for QuickStart framework: hooks will be called after application startup finishes Test Plan: test/jdk/com/alibaba/quickstart/TestQuickStartHooks.java --- make/data/hotspot-symbols/symbols-unix | 3 + src/hotspot/share/classfile/vmClassMacros.hpp | 2 + src/hotspot/share/classfile/vmSymbols.hpp | 3 + src/hotspot/share/include/jvm.h | 2 + src/hotspot/share/prims/jvm.cpp | 6 + src/hotspot/share/runtime/quickStart.cpp | 27 ++-- src/hotspot/share/runtime/quickStart.hpp | 4 +- src/hotspot/share/runtime/thread.cpp | 7 ++ .../classes/com/alibaba/util/QuickStart.java | 86 +++++++++++++ .../share/native/libjava/QuickStart.c | 15 +++ test/jdk/com/alibaba/quickstart/TestDump.java | 118 ++++++++++++++++++ .../alibaba/quickstart/TestNotifyDump.java | 42 +++++++ .../quickstart/TestNotifyDumpByAPI.java | 43 +++++++ 13 files changed, 346 insertions(+), 12 deletions(-) create mode 100644 src/java.base/share/classes/com/alibaba/util/QuickStart.java create mode 100644 src/java.base/share/native/libjava/QuickStart.c create mode 100644 test/jdk/com/alibaba/quickstart/TestDump.java create mode 100644 test/jdk/com/alibaba/quickstart/TestNotifyDump.java create mode 100644 test/jdk/com/alibaba/quickstart/TestNotifyDumpByAPI.java diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index d9fff0f1dd7..db6a768cd78 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -208,3 +208,6 @@ JVM_AddReadsModule JVM_DefineArchivedModules JVM_DefineModule JVM_SetBootLoaderUnnamedModule + +# Quickstart API +JVM_NotifyDump diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index a4f55641b51..7dcceda4d8f 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -181,6 +181,8 @@ do_klass(vector_VectorMask_klass, jdk_internal_vm_vector_VectorMask ) \ do_klass(vector_VectorShuffle_klass, jdk_internal_vm_vector_VectorShuffle ) \ \ + /* support for quickstart */ \ + do_klass(com_alibaba_util_QuickStart_klass, com_alibaba_util_QuickStart ) \ /*end*/ #endif // SHARE_CLASSFILE_VMCLASSMACROS_HPP diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 0c5da4e7a2a..05611ca7785 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -714,6 +714,9 @@ template(com_alibaba_cds_NotFoundClassSet, "com/alibaba/cds/NotFoundClassSet") \ template(isNotFound_name, "isNotFound") \ template(isNotFound_signature, "(Ljava/lang/String;I)Z") \ + template(com_alibaba_util_QuickStart, "com/alibaba/util/QuickStart") \ + template(initialize_name, "initialize") \ + template(boolean_String_void_signature, "(ZLjava/lang/String;)V") \ /*end*/ // enum for figuring positions and size of Symbol::_vm_symbols[] diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index f37b9463e1e..204ee158d26 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1099,6 +1099,8 @@ JVM_GetTemporaryDirectory(JNIEnv *env); JNIEXPORT jobjectArray JNICALL JVM_GetEnclosingMethodInfo(JNIEnv* env, jclass ofClass); +JNIEXPORT void JNICALL +JVM_NotifyDump(JNIEnv *env, jclass ignored); /* * This structure is used by the launcher to get the default thread * stack size from the VM using JNI_GetDefaultJavaVMInitArgs() with a diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 31be97add8f..1ddafe33708 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -89,6 +89,7 @@ #include "runtime/vframe.inline.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vm_version.hpp" +#include "runtime/quickStart.hpp" #include "services/attachListener.hpp" #include "services/management.hpp" #include "services/threadService.hpp" @@ -3872,3 +3873,8 @@ JVM_END JVM_ENTRY_NO_ENV(jint, JVM_FindSignal(const char *name)) return os::get_signal_number(name); JVM_END + +JVM_ENTRY(void, JVM_NotifyDump(JNIEnv *env, jclass ignored)) + JVMWrapper("JVM_NotifyDump"); + QuickStart::notify_dump(); +JVM_END diff --git a/src/hotspot/share/runtime/quickStart.cpp b/src/hotspot/share/runtime/quickStart.cpp index 22dae66f00a..b90496af1b8 100644 --- a/src/hotspot/share/runtime/quickStart.cpp +++ b/src/hotspot/share/runtime/quickStart.cpp @@ -3,7 +3,11 @@ #include #include #include "classfile/vmSymbols.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/stringTable.hpp" +#include "runtime/arguments.hpp" #include "runtime/java.hpp" +#include "runtime/javaCalls.hpp" #include "runtime/quickStart.hpp" #include "utilities/defaultStream.hpp" @@ -12,7 +16,6 @@ bool QuickStart::_is_enabled = false; bool QuickStart::_verbose = false; bool QuickStart::_print_stat_enabled = false; bool QuickStart::_need_destroy = false; -bool QuickStart::_need_finish_check = true; QuickStart::QuickStartRole QuickStart::_role = QuickStart::Normal; @@ -77,16 +80,6 @@ bool QuickStart::parse_command_line_arguments(const char* options) { _need_destroy = true; } else if (match_option(cur, "path=", &tail)) { _cache_path = os::strdup_check_oom(tail, mtArguments); - } else if (match_option(cur, "dumpPolicy=", &tail)) { - size_t len = strlen(tail); - if (strncmp(tail, "api", len) == 0) { - _need_finish_check = false; - } else if (strncmp(tail, "auto", len) == 0) { - _need_finish_check = true; - } else { - success = false; - tty->print_cr("[QuickStart] Invalid -Xquickstart option '%s'", cur); - } } else if (match_option(cur, "dockerImageEnv=", &tail)) { _image_env = os::strdup_check_oom(tail, mtArguments); } else { @@ -143,6 +136,14 @@ void QuickStart::print_command_line_help(outputStream* out) { // initialize JDK part for QuickStart void QuickStart::initialize(TRAPS) { + Klass* klass = SystemDictionary::com_alibaba_util_QuickStart_klass(); + JavaValue result(T_VOID); + JavaCallArguments args(2); + args.push_int(is_tracer()); + args.push_oop(java_lang_String::create_from_str(QuickStart::cache_path(), THREAD)); + + JavaCalls::call_static(&result, klass, vmSymbols::initialize_name(), + vmSymbols::boolean_String_void_signature(), &args, CHECK); } void QuickStart::post_process_arguments() { @@ -347,3 +348,7 @@ int QuickStart::remove_dir(const char* dir) { } return ret; } + +void QuickStart::notify_dump() { + log("startup finishes"); +} diff --git a/src/hotspot/share/runtime/quickStart.hpp b/src/hotspot/share/runtime/quickStart.hpp index 060d113f566..56f012ac1a9 100644 --- a/src/hotspot/share/runtime/quickStart.hpp +++ b/src/hotspot/share/runtime/quickStart.hpp @@ -55,7 +55,6 @@ class QuickStart : AllStatic { static bool _verbose; static bool _print_stat_enabled; static bool _need_destroy; - static bool _need_finish_check; static const char* _opt_name[]; static bool _opt_enabled[]; @@ -69,6 +68,9 @@ class QuickStart : AllStatic { static bool match_option(const char* option, const char* name, const char** tail); static void print_command_line_help(outputStream* out); static void log(const char* msg, ...) ATTRIBUTE_PRINTF(1, 2); + +public: + static void notify_dump(); }; #endif diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index 385f7a3fa99..c060454e803 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -118,6 +118,7 @@ #include "runtime/vmThread.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vm_version.hpp" +#include "runtime/quickStart.hpp" #include "services/attachListener.hpp" #include "services/management.hpp" #include "services/memTracker.hpp" @@ -2644,6 +2645,12 @@ static void call_initPhase3(TRAPS) { JavaValue result(T_VOID); JavaCalls::call_static(&result, klass, vmSymbols::initPhase3_name(), vmSymbols::void_method_signature(), CHECK); + if (QuickStart::is_tracer() || QuickStart::is_replayer()) { + QuickStart::initialize(THREAD); + if (HAS_PENDING_EXCEPTION) { + vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION)); + } + } } void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { diff --git a/src/java.base/share/classes/com/alibaba/util/QuickStart.java b/src/java.base/share/classes/com/alibaba/util/QuickStart.java new file mode 100644 index 00000000000..44dfdf9036e --- /dev/null +++ b/src/java.base/share/classes/com/alibaba/util/QuickStart.java @@ -0,0 +1,86 @@ +package com.alibaba.util; + +import java.util.ArrayList; +import java.util.List; + +public class QuickStart { + + private static native void registerNatives(); + + static { + registerNatives(); + } + + /** + * The enumeration is the same as VM level `enum QuickStart::QuickStartRole` + */ + public enum QuickStartRole { NORMAL, TRACER, REPLAYER } + + private static QuickStartRole role = QuickStartRole.NORMAL; + + private final static List dumpHooks = new ArrayList<>(); + + // JVM will set these fields + protected static String resourcePath; + + // called by JVM + private static void initialize(boolean isTracer, String resourcePath) { + role = isTracer ? QuickStartRole.TRACER : QuickStartRole.REPLAYER; + QuickStart.resourcePath = resourcePath; + + Runtime.getRuntime().addShutdownHook(new Thread(QuickStart::notifyDump)); + } + + /** + * Detect whether this Java process is a normal one. + * Has the same semantics as VM level `!QuickStart::is_enabled()` + * @return true if this Java process is a normal process. + */ + public static boolean isNormal() { + return role == QuickStartRole.NORMAL; + } + + /** + * Detect whether this Java process is a tracer. + * Has the same semantics as VM level `QuickStart::is_tracer()` + * @return true if this Java process is a tracer. + */ + public static boolean isTracer() { + return role == QuickStartRole.TRACER; + } + + /** + * Detect whether this Java process is a replayer. + * Has the same semantics as VM level `QuickStart::is_replayer()` + * @return true if this Java process is replayer. + */ + public static boolean isReplayer() { + return role == QuickStartRole.REPLAYER; + } + + public static String resourcePath() { + return resourcePath; + } + + public static synchronized void addDumpHook(Runnable runnable) { + if (notifyCompleted) { + return; + } + dumpHooks.add(runnable); + } + + public static synchronized void notifyDump() { + if (notifyCompleted) { + return; + } + for (Runnable dumpHook : dumpHooks) { + dumpHook.run(); + } + notifyDump0(); + notifyCompleted = true; + } + + private static boolean notifyCompleted = false; + + private static native void notifyDump0(); +} diff --git a/src/java.base/share/native/libjava/QuickStart.c b/src/java.base/share/native/libjava/QuickStart.c new file mode 100644 index 00000000000..dc8e8cf8942 --- /dev/null +++ b/src/java.base/share/native/libjava/QuickStart.c @@ -0,0 +1,15 @@ +#include "jni.h" +#include "jni_util.h" +#include "jvm.h" +#include "jmm.h" +#include "com_alibaba_util_QuickStart.h" +#define THREAD "Ljava/lang/Thread;" +#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0])) +static JNINativeMethod methods[] = { + {"notifyDump0", "()V", (void *)&JVM_NotifyDump } +}; +JNIEXPORT void JNICALL +Java_com_alibaba_util_QuickStart_registerNatives(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); +} \ No newline at end of file diff --git a/test/jdk/com/alibaba/quickstart/TestDump.java b/test/jdk/com/alibaba/quickstart/TestDump.java new file mode 100644 index 00000000000..63cc7ff764f --- /dev/null +++ b/test/jdk/com/alibaba/quickstart/TestDump.java @@ -0,0 +1,118 @@ +import com.alibaba.util.QuickStart; + +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +public class TestDump { + + private static long CHECK_INTERVAL_MS; + private static long CLASS_INC_COUNT; + private static long MAX_START_SECONDS; + private static ClassLoadingMXBean mxbean = ManagementFactory.getClassLoadingMXBean(); + + static { + Properties p = AccessController.doPrivileged((PrivilegedAction)System::getProperties); + CHECK_INTERVAL_MS = parsePositiveLongParameter(p, "-DcheckIntervalMS", 200); + CLASS_INC_COUNT = parsePositiveLongParameter(p, "-DclassIncCount", 200); + MAX_START_SECONDS = parsePositiveLongParameter(p, "-DmaxStartSeconds", 300); + } + + private static long parsePositiveLongParameter(Properties p, String key, long defaultVal) { + String value; + if ((value = p.getProperty(key)) == null) { + return defaultVal; + } + long res; + try { + res = Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultVal; + } + return res <= 0 ? defaultVal : res; + } + + private static Policy policy = null; + + interface Policy { + boolean check(); + } + + static class ClassLoadingPolicy implements Policy { + private final static long STARTING_MIN_INC = CLASS_INC_COUNT / + TimeUnit.SECONDS.toMillis(1) * CHECK_INTERVAL_MS; + private long lastLoadedClasses = 0; + + @Override + public boolean check() { + long cnt = mxbean.getLoadedClassCount(); + if (cnt - lastLoadedClasses <= STARTING_MIN_INC) { + return true; + } + lastLoadedClasses = cnt; + return false; + } + } + + static class WatcherThread extends Thread { + + public WatcherThread() { + super("QuickStart-WatcherThread"); + } + + @Override + public void run() { + System.out.println("Watcher Thread begins..."); + long start = System.currentTimeMillis(); + + do { + try { + Thread.sleep(CHECK_INTERVAL_MS); + } catch (InterruptedException e) { + return; + } + } while (System.currentTimeMillis() - start < TimeUnit.SECONDS.toMillis(MAX_START_SECONDS) + && !policy.check()); + QuickStart.notifyDump(); + } + } + + public static boolean checkDumpUsingAPI() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + for (String arg : arguments) { + if (arg.contains("-DtestHooks=true")) { + return true; + } + } + return false; + } + + public static final long SLEEP_MILLIS = 5000; + public static final String ANCHOR = "QuickStart startup finish detected!"; + + public static void main(String[] args) { + if (checkDumpUsingAPI()) { + policy = new ClassLoadingPolicy(); + WatcherThread thread = new WatcherThread(); + thread.setDaemon(true); + thread.start(); + } + QuickStart.addDumpHook(() -> { + System.out.println(ANCHOR); + }); + try { + Thread.sleep(SLEEP_MILLIS); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + System.out.println("finished"); + } + } + +} diff --git a/test/jdk/com/alibaba/quickstart/TestNotifyDump.java b/test/jdk/com/alibaba/quickstart/TestNotifyDump.java new file mode 100644 index 00000000000..61e7566cb1b --- /dev/null +++ b/test/jdk/com/alibaba/quickstart/TestNotifyDump.java @@ -0,0 +1,42 @@ +/* + * @test + * @modules java.base/sun.security.action + * @summary Test dumping when process exits + * @library /test/lib + * @build TestDump + * @run main/othervm TestNotifyDump + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import sun.security.action.GetPropertyAction; + +import java.io.File; +import java.security.AccessController; + +public class TestNotifyDump { + + private static final String TESTCLASS = "TestDump"; + + public static void main(String[] args) throws Exception { + String dir = AccessController.doPrivileged(new GetPropertyAction("test.classes")); + TestNotifyDump.verifyPathSetting(dir); + new File(dir).delete(); + } + + static void verifyPathSetting(String parentDir) throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xquickstart:path=" + parentDir + "/quickstartcache", + "-Xquickstart:verbose", + // In sleeping condition there is no classloading happens, + // we will consider it as the start-up finish + "-DcheckIntervalMS=" + (TestDump.SLEEP_MILLIS / 5), + TESTCLASS); + pb.redirectErrorStream(true); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println("[Child Output] " + output.getOutput()); + output.shouldContain(TestDump.ANCHOR); + output.shouldHaveExitValue(0); + } + +} diff --git a/test/jdk/com/alibaba/quickstart/TestNotifyDumpByAPI.java b/test/jdk/com/alibaba/quickstart/TestNotifyDumpByAPI.java new file mode 100644 index 00000000000..4f4a7c1cb28 --- /dev/null +++ b/test/jdk/com/alibaba/quickstart/TestNotifyDumpByAPI.java @@ -0,0 +1,43 @@ +/* + * @test + * @modules java.base/sun.security.action + * @summary Test dumping using java level API hooks + * @library /test/lib + * @build TestDump + * @run main/othervm TestNotifyDumpByAPI + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import sun.security.action.GetPropertyAction; + +import java.io.File; +import java.security.AccessController; + +public class TestNotifyDumpByAPI { + + private static final String TESTCLASS = "TestDump"; + + public static void main(String[] args) throws Exception { + String dir = AccessController.doPrivileged(new GetPropertyAction("test.classes")); + TestNotifyDumpByAPI.verifyPathSetting(dir); + new File(dir).delete(); + } + + static void verifyPathSetting(String parentDir) throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xquickstart:path=" + parentDir + "/quickstartcache", + "-Xquickstart:verbose", + // In sleeping condition there is no classloading happens, + // we will consider it as the start-up finish + "-DcheckIntervalMS=" + (TestDump.SLEEP_MILLIS / 5), + "-DtestHooks=true", + TESTCLASS); + pb.redirectErrorStream(true); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + System.out.println("[Child Output] " + output.getOutput()); + output.shouldContain(TestDump.ANCHOR); + output.shouldHaveExitValue(0); + } + +}