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