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