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.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.file.InvalidPathException; 37 import java.nio.file.Path; 38 import java.util.Arrays; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 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_ARCHIVE = 1 << 0; 51 private static final int IS_DUMPING_METHOD_HANDLES = 1 << 1; 52 private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 2; 53 private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3; 54 private static final int IS_USING_ARCHIVE = 1 << 4; 55 private static final int IS_DUMPING_HEAP = 1 << 5; 56 private static final int IS_LOGGING_DYNAMIC_PROXIES = 1 << 6; 57 private static final int IS_DUMPING_PACKAGES = 1 << 7; 58 private static final int IS_DUMPING_PROTECTION_DOMAINS = 1 << 8; 59 private static final int configStatus = getCDSConfigStatus(); 60 61 /** 62 * Should we log the use of lambda form invokers? 63 */ 64 public static boolean isLoggingLambdaFormInvokers() { 65 return (configStatus & IS_LOGGING_LAMBDA_FORM_INVOKERS) != 0; 66 } 67 68 /** 69 * Is the VM writing to a (static or dynamic) CDS archive. 70 */ 71 public static boolean isDumpingArchive() { 72 return (configStatus & IS_DUMPING_ARCHIVE) != 0; 73 } 74 75 /** 76 * Is the VM using at least one CDS archive? 77 */ 78 public static boolean isUsingArchive() { 79 return (configStatus & IS_USING_ARCHIVE) != 0; 80 } 81 82 /** 83 * Is dumping static archive. 84 */ 85 public static boolean isDumpingStaticArchive() { 86 return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0; 87 } 88 89 public static boolean isDumpingHeap() { 90 return (configStatus & IS_DUMPING_HEAP) != 0; 91 } 92 93 public static boolean isLoggingDynamicProxies() { 94 return (configStatus & IS_LOGGING_DYNAMIC_PROXIES) != 0; 95 } 96 97 public static boolean isDumpingPackages() { 98 return (configStatus & IS_DUMPING_PACKAGES) != 0; 99 } 100 101 public static boolean isDumpingProtectionDomains() { 102 return (configStatus & IS_DUMPING_PROTECTION_DOMAINS) != 0; 103 } 104 105 public static boolean isSingleThreadVM() { 106 return isDumpingStaticArchive(); 107 } 108 109 private static native int getCDSConfigStatus(); 110 private static native void logLambdaFormInvoker(String line); 111 112 113 // Used only when dumping static archive to keep weak references alive to 114 // ensure that Soft/Weak Reference objects can be reliably archived. 115 private static ArrayList<Object> keepAliveList; 116 117 public static void keepAlive(Object s) { 118 assert isSingleThreadVM(); // no need for synchronization 119 assert isDumpingStaticArchive(); 120 if (keepAliveList == null) { 121 keepAliveList = new ArrayList<>(); 122 } 123 keepAliveList.add(s); 124 } 125 126 // This is called by native JVM code at the very end of Java execution before 127 // dumping the static archive. 128 // It collects the objects from keepAliveList so that they can be easily processed 129 // by the native JVM code to check that any Reference objects that need special 130 // clean up must have been registed with keepAlive() 131 private static Object[] getKeepAliveObjects() { 132 return keepAliveList.toArray(); 133 } 134 135 /** 136 * Initialize archived static fields in the given Class using archived 137 * values from CDS dump time. Also initialize the classes of objects in 138 * the archived graph referenced by those fields. 139 * 140 * Those static fields remain as uninitialized if there is no mapped CDS 141 * java heap data or there is any error during initialization of the 142 * object class in the archived graph. 143 */ 144 public static native void initializeFromArchive(Class<?> c); 145 146 /** 147 * Ensure that the native representation of all archived java.lang.Module objects 148 * are properly restored. 149 */ 150 public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader); 151 152 /** 153 * Returns a predictable "random" seed derived from the VM's build ID and version, 154 * to be used by java.util.ImmutableCollections to ensure that archived 155 * ImmutableCollections are always sorted the same order for the same VM build. 156 */ 157 public static native long getRandomSeedForDumping(); 158 159 /** 160 * log lambda form invoker holder, name and method type 161 */ 162 public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) { 163 if (isLoggingLambdaFormInvokers()) { 164 logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type); 165 } 166 } 167 168 /** 169 * log species 170 */ 171 public static void logSpeciesType(String prefix, String cn) { 172 if (isLoggingLambdaFormInvokers()) { 173 logLambdaFormInvoker(prefix + " " + cn); 174 } 175 } 176 177 public static void logDynamicProxy(ClassLoader loader, String proxyName, 178 Class<?>[] interfaces, int accessFlags) { 179 Objects.requireNonNull(proxyName); 180 Objects.requireNonNull(interfaces); 181 logDynamicProxy0(loader, proxyName, interfaces, accessFlags); 182 } 183 private static native void logDynamicProxy0(ClassLoader loader, String proxyName, 184 Class<?>[] interfaces, int accessFlags); 185 186 static final String DIRECT_HOLDER_CLASS_NAME = "java.lang.invoke.DirectMethodHandle$Holder"; 187 static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder"; 188 static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder"; 189 static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder"; 190 191 private static boolean isValidHolderName(String name) { 192 return name.equals(DIRECT_HOLDER_CLASS_NAME) || 193 name.equals(DELEGATING_HOLDER_CLASS_NAME) || 194 name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) || 195 name.equals(INVOKERS_HOLDER_CLASS_NAME); 196 } 197 198 private static boolean isBasicTypeChar(char c) { 199 return "LIJFDV".indexOf(c) >= 0; 200 } 201 202 private static boolean isValidMethodType(String type) { 203 String[] typeParts = type.split("_"); 204 // check return type (second part) 205 if (typeParts.length != 2 || typeParts[1].length() != 1 206 || !isBasicTypeChar(typeParts[1].charAt(0))) { 207 return false; 208 } 209 // first part 210 if (!isBasicTypeChar(typeParts[0].charAt(0))) { 211 return false; 212 } 213 for (int i = 1; i < typeParts[0].length(); i++) { 214 char c = typeParts[0].charAt(i); 215 if (!isBasicTypeChar(c)) { 216 if (!(c >= '0' && c <= '9')) { 217 return false; 218 } 219 } 220 } 221 return true; 222 } 223 224 // Throw exception on invalid input 225 private static void validateInputLines(String[] lines) { 226 for (String s: lines) { 227 if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) { 228 throw new IllegalArgumentException("Wrong prefix: " + s); 229 } 230 231 String[] parts = s.split(" "); 232 boolean isLF = s.startsWith("[LF_RESOLVE]"); 233 234 if (isLF) { 235 if (parts.length != 4) { 236 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 237 } 238 if (!isValidHolderName(parts[1])) { 239 throw new IllegalArgumentException("Invalid holder class name: " + parts[1]); 240 } 241 if (!isValidMethodType(parts[3])) { 242 throw new IllegalArgumentException("Invalid method type: " + parts[3]); 243 } 244 } else { 245 if (parts.length != 2) { 246 throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); 247 } 248 } 249 } 250 } 251 252 /** 253 * called from vm to generate MethodHandle holder classes 254 * @return {@code Object[]} if holder classes can be generated. 255 * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output 256 */ 257 private static Object[] generateLambdaFormHolderClasses(String[] lines) { 258 Objects.requireNonNull(lines); 259 validateInputLines(lines); 260 Stream<String> lineStream = Arrays.stream(lines); 261 Map<String, byte[]> result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream); 262 int size = result.size(); 263 Object[] retArray = new Object[size * 2]; 264 int index = 0; 265 for (Map.Entry<String, byte[]> entry : result.entrySet()) { 266 retArray[index++] = entry.getKey(); 267 retArray[index++] = entry.getValue(); 268 }; 269 return retArray; 270 } 271 272 private static native void dumpClassList(String listFileName); 273 private static native void dumpDynamicArchive(String archiveFileName); 274 275 private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) { 276 String fileName = "java_pid" + pid + "_" + tail; 277 new Thread( ()-> { 278 try (InputStreamReader isr = new InputStreamReader(stream); 279 BufferedReader rdr = new BufferedReader(isr); 280 PrintStream prt = new PrintStream(fileName)) { 281 prt.println("Command:"); 282 for (String s : cmds) { 283 prt.print(s + " "); 284 } 285 prt.println(""); 286 String line; 287 while((line = rdr.readLine()) != null) { 288 prt.println(line); 289 } 290 } catch (IOException e) { 291 throw new RuntimeException("IOException happens during drain stream to file " + 292 fileName + ": " + e.getMessage()); 293 }}).start(); 294 return fileName; 295 } 296 297 private static String[] excludeFlags = { 298 "-XX:DumpLoadedClassList=", 299 "-XX:+RecordDynamicDumpInfo", 300 "-Xshare:", 301 "-XX:SharedClassListFile=", 302 "-XX:SharedArchiveFile=", 303 "-XX:ArchiveClassesAtExit="}; 304 private static boolean containsExcludedFlags(String testStr) { 305 for (String e : excludeFlags) { 306 if (testStr.contains(e)) { 307 return true; 308 } 309 } 310 return false; 311 } 312 313 /** 314 * called from jcmd VM.cds to dump static or dynamic shared archive 315 * @param isStatic true for dump static archive or false for dynnamic archive. 316 * @param fileName user input archive name, can be null. 317 * @return The archive name if successfully dumped. 318 */ 319 private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception { 320 String cwd = new File("").getAbsolutePath(); // current dir used for printing message. 321 String currentPid = String.valueOf(ProcessHandle.current().pid()); 322 String archiveFileName = fileName != null ? fileName : 323 "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa"); 324 325 String tempArchiveFileName = archiveFileName + ".temp"; 326 File tempArchiveFile = new File(tempArchiveFileName); 327 // The operation below may cause exception if the file or its dir is protected. 328 if (!tempArchiveFile.exists()) { 329 tempArchiveFile.createNewFile(); 330 } 331 tempArchiveFile.delete(); 332 333 if (isStatic) { 334 String listFileName = archiveFileName + ".classlist"; 335 File listFile = new File(listFileName); 336 if (listFile.exists()) { 337 listFile.delete(); 338 } 339 dumpClassList(listFileName); 340 String jdkHome = StaticProperty.javaHome(); 341 String classPath = System.getProperty("java.class.path"); 342 List<String> cmds = new ArrayList<String>(); 343 cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java 344 cmds.add("-cp"); 345 cmds.add(classPath); 346 cmds.add("-Xlog:cds"); 347 cmds.add("-Xshare:dump"); 348 cmds.add("-XX:SharedClassListFile=" + listFileName); 349 cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName); 350 351 // All runtime args. 352 String[] vmArgs = VM.getRuntimeArguments(); 353 if (vmArgs != null) { 354 for (String arg : vmArgs) { 355 if (arg != null && !containsExcludedFlags(arg)) { 356 cmds.add(arg); 357 } 358 } 359 } 360 361 Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0])); 362 363 // Drain stdout/stderr to files in new threads. 364 String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds); 365 String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds); 366 367 proc.waitFor(); 368 // done, delete classlist file. 369 listFile.delete(); 370 371 // Check if archive has been successfully dumped. We won't reach here if exception happens. 372 // Throw exception if file is not created. 373 if (!tempArchiveFile.exists()) { 374 throw new RuntimeException("Archive file " + tempArchiveFileName + 375 " is not created, please check stdout file " + 376 cwd + File.separator + stdOutFileName + " or stderr file " + 377 cwd + File.separator + stdErrFileName + " for more detail"); 378 } 379 } else { 380 dumpDynamicArchive(tempArchiveFileName); 381 if (!tempArchiveFile.exists()) { 382 throw new RuntimeException("Archive file " + tempArchiveFileName + 383 " is not created, please check current working directory " + 384 cwd + " for process " + 385 currentPid + " output for more detail"); 386 } 387 } 388 // Override the existing archive file 389 File archiveFile = new File(archiveFileName); 390 if (archiveFile.exists()) { 391 archiveFile.delete(); 392 } 393 if (!tempArchiveFile.renameTo(archiveFile)) { 394 throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName); 395 } 396 // Everything goes well, print out the file name. 397 String archiveFilePath = new File(archiveFileName).getAbsolutePath(); 398 System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); 399 return archiveFilePath; 400 } 401 402 /** 403 * Detects if we need to emit explicit class initialization checks in 404 * AOT-cached MethodHandles and VarHandles before accessing static fields 405 * and methods. 406 * @see jdk.internal.misc.Unsafe::shouldBeInitialized 407 * 408 * @return false only if a call to {@code ensureClassInitialized} would have 409 * no effect during the application's production run. 410 */ 411 public static boolean needsClassInitBarrier(Class<?> c) { 412 if (c == null) { 413 throw new NullPointerException(); 414 } 415 416 if ((configStatus & IS_DUMPING_METHOD_HANDLES) == 0) { 417 return false; 418 } else { 419 return needsClassInitBarrier0(c); 420 } 421 } 422 423 private static native boolean needsClassInitBarrier0(Class<?> c); 424 425 /** 426 * This class is used only by native JVM code at CDS dump time for loading 427 * "unregistered classes", which are archived classes that are intended to 428 * be loaded by custom class loaders during runtime. 429 * See src/hotspot/share/cds/unregisteredClasses.cpp. 430 */ 431 private static class UnregisteredClassLoader extends URLClassLoader { 432 private String currentClassName; 433 private Class<?> currentSuperClass; 434 private Class<?>[] currentInterfaces; 435 436 /** 437 * Used only by native code. Construct an UnregisteredClassLoader for loading 438 * unregistered classes from the specified file. If the file doesn't exist, 439 * the exception will be caughted by native code which will print a warning message and continue. 440 * 441 * @param fileName path of the the JAR file to load unregistered classes from. 442 */ 443 private UnregisteredClassLoader(String fileName) throws InvalidPathException, IOException { 444 super(toURLArray(fileName), /*parent*/null); 445 currentClassName = null; 446 currentSuperClass = null; 447 currentInterfaces = null; 448 } 449 450 private static URL[] toURLArray(String fileName) throws InvalidPathException, IOException { 451 if (!((new File(fileName)).exists())) { 452 throw new IOException("No such file: " + fileName); 453 } 454 return new URL[] { 455 // Use an intermediate File object to construct a URI/URL without 456 // authority component as URLClassPath can't handle URLs with a UNC 457 // server name in the authority component. 458 Path.of(fileName).toRealPath().toFile().toURI().toURL() 459 }; 460 } 461 462 463 /** 464 * Load the class of the given <code>/name<code> from the JAR file that was given to 465 * the constructor of the current UnregisteredClassLoader instance. This class must be 466 * a direct subclass of <code>superClass</code>. This class must be declared to implement 467 * the specified <code>interfaces</code>. 468 * <p> 469 * This method must be called in a single threaded context. It will never be recursed (thus 470 * the asserts) 471 * 472 * @param name the name of the class to be loaded. 473 * @param superClass must not be null. The named class must have a super class. 474 * @param interfaces could be null if the named class does not implement any interfaces. 475 */ 476 private Class<?> load(String name, Class<?> superClass, Class<?>[] interfaces) 477 throws ClassNotFoundException 478 { 479 assert currentClassName == null; 480 assert currentSuperClass == null; 481 assert currentInterfaces == null; 482 483 try { 484 currentClassName = name; 485 currentSuperClass = superClass; 486 currentInterfaces = interfaces; 487 488 return findClass(name); 489 } finally { 490 currentClassName = null; 491 currentSuperClass = null; 492 currentInterfaces = null; 493 } 494 } 495 496 /** 497 * This method must be called from inside the <code>load()</code> method. The <code>/name<code> 498 * can be only: 499 * <ul> 500 * <li> the <code>name</code> parameter for <code>load()</code> 501 * <li> the name of the <code>superClass</code> parameter for <code>load()</code> 502 * <li> the name of one of the interfaces in <code>interfaces</code> parameter for <code>load()</code> 503 * <ul> 504 * 505 * For all other cases, a <code>ClassNotFoundException</code> will be thrown. 506 */ 507 protected Class<?> findClass(final String name) 508 throws ClassNotFoundException 509 { 510 Objects.requireNonNull(currentClassName); 511 Objects.requireNonNull(currentSuperClass); 512 513 if (name.equals(currentClassName)) { 514 // Note: the following call will call back to <code>this.findClass(name)</code> to 515 // resolve the super types of the named class. 516 return super.findClass(name); 517 } 518 if (name.equals(currentSuperClass.getName())) { 519 return currentSuperClass; 520 } 521 if (currentInterfaces != null) { 522 for (Class<?> c : currentInterfaces) { 523 if (name.equals(c.getName())) { 524 return c; 525 } 526 } 527 } 528 529 throw new ClassNotFoundException(name); 530 } 531 } 532 533 /** 534 * This class is used only by native JVM code to spawn a child JVM process to assemble 535 * the AOT cache. <code>args[]</code> are passed in the <code>JAVA_TOOL_OPTIONS</code> 536 * environment variable as described in 537 * https://docs.oracle.com/en/java/javase/24/docs/specs/jvmti.html#tooloptions 538 */ 539 private static class ProcessLauncher { 540 static int execWithJavaToolOptions(String javaLauncher, String args[]) throws IOException, InterruptedException { 541 ProcessBuilder pb = new ProcessBuilder().inheritIO().command(javaLauncher); 542 StringBuilder sb = new StringBuilder(); 543 String prefix = ""; 544 for (String arg : args) { 545 sb.append(prefix); 546 547 for (int i = 0; i < arg.length(); i++) { 548 char c = arg.charAt(i); 549 if (c == '"' || Character.isWhitespace(c)) { 550 sb.append('\''); 551 sb.append(c); 552 sb.append('\''); 553 } else if (c == '\'') { 554 sb.append('"'); 555 sb.append(c); 556 sb.append('"'); 557 } else { 558 sb.append(c); 559 } 560 } 561 562 prefix = " "; 563 } 564 565 Map<String, String> env = pb.environment(); 566 env.put("JAVA_TOOL_OPTIONS", sb.toString()); 567 env.remove("_JAVA_OPTIONS"); 568 env.remove("CLASSPATH"); 569 Process process = pb.start(); 570 return process.waitFor(); 571 } 572 } 573 574 /** 575 * This class is to ensure that the dynamic CDS archive contains at least one class, so we can avoid 576 * error handling for the degenerative case where the dynamic archive is completely empty (which doesn't 577 * happen for realistic applications). 578 */ 579 private static class DummyForDynamicArchive {} 580 }