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