Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8259070: Add jcmd option to dump CDS #2737

Closed
wants to merge 17 commits into from
Closed
Changes from 2 commits
Commits
File filter
Filter file types
Jump to
Jump to file
Failed to load files.

Always

Just for now

@@ -1121,7 +1121,7 @@ void DumpSharedArchiveDCmd::execute(DCmdSource source, TRAPS) {
fileh = java_lang_String::create_from_str(_filename.value(), CHECK);
}
Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS();
Klass* cds_klass = SystemDictionary::resolve_or_null(cds_name, THREAD);
Klass* cds_klass = SystemDictionary::resolve_or_fail(cds_name, true /*throw error*/, CHECK);
JavaValue result(T_OBJECT);

This comment has been minimized.

@iklam

iklam Apr 1, 2021
Member

Should be result(T_VOID) to match the signature of CDS.dumpSharedArchive()

JavaCallArguments args;
args.push_int(is_static);
@@ -26,6 +26,8 @@
package jdk.internal.misc;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Map;
@@ -205,27 +207,60 @@ private static void validateInputLines(String[] lines) {
private static native void dumpClassList(String listFileName);
private static native void dumpDynamicArchive(String archiveFileName);

private static void outputStdStream(InputStream stream) {
String line;
InputStreamReader isr = new InputStreamReader(stream);
BufferedReader rdr = new BufferedReader(isr);
try {
while((line = rdr.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("IOExeption happens during drain stream " + e.getMessage());
}
}

private static String[] excludeFlags = {
"-XX:DumpLoadedClassList=",
"-XX:+DumpSharedSpaces",
"-XX:+DynamicDumpSharedSpaces",
"-XX:+RecordDynamicDumpInfo",
"-Xshare:",
"-XX:SharedClassListFile=",
"-XX:SharedArchiveFile=",
"-XX:ArchiveClassesAtExit=",
"-XX:+UseSharedSpaces",
"-XX:+RequireSharedSpaces"};
private static boolean containsExcludedFlags(String testStr) {
return testStr.contains("-XX:DumpLoadedClassList=") ||
testStr.contains("-XX:+DumpSharedSpaces") ||
testStr.contains("-XX:+DynamicDumpSharedSpaces") ||
testStr.contains("-XX:+RecordDynamicDumpInfo");
for (String e : excludeFlags) {
if (testStr.contains(e)) {
return true;
}
}
return false;
}
This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Mar 10, 2021
Member

The following flags should also be excluded:

  • -XX:-DumpSharedSpaces
  • -Xshare:
  • -XX:SharedClassListFile=
  • -XX:SharedArchiveFile=
  • -XX:ArchiveClassesAtExit=
  • -XX:+UseSharedSpaces
  • -XX:+RequireSharedSpaces

We also need to have a few test cases when the LingeredApp is started with these flags.

This comment has been minimized.

@yminqi

yminqi Mar 11, 2021
Author Contributor

Added String[] for those flags to check.


/**
* called from jcmd VM.cds to dump static or dynamic shared archive
* @param isStatic indicates dump static archive of dynnamic archive.
* @param isStatic true for dump static archive or false for dynnamic archive.
* @param fileName user input archive name, can be null.
*/
private static void dumpSharedArchive(boolean isStatic, String fileName) throws Exception {
boolean DEBUG = System.getProperty("CDS.Debug") == "true";
String archiveFile = fileName != null ? fileName :
"java_pid" + ProcessHandle.current().pid() + (isStatic ? "_static.jsa" : "_dynamic.jsa");
if (DEBUG) {
System.out.println((isStatic ? "Static" : " Dynamic") + " dump to file " + archiveFile);
System.out.println((isStatic ? "Static" : " Dynamic") + " dump to file " + archiveFile);

// delete if archive file aready exists
File fileArchive = new File(archiveFile);
if (fileArchive.exists()) {
fileArchive.delete();
}
if (isStatic) {
String listFile = archiveFile + ".classlist";
File fileList = new File(listFile);
if (fileList.exists()) {
fileList.delete();
}
dumpClassList(listFile);
String jdkHome = System.getProperty("java.home");
ArrayList<String> cmds = new ArrayList<String>();
@@ -244,36 +279,37 @@ private static void dumpSharedArchive(boolean isStatic, String fileName) throws
}
}

if (DEBUG) {
System.out.println("Static dump cmd: ");
for (String s : cmds) {
System.out.print(s + " ");
}
System.out.println("");
System.out.println("Static dump cmd: ");
for (String s : cmds) {
System.out.print(s + " ");
}
System.out.println("");

// Do not take parent env which will cause dumping fail.
// Do not take parent env which will cause check error on empty directory if
// classpath carried in envp.
Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0]),
This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Mar 10, 2021
Member

Could you explain why the parent's env variables will cause dumping to fail?

This comment has been minimized.

@yminqi

yminqi Mar 18, 2021
Author Contributor

I found jtreg env will be brought in to the children env which is not needed in this case. Add comment.

new String[] {"EnvP=null"});
if (DEBUG) {
System.out.println("Dumping process " + proc.pid() + " Stdout: ");
String line;
InputStreamReader isr = new InputStreamReader(proc.getInputStream());
BufferedReader rdr = new BufferedReader(isr);
while((line = rdr.readLine()) != null) {
System.out.println(line);
}

System.out.println("Dumping process " + proc.pid() + " Stderr: ");
isr = new InputStreamReader(proc.getErrorStream());
rdr = new BufferedReader(isr);
while((line = rdr.readLine()) != null) {
System.out.println(line);
}
}
// Drain stdout in a separate thread.
new Thread( ()-> {
System.out.println("Dumping process " + proc.pid() + " Stdout: ");
outputStdStream(proc.getInputStream());
}).start();

// Drain stderr in a separate thread.
new Thread( ()-> {
System.out.println("Dumping process " + proc.pid() + " Stdout: ");
outputStdStream(proc.getErrorStream());
}).start();

This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Apr 1, 2021
Member

I think the above code can be refactored to avoid duplication. Something like:

String stdOutFile = drainOutput(proc.getInputStream(), "stdout");
String stdErrFile = drainOutput(proc.getErrorStream(), "stderr");

and move the common code into drainOutput().

proc.waitFor();
} else {
dumpDynamicArchive(archiveFile);
}
// Check if archive has been successfully dumped. We won't reach here if exception happens.
// Throw exception if file is not created.
if (!fileArchive.exists()) {
throw new RuntimeException("Archive file " + archiveFile + " is not created");
}
}
This conversation was marked as resolved by yminqi

This comment has been minimized.

@iklam

iklam Mar 10, 2021
Member

I think we should have some error checks and clean up:

  • Remove the classlist file
  • Check if if the process exit status is 0
  • Remove the JSA file first, then try to dump it, and check if the file exists afterwards. If not, report the error. (For both dynamic and static dumps)

This comment has been minimized.

@iklam

iklam Mar 19, 2021
Member

The classlist file is not deleted after the dump has finished.

}
@@ -36,6 +36,7 @@
*/

import java.io.File;
import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.Files;
import java.nio.file.FileSystems;
@@ -104,13 +105,19 @@ private static LingeredApp createLingeredApp(String... args) throws Exception {
// app exit premature due to incompactible args
return null;
}
Process proc = app.getProcess();
if (e instanceof IOException && proc.exitValue() == 0) {
// Process started and exit normally.
return null;
}
throw e;
}
return app;

}

private static void test(String jcmdSub, String archiveFile, long pid, boolean expectOK) throws Exception {
private static void test(String jcmdSub, String archiveFile,
long pid, boolean expectOK) throws Exception {
boolean isStatic = jcmdSub.equals(SUBCMD_STATIC_DUMP);
String fileName = archiveFile != null ? archiveFile :
("java_pid" + pid + (isStatic ? "_static" : "_dynamic") + ".jsa");
@@ -145,62 +152,108 @@ private static void print2ln(String arg) {
System.out.println("\n" + arg + "\n");
}

private static void testStatic() throws Exception {
// Those two flags will not create a successful LingeredApp.
private static String[] noDumpFlags =
{"-XX:+DumpSharedSpaces",
"-Xshare:dump"};
// Those flags will be excluded in static dumping.
private static String[] excludeFlags =
{"-XX:DumpLoadedClassList=AnyFileName.classlist",
"-XX:+UseSharedSpaces",
"-XX:+RecordDynamicDumpInfo",
"-XX:+RequireSharedSpaces",
"-XX:+DynamicDumpSharedSpaces",
"-XX:ArchiveClassesAtExit=tmp.jsa",
"-Xshare:auto",
"-Xshare:on"};
This conversation was marked as resolved by yminqi
Comment on lines +160 to +168

This comment has been minimized.

@calvinccheung

calvinccheung Mar 24, 2021
Member

The excludeFlags doesn't match the ones in CDS.java.

226     private static String[] excludeFlags = {
227          "-XX:DumpLoadedClassList=",
228          "-XX:+DumpSharedSpaces",
229          "-XX:+DynamicDumpSharedSpaces",
230          "-XX:+RecordDynamicDumpInfo",
231          "-Xshare:",
232          "-XX:SharedClassListFile=",
233          "-XX:SharedArchiveFile=",
234          "-XX:ArchiveClassesAtExit=",
235          "-XX:+UseSharedSpaces",
236          "-XX:+RequireSharedSpaces"};

// Times to dump cds against same process.
private static final int ITERATION_TIMES = 2;

private static void test() throws Exception {
LingeredApp app = null;
long pid;

ArrayList<String> vmArgs = new ArrayList<String>();

// 1. Static dump with default name multiple times.
print2ln("1: Static dump with default name multiple times.");
vmArgs.add("-cp");
vmArgs.add(jarFile);
app = createLingeredApp(vmArgs.toArray(new String[0]));
app = createLingeredApp("-cp", jarFile);
pid = app.getPid();
for (int i = 0; i < 3; i++) {
for (int i = 0; i < ITERATION_TIMES; i++) {
test(SUBCMD_STATIC_DUMP, null, pid, EXPECT_PASS);
}
app.stopApp();
vmArgs.clear();

// 2. Test static dump with -XX:+RecordDynamicDumpInfo to create archive multiple times
print2ln("2. Test static dump with -XX:+RecordDynamicDumpInfo to create archive multiple times.");
vmArgs.add("-cp");
vmArgs.add(jarFile);
vmArgs.add("-Xlog:class+path");
vmArgs.add("-XX:+RecordDynamicDumpInfo");
app = createLingeredApp(vmArgs.toArray(new String[0]));
pid = app.getPid();

for (int i = 0; i < 3; i++) {
// 2. Test static dump with given file name.
print2ln("2. Test static dump with given file name.");
app = createLingeredApp("-cp", jarFile);
pid = app.getPid();
for (int i = 0; i < ITERATION_TIMES; i++) {
test(SUBCMD_STATIC_DUMP, STATIC_DUMP_FILE + "0" + i, pid, EXPECT_PASS);
}
app.stopApp();
}

private static void testDynamic() throws Exception {
ArrayList<String> vmArgs = new ArrayList<String>();
// 3. Test dynamic dump with -XX:+RecordDynamicDumpInfo.
print2ln("3. Test dynamic dump with -XX:+RecordDynamicDumpInfo.");
vmArgs.add("-cp");
vmArgs.add(jarFile);
vmArgs.add("-XX:+RecordDynamicDumpInfo");
LingeredApp app = createLingeredApp(vmArgs.toArray(new String[0]));
long pid = app.getPid();
// 3. Test static dump with flags which no dump will happen.
// This test will result classes.jsa in default server dir if -XX:SharedArchiveFile= not set.
print2ln("3. Test static dump with flags which will no dump will happen.");
for (String flag : noDumpFlags) {
app = createLingeredApp("-cp", jarFile, flag, "-XX:SharedArchiveFile=tmp.jsa");
// Following should not be executed.
if (app != null && app.getProcess().isAlive()) {
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, EXPECT_FAIL);
app.stopApp();
// if above executed OK, mean failed.
throw new RuntimeException("Should not dump successful with " + flag);
}
}

// 4. Test static dump with flags which will be filtered before dumping.
print2ln("4. Test static dump with flags which will be filtered before dumping.");
for (String flag : excludeFlags) {
app = createLingeredApp("-cp", jarFile, flag);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, EXPECT_PASS);
app.stopApp();
}


// 5. Test static with -Xshare:off will be OK to dump.
print2ln("5. Test static with -Xshare:off will be OK to dump.");
app = createLingeredApp("-Xshare:off", "-cp", jarFile);
pid = app.getPid();
test(SUBCMD_STATIC_DUMP, null, pid, EXPECT_PASS);
app.stopApp();

// 6. Test dynamic dump with -XX:+RecordDynamicDumpInfo.
print2ln("6. Test dynamic dump with -XX:+RecordDynamicDumpInfo.");
app = createLingeredApp("-cp", jarFile, "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "01", pid, EXPECT_PASS);
// 4. Test dynamic dump twice to same process
print2ln("4. Test dynamic dump second time to the same process.");

// 7. Test dynamic dump twice to same process.
print2ln("7. Test dynamic dump second time to the same process.");
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "02", pid, EXPECT_FAIL);
app.stopApp();
vmArgs.clear();
// 5. Test dynamic dump with -XX:ArchiveClassAtExit will fail.
print2ln("5. Test dynamic dump with -XX:ArchiveClassAtExit will fail.");
vmArgs.add("-Xshare:auto");
vmArgs.add("-XX:+RecordDynamicDumpInfo");
vmArgs.add("-XX:ArchiveClassesAtExit=noexist.jsa");
vmArgs.add("-cp");
vmArgs.add(jarFile);
app = createLingeredApp(vmArgs.toArray(new String[0]));

// 8. Test dynamic dump with -XX:-RecordDynamicDumpInfo.
print2ln("8. Test dynamic dump with -XX:-RecordDynamicDumpInfo.");
app = createLingeredApp("-cp", jarFile);
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, DYNAMIC_DUMP_FILE + "01", pid, EXPECT_FAIL);
app.stopApp();

// 9. Test dynamic dump with default archive name (null).
print2ln("9. Test dynamic dump with default archive name (null).");
app = createLingeredApp("-cp", jarFile, "-XX:+RecordDynamicDumpInfo");
pid = app.getPid();
test(SUBCMD_DYNAMIC_DUMP, null, pid, EXPECT_PASS);

// 10. Test dynamic dump with -XX:ArchiveClassAtExit will fail.
print2ln("10. Test dynamic dump with -XX:ArchiveClassAtExit will fail.");
app = createLingeredApp("-cp", jarFile,
"-Xshare:auto",
"-XX:+RecordDynamicDumpInfo",
"-XX:ArchiveClassesAtExit=AnyName.jsa");

if (app != null) {
if (app.getProcess().isAlive()) {
@@ -215,7 +268,6 @@ public static void main(String... args) throws Exception {
throw new SkippedException("CDS is not available for this JDK.");
}
buildJar();
testStatic();
testDynamic();
test();
}
}
ProTip! Use n and p to navigate between commits in a pull request.