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