Skip to content

Commit

Permalink
[Runtime] Dumphooks for QuickStart framework
Browse files Browse the repository at this point in the history
Summary: Add dumphooks for QuickStart framework: hooks will be called after application startup finishes

Test Plan: test/jdk/com/alibaba/quickstart/TestQuickStartHooks.java
  • Loading branch information
jia-wei-tang committed Jul 7, 2023
1 parent 6641997 commit 15f24c9
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 12 deletions.
3 changes: 3 additions & 0 deletions make/data/hotspot-symbols/symbols-unix
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ JVM_AddReadsModule
JVM_DefineArchivedModules
JVM_DefineModule
JVM_SetBootLoaderUnnamedModule

# Quickstart API
JVM_NotifyDump
2 changes: 2 additions & 0 deletions src/hotspot/share/classfile/vmClassMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/hotspot/share/classfile/vmSymbols.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/include/jvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/hotspot/share/prims/jvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
27 changes: 16 additions & 11 deletions src/hotspot/share/runtime/quickStart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
#include <stdlib.h>
#include <strings.h>
#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"

Expand All @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -347,3 +348,7 @@ int QuickStart::remove_dir(const char* dir) {
}
return ret;
}

void QuickStart::notify_dump() {
log("startup finishes");
}
4 changes: 3 additions & 1 deletion src/hotspot/share/runtime/quickStart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand All @@ -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
7 changes: 7 additions & 0 deletions src/hotspot/share/runtime/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
86 changes: 86 additions & 0 deletions src/java.base/share/classes/com/alibaba/util/QuickStart.java
Original file line number Diff line number Diff line change
@@ -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<Runnable> 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();
}
15 changes: 15 additions & 0 deletions src/java.base/share/native/libjava/QuickStart.c
Original file line number Diff line number Diff line change
@@ -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));
}
118 changes: 118 additions & 0 deletions test/jdk/com/alibaba/quickstart/TestDump.java
Original file line number Diff line number Diff line change
@@ -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<Properties>)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<String> 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");
}
}

}
Loading

0 comments on commit 15f24c9

Please sign in to comment.