| /* |
| * 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+.*"); |
| } |
| } |
| } |