d52a995f35
Reviewed-by: lmesnik, dholmes, rriggs, stefank
221 lines
9.3 KiB
Java
221 lines
9.3 KiB
Java
/*
|
|
* Copyright (c) 2021 SAP SE. All rights reserved.
|
|
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
|
|
/*
|
|
* This test tests the ability of NMT to work correctly when masses of allocations happen before NMT is initialized;
|
|
* That pre-NMT-init phase starts when the libjvm is loaded and the C++ dynamic initialization runs, and ends when
|
|
* NMT is initialized after the VM parsed its arguments in CreateJavaVM.
|
|
*
|
|
* During that phase, NMT is not yet initialized fully; C-heap allocations are kept in a special lookup table to
|
|
* be able to tell them apart from post-NMT-init initializations later. For details, see nmtPreInit.hpp.
|
|
*
|
|
* The size of this table is limited, and its load factor affects lookup time; that lookup time is paid throughout
|
|
* the VM life for all os::free() calls, regardless if NMT is on or not. Therefore we are interested in keeping the
|
|
* number of pre-NMT-init allocations low.
|
|
*
|
|
* Normally, the VM allocates about 500 surviving allocations (allocations which are not freed before NMT initialization
|
|
* finishes). The number is mainly influenced by the number of VM arguments, since those get strdup'ed around.
|
|
* Therefore, the only direct way to test pre-NMT-init allocations is by feeding the VM a lot of arguments, and this is
|
|
* what this test does.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @test id=normal-off
|
|
* @bug 8256844
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* java.management
|
|
* @run driver NMTInitializationTest normal off
|
|
*/
|
|
|
|
/**
|
|
* @test id=normal-detail
|
|
* @bug 8256844
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* java.management
|
|
* @run driver NMTInitializationTest normal detail
|
|
*/
|
|
|
|
/**
|
|
* @test id=default_long-off
|
|
* @bug 8256844
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* java.management
|
|
* @run driver NMTInitializationTest long off
|
|
*/
|
|
|
|
/**
|
|
* @test id=default_long-detail
|
|
* @bug 8256844
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* java.management
|
|
* @run driver NMTInitializationTest long detail
|
|
*/
|
|
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
|
|
import java.io.FileWriter;
|
|
import java.io.PrintWriter;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Random;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class NMTInitializationTest {
|
|
|
|
final static boolean debug = true;
|
|
|
|
static String randomString() {
|
|
Random r = new Random();
|
|
int len = r.nextInt(100) + 100;
|
|
StringBuilder bld = new StringBuilder();
|
|
for (int i = 0; i < len; i ++) {
|
|
bld.append(r.nextInt(26) + 'A');
|
|
}
|
|
return bld.toString();
|
|
}
|
|
|
|
static Path createCommandFile(int numlines) throws Exception {
|
|
String fileName = "commands_" + numlines + ".txt";
|
|
FileWriter fileWriter = new FileWriter(fileName);
|
|
PrintWriter printWriter = new PrintWriter(fileWriter);
|
|
String line = "-XX:ErrorFile=" + randomString();
|
|
for (long i = 0; i < numlines / 2; i++) {
|
|
printWriter.println(line);
|
|
}
|
|
printWriter.close();
|
|
return Paths.get(fileName);
|
|
}
|
|
|
|
enum TestMode {
|
|
// call the VM with a normal-ish command line (long but not oudlandishly so). We expect the lookup table after
|
|
// initialization to be sparsely populated and sport very short chain lengths.
|
|
mode_normal(30, 5),
|
|
// call the VM with an outlandishly long command line. We expect the lookup table after initialization
|
|
// to be densely populated but hopefully evenly distributed.
|
|
mode_long(20000, 20);
|
|
|
|
final int num_command_line_args;
|
|
final int expected_max_chain_len;
|
|
|
|
TestMode(int num_command_line_args, int expected_max_chain_len) {
|
|
this.num_command_line_args = num_command_line_args;
|
|
this.expected_max_chain_len = expected_max_chain_len;
|
|
}
|
|
};
|
|
|
|
enum NMTMode {
|
|
off, summary, detail
|
|
};
|
|
|
|
public static void main(String args[]) throws Exception {
|
|
TestMode testMode = TestMode.valueOf("mode_" + args[0]);
|
|
NMTMode nmtMode = NMTMode.valueOf(args[1]);
|
|
|
|
System.out.println("Test mode: " + testMode + ", NMT mode: " + nmtMode);
|
|
|
|
Path commandLineFile = createCommandFile(testMode.num_command_line_args);
|
|
|
|
ArrayList<String> vmArgs = new ArrayList<>();
|
|
vmArgs.add("-Xlog:nmt");
|
|
vmArgs.add("-XX:NativeMemoryTracking=" + nmtMode.name());
|
|
vmArgs.add("-XX:+UnlockDiagnosticVMOptions");
|
|
vmArgs.add("-XX:+PrintNMTStatistics");
|
|
|
|
if (commandLineFile != null) {
|
|
vmArgs.add("@" + commandLineFile.getFileName());
|
|
}
|
|
vmArgs.add("-version");
|
|
|
|
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(vmArgs);
|
|
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
|
if (debug) {
|
|
output.reportDiagnosticSummary();
|
|
}
|
|
|
|
output.shouldHaveExitValue(0);
|
|
|
|
// Now evaluate the output of -Xlog:nmt
|
|
// We expect something like:
|
|
// [0.001s][info][nmt] NMT initialized: detail
|
|
// [0.001s][info][nmt] Preinit state:
|
|
// [0.001s][info][nmt] entries: 342 (primary: 342, empties: 7577), sum bytes: 12996, longest chain length: 1
|
|
// [0.001s][info][nmt] pre-init mallocs: 375, pre-init reallocs: 6, pre-init frees: 33, pre-to-post reallocs: 4, pre-to-post frees: 0
|
|
|
|
output.shouldContain("NMT initialized: " + nmtMode.name());
|
|
output.shouldContain("Preinit state:");
|
|
if (nmtMode != NMTMode.off) { // in OFF mode LU table is deleted after VM initialization, nothing to see there
|
|
String regex = ".*entries: (\\d+).*sum bytes: (\\d+).*longest chain length: (\\d+).*";
|
|
output.shouldMatch(regex);
|
|
String line = output.firstMatch(regex, 0);
|
|
if (line == null) {
|
|
throw new RuntimeException("expected: " + regex);
|
|
}
|
|
System.out.println(line);
|
|
Pattern p = Pattern.compile(regex);
|
|
Matcher mat = p.matcher(line);
|
|
mat.matches();
|
|
int entries = Integer.parseInt(mat.group(1));
|
|
int sum_bytes = Integer.parseInt(mat.group(2));
|
|
int longest_chain = Integer.parseInt(mat.group(3));
|
|
System.out.println("found: " + entries + " - " + sum_bytes + longest_chain + ".");
|
|
|
|
// Now we test the state of the internal lookup table, and through our assumptions about
|
|
// early pre-NMT-init allocations:
|
|
// The normal allocation count of surviving pre-init allocations is around 300-500, with the sum of allocated
|
|
// bytes of a few dozen KB. We check these boundaries (with a very generous overhead) to see if the numbers are
|
|
// way off. If they are, we may either have a leak or just a lot more allocations than we thought before
|
|
// NMT initialization. Both cases should be investigated. Even if the allocations are valid, too many of them
|
|
// stretches the limits of the lookup map, and therefore may cause slower lookup. We should then either change
|
|
// the coding, reducing the number of allocations. Or enlarge the lookup table.
|
|
|
|
// Apply some sensible assumptions
|
|
if (entries > testMode.num_command_line_args + 2000) { // Note: normal baseline is 400-500
|
|
throw new RuntimeException("Suspiciously high number of pre-init allocations.");
|
|
}
|
|
if (sum_bytes > 128 * 1024 * 1024) { // Note: normal baseline is ~30-40KB
|
|
throw new RuntimeException("Suspiciously high pre-init memory usage.");
|
|
}
|
|
if (longest_chain > testMode.expected_max_chain_len) {
|
|
// Under normal circumstances, load factor of the map should be about 0.1. With a good hash distribution, we
|
|
// should rarely see even a chain > 1. Warn if we see exceedingly long bucket chains, since this indicates
|
|
// either that the hash algorithm is inefficient or we have a bug somewhere.
|
|
throw new RuntimeException("Suspiciously long bucket chains in lookup table.");
|
|
}
|
|
|
|
// Finally, check that we see our final NMT report:
|
|
output.shouldContain("Native Memory Tracking:");
|
|
output.shouldMatch("Total: reserved=\\d+, committed=\\d+.*");
|
|
}
|
|
}
|
|
}
|