1 /* 2 * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.misc; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.InputStreamReader; 31 import java.io.InputStream; 32 import java.io.IOException; 33 import java.io.PrintStream; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.util.Arrays; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Objects; 42 import java.util.jar.JarFile; 43 import java.util.stream.Stream; 44 45 import jdk.internal.access.SharedSecrets; 46 import jdk.internal.util.StaticProperty; 47 48 public class CDS { 49 // Must be in sync with cdsConfig.hpp 50 private static final int IS_DUMPING_AOT_LINKED_CLASSES = 1 << 0; 51 private static final int IS_DUMPING_ARCHIVE = 1 << 1; 52 private static final int IS_DUMPING_METHOD_HANDLES = 1 << 2; 53 private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 3; 54 private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 4; 55 private static final int IS_USING_ARCHIVE = 1 << 5; 56 57 private static final int configStatus = getCDSConfigStatus(); 58 59 /** 60 * Should we log the use of lambda form invokers? 61 */ 62 public static boolean isLoggingLambdaFormInvokers() { 63 return (configStatus & IS_LOGGING_LAMBDA_FORM_INVOKERS) != 0; 64 } 65 66 /** 67 * Is the VM writing to a (static or dynamic) CDS archive. 68 */ 69 public static boolean isDumpingArchive() { 70 return (configStatus & IS_DUMPING_ARCHIVE) != 0; 71 } 72 73 /** 74 * Is the VM using at least one CDS archive? 75 */ 76 public static boolean isUsingArchive() { 77 return (configStatus & IS_USING_ARCHIVE) != 0; 78 } 79 80 /** 81 * Is dumping static archive. 82 */ 83 public static boolean isDumpingStaticArchive() { 84 return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0; 85 } 86 87 public static boolean isDumpingAOTLinkedClasses() { 88 return (configStatus & IS_DUMPING_AOT_LINKED_CLASSES) != 0; 89 } 90 91 public static boolean isSingleThreadVM() { 92 return isDumpingStaticArchive(); 93 } 94 95 private static native int getCDSConfigStatus(); 96 private static native void logLambdaFormInvoker(String line); 97 98 99 // Used only when dumping static archive to keep weak references alive to 100 // ensure that Soft/Weak Reference objects can be reliably archived. 101 private static ArrayList<Object> keepAliveList; 102 103 public static void keepAlive(Object s) { 104 assert isSingleThreadVM(); // no need for synchronization 105 assert isDumpingStaticArchive(); 106 if (keepAliveList == null) { 107 keepAliveList = new ArrayList<>(); 108 } 109 keepAliveList.add(s); 110 } 111 112 // This is called by native JVM code at the very end of Java execution before 113 // dumping the static archive. 114 // It collects the objects from keepAliveList so that they can be easily processed 115 // by the native JVM code to check that any Reference objects that need special 116 // clean up must have been registed with keepAlive() 117 private static Object[] getKeepAliveObjects() { 118 return keepAliveList.toArray(); 119 } 120 121 /** 122 * Initialize archived static fields in the given Class using archived 123 * values from CDS dump time. Also initialize the classes of objects in 124 * the archived graph referenced by those fields. 125 * 126 * Those static fields remain as uninitialized if there is no mapped CDS 127 * java heap data or there is any error during initialization of the 128 * object class in the archived graph. 129 */ 130 public static native void initializeFromArchive(Class<?> c); 131 132 /** 133 * Ensure that the native representation of all archived java.lang.Module objects 134 * are properly restored. 135 */ 136 public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader); 137 138 /** 139 * Returns a predictable "random" seed derived from the VM's build ID and version, 140 * to be used by java.util.ImmutableCollections to ensure that archived 141 * ImmutableCollections are always sorted the same order for the same VM build. 142 */ 143 public static native long getRandomSeedForDumping(); 144 145 /** 146 * log lambda form invoker holder, name and method type 147 */ 148 public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) { 149 if (isLoggingLambdaFormInvokers()) { 150 logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type); 151 } 152 } 153 154 /** 155 * log species 156 */ 157 public static void logSpeciesType(String prefix, String cn) { 158 if (isLoggingLambdaFormInvokers()) { 159 logLambdaFormInvoker(prefix + " " + cn); 160 } 161 } 162 163 static final String DIRECT_HOLDER_CLASS_NAME = "java.lang.invoke.DirectMethodHandle$Holder"; 164 static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder"; 165 static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder"; 166 static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder"; 167 168 private static boolean isValidHolderName(String name) { 169 return name.equals(DIRECT_HOLDER_CLASS_NAME) || 170 name.equals(DELEGATING_HOLDER_CLASS_NAME) || 171 name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) || 172 name.equals(INVOKERS_HOLDER_CLASS_NAME); 173 } 174 175 private static boolean isBasicTypeChar(char c) { 176 return "LIJFDV".indexOf(c) >= 0; 177 } 178 179 private static boolean isValidMethodType(String type) { 180 String[] typeParts = type.split("_"); 181 // check return type (second part) 182 if (typeParts.length != 2 || typeParts[1].length() != 1 183 || !isBasicTypeChar(typeParts[1].charAt(0))) { 184 return false; 185 } 186 // first part 187 if (!isBasicTypeChar(typeParts[0].charAt(0))) { 188 return false; 189 } 190 for (int i = 1; i < typeParts[0].length(); i++) { 191 char c = typeParts[0].charAt(i); 192 if (!isBasicTypeChar(c)) { 193 if (!(c >= '0' && c <= '9')) { 194 return false; 195 } 196 } 197 } 198 return true; 199 } 200 201 // Throw exception on invalid input 202 private static void validateInputLines(String[] lines) { 203 for (String s: lines) { 204 if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) { 205 throw new IllegalArgumentException("Wrong prefix: " + s); 206 } 207 208 String[] parts = s.split(" "); 209 boolean isLF = s.startsWith("[LF_RESOLVE]"); 210 211 if (isLF) { 212 if (parts.length != 4) { 213 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 214 } 215 if (!isValidHolderName(parts[1])) { 216 throw new IllegalArgumentException("Invalid holder class name: " + parts[1]); 217 } 218 if (!isValidMethodType(parts[3])) { 219 throw new IllegalArgumentException("Invalid method type: " + parts[3]); 220 } 221 } else { 222 if (parts.length != 2) { 223 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 224 } 225 } 226 } 227 } 228 229 /** 230 * called from vm to generate MethodHandle holder classes 231 * @return {@code Object[]} if holder classes can be generated. 232 * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output 233 */ 234 private static Object[] generateLambdaFormHolderClasses(String[] lines) { 235 Objects.requireNonNull(lines); 236 validateInputLines(lines); 237 Stream<String> lineStream = Arrays.stream(lines); 238 Map<String, byte[]> result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream); 239 int size = result.size(); 240 Object[] retArray = new Object[size * 2]; 241 int index = 0; 242 for (Map.Entry<String, byte[]> entry : result.entrySet()) { 243 retArray[index++] = entry.getKey(); 244 retArray[index++] = entry.getValue(); 245 }; 246 return retArray; 247 } 248 249 private static native void dumpClassList(String listFileName); 250 private static native void dumpDynamicArchive(String archiveFileName); 251 252 private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) { 253 String fileName = "java_pid" + pid + "_" + tail; 254 new Thread( ()-> { 255 try (InputStreamReader isr = new InputStreamReader(stream); 256 BufferedReader rdr = new BufferedReader(isr); 257 PrintStream prt = new PrintStream(fileName)) { 258 prt.println("Command:"); 259 for (String s : cmds) { 260 prt.print(s + " "); 261 } 262 prt.println(""); 263 String line; 264 while((line = rdr.readLine()) != null) { 265 prt.println(line); 266 } 267 } catch (IOException e) { 268 throw new RuntimeException("IOException happens during drain stream to file " + 269 fileName + ": " + e.getMessage()); 270 }}).start(); 271 return fileName; 272 } 273 274 private static String[] excludeFlags = { 275 "-XX:DumpLoadedClassList=", 276 "-XX:+RecordDynamicDumpInfo", 277 "-Xshare:", 278 "-XX:SharedClassListFile=", 279 "-XX:SharedArchiveFile=", 280 "-XX:ArchiveClassesAtExit="}; 281 private static boolean containsExcludedFlags(String testStr) { 282 for (String e : excludeFlags) { 283 if (testStr.contains(e)) { 284 return true; 285 } 286 } 287 return false; 288 } 289 290 /** 291 * called from jcmd VM.cds to dump static or dynamic shared archive 292 * @param isStatic true for dump static archive or false for dynnamic archive. 293 * @param fileName user input archive name, can be null. 294 * @return The archive name if successfully dumped. 295 */ 296 private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception { 297 String cwd = new File("").getAbsolutePath(); // current dir used for printing message. 298 String currentPid = String.valueOf(ProcessHandle.current().pid()); 299 String archiveFileName = fileName != null ? fileName : 300 "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa"); 301 302 String tempArchiveFileName = archiveFileName + ".temp"; 303 File tempArchiveFile = new File(tempArchiveFileName); 304 // The operation below may cause exception if the file or its dir is protected. 305 if (!tempArchiveFile.exists()) { 306 tempArchiveFile.createNewFile(); 307 } 308 tempArchiveFile.delete(); 309 310 if (isStatic) { 311 String listFileName = archiveFileName + ".classlist"; 312 File listFile = new File(listFileName); 313 if (listFile.exists()) { 314 listFile.delete(); 315 } 316 dumpClassList(listFileName); 317 String jdkHome = StaticProperty.javaHome(); 318 String classPath = System.getProperty("java.class.path"); 319 List<String> cmds = new ArrayList<String>(); 320 cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java 321 cmds.add("-cp"); 322 cmds.add(classPath); 323 cmds.add("-Xlog:cds"); 324 cmds.add("-Xshare:dump"); 325 cmds.add("-XX:SharedClassListFile=" + listFileName); 326 cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName); 327 328 // All runtime args. 329 String[] vmArgs = VM.getRuntimeArguments(); 330 if (vmArgs != null) { 331 for (String arg : vmArgs) { 332 if (arg != null && !containsExcludedFlags(arg)) { 333 cmds.add(arg); 334 } 335 } 336 } 337 338 Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0])); 339 340 // Drain stdout/stderr to files in new threads. 341 String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds); 342 String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds); 343 344 proc.waitFor(); 345 // done, delete classlist file. 346 listFile.delete(); 347 348 // Check if archive has been successfully dumped. We won't reach here if exception happens. 349 // Throw exception if file is not created. 350 if (!tempArchiveFile.exists()) { 351 throw new RuntimeException("Archive file " + tempArchiveFileName + 352 " is not created, please check stdout file " + 353 cwd + File.separator + stdOutFileName + " or stderr file " + 354 cwd + File.separator + stdErrFileName + " for more detail"); 355 } 356 } else { 357 dumpDynamicArchive(tempArchiveFileName); 358 if (!tempArchiveFile.exists()) { 359 throw new RuntimeException("Archive file " + tempArchiveFileName + 360 " is not created, please check current working directory " + 361 cwd + " for process " + 362 currentPid + " output for more detail"); 363 } 364 } 365 // Override the existing archive file 366 File archiveFile = new File(archiveFileName); 367 if (archiveFile.exists()) { 368 archiveFile.delete(); 369 } 370 if (!tempArchiveFile.renameTo(archiveFile)) { 371 throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName); 372 } 373 // Everything goes well, print out the file name. 374 String archiveFilePath = new File(archiveFileName).getAbsolutePath(); 375 System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); 376 return archiveFilePath; 377 } 378 379 /** 380 * Detects if we need to emit explicit class initialization checks in 381 * AOT-cached MethodHandles and VarHandles before accessing static fields 382 * and methods. 383 * @see jdk.internal.misc.Unsafe::shouldBeInitialized 384 * 385 * @return false only if a call to {@code ensureClassInitialized} would have 386 * no effect during the application's production run. 387 */ 388 public static boolean needsClassInitBarrier(Class<?> c) { 389 if (c == null) { 390 throw new NullPointerException(); 391 } 392 393 if ((configStatus & IS_DUMPING_METHOD_HANDLES) == 0) { 394 return false; 395 } else { 396 return needsClassInitBarrier0(c); 397 } 398 } 399 400 private static native boolean needsClassInitBarrier0(Class<?> c); 401 402 /** 403 * This class is used only by native JVM code at CDS dump time for loading 404 * "unregistered classes", which are archived classes that are intended to 405 * be loaded by custom class loaders during runtime. 406 * See src/hotspot/share/cds/unregisteredClasses.cpp. 407 */ 408 private static class UnregisteredClassLoader extends ClassLoader { 409 static { 410 registerAsParallelCapable(); 411 } 412 413 static interface Source { 414 public byte[] readClassFile(String className) throws IOException; 415 } 416 417 static class JarSource implements Source { 418 private final JarFile jar; 419 420 JarSource(File file) throws IOException { 421 jar = new JarFile(file); 422 } 423 424 @Override 425 public byte[] readClassFile(String className) throws IOException { 426 final var entryName = className.replace('.', '/').concat(".class"); 427 final var entry = jar.getEntry(entryName); 428 if (entry == null) { 429 throw new IOException("No such entry: " + entryName + " in " + jar.getName()); 430 } 431 try (final var in = jar.getInputStream(entry)) { 432 return in.readAllBytes(); 433 } 434 } 435 } 436 437 static class DirSource implements Source { 438 private final String basePath; 439 440 DirSource(File dir) { 441 assert dir.isDirectory(); 442 basePath = dir.toString(); 443 } 444 445 @Override 446 public byte[] readClassFile(String className) throws IOException { 447 final var subPath = className.replace('.', File.separatorChar).concat(".class"); 448 final var fullPath = Path.of(basePath, subPath); 449 return Files.readAllBytes(fullPath); 450 } 451 } 452 453 private final HashMap<String, Source> sources = new HashMap<>(); 454 455 private Source resolveSource(String path) throws IOException { 456 Source source = sources.get(path); 457 if (source != null) { 458 return source; 459 } 460 461 final var file = new File(path); 462 if (!file.exists()) { 463 throw new IOException("No such file: " + path); 464 } 465 if (file.isFile()) { 466 source = new JarSource(file); 467 } else if (file.isDirectory()) { 468 source = new DirSource(file); 469 } else { 470 throw new IOException("Not a normal file: " + path); 471 } 472 sources.put(path, source); 473 474 return source; 475 } 476 477 /** 478 * Load the class of the given <code>name</code> from the given <code>source</code>. 479 * <p> 480 * All super classes and interfaces of the named class must have already been loaded: 481 * either defined by this class loader (unregistered ones) or loaded, possibly indirectly, 482 * by the system class loader (registered ones). 483 * <p> 484 * If the named class has a registered super class or interface named N there should be no 485 * unregistered class or interface named N loaded yet. 486 * 487 * @param name the name of the class to be loaded. 488 * @param source path to a directory or a JAR file from which the named class should be 489 * loaded. 490 */ 491 private Class<?> load(String name, String source) throws IOException { 492 final Source resolvedSource = resolveSource(source); 493 final byte[] bytes = resolvedSource.readClassFile(name); 494 // 'defineClass()' may cause loading of supertypes of this unregistered class by VM 495 // calling 'this.loadClass()'. 496 // 497 // For any supertype S named SN specified in the classlist the following is ensured by 498 // the CDS implementation: 499 // - if S is an unregistered class it must have already been defined by this class 500 // loader and thus will be found by 'this.findLoadedClass(SN)', 501 // - if S is not an unregistered class there should be no unregistered class named SN 502 // loaded yet so either S has previously been (indirectly) loaded by this class loader 503 // and thus it will be found when calling 'this.findLoadedClass(SN)' or it will be 504 // found when delegating to the system class loader, which must have already loaded S, 505 // by calling 'this.getParent().loadClass(SN, false)'. 506 // See the implementation of 'ClassLoader.loadClass()' for details. 507 // 508 // Therefore, we should resolve all supertypes to the expected ones as specified by the 509 // "super:" and "interfaces:" attributes in the classlist. This invariant is validated 510 // by the C++ function 'ClassListParser::load_class_from_source()'. 511 assert getParent() == getSystemClassLoader(); 512 return defineClass(name, bytes, 0, bytes.length); 513 } 514 } 515 516 /** 517 * This class is used only by native JVM code to spawn a child JVM process to assemble 518 * the AOT cache. <code>args[]</code> are passed in the <code>JAVA_TOOL_OPTIONS</code> 519 * environment variable. 520 */ 521 private static class ProcessLauncher { 522 static int execWithJavaToolOptions(String javaLauncher, String args[]) throws IOException, InterruptedException { 523 ProcessBuilder pb = new ProcessBuilder().inheritIO().command(javaLauncher); 524 StringBuilder sb = new StringBuilder(); 525 526 // Encode the args as described in 527 // https://docs.oracle.com/en/java/javase/24/docs/specs/jvmti.html#tooloptions 528 String prefix = ""; 529 for (String arg : args) { 530 sb.append(prefix); 531 532 for (int i = 0; i < arg.length(); i++) { 533 char c = arg.charAt(i); 534 if (c == '"' || Character.isWhitespace(c)) { 535 sb.append('\''); 536 sb.append(c); 537 sb.append('\''); 538 } else if (c == '\'') { 539 sb.append('"'); 540 sb.append(c); 541 sb.append('"'); 542 } else { 543 sb.append(c); 544 } 545 } 546 547 prefix = " "; 548 } 549 550 Map<String, String> env = pb.environment(); 551 env.put("JAVA_TOOL_OPTIONS", sb.toString()); 552 env.remove("_JAVA_OPTIONS"); 553 env.remove("CLASSPATH"); 554 Process process = pb.start(); 555 return process.waitFor(); 556 } 557 } 558 }