1 /* 2 * Copyright (c) 2020, 2023, 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 package java.lang; 26 27 import java.io.PrintStream; 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.util.LinkedHashMap; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Objects; 34 import java.util.Set; 35 import java.util.stream.Collectors; 36 import static java.lang.StackWalker.Option.*; 37 import jdk.internal.access.JavaIOPrintStreamAccess; 38 import jdk.internal.access.SharedSecrets; 39 import jdk.internal.misc.InternalLock; 40 import jdk.internal.vm.Continuation; 41 42 /** 43 * Helper class to print the virtual thread stack trace when pinned. 44 * 45 * The class maintains a ClassValue with the hashes of stack traces that are pinned by 46 * code in that Class. This is used to avoid printing the same stack trace many times. 47 */ 48 class PinnedThreadPrinter { 49 private static final JavaIOPrintStreamAccess JIOPSA = SharedSecrets.getJavaIOPrintStreamAccess(); 50 private static final StackWalker STACK_WALKER; 51 static { 52 var options = Set.of(SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE); 53 PrivilegedAction<StackWalker> pa = () -> 54 LiveStackFrame.getStackWalker(options, VirtualThread.continuationScope()); 55 @SuppressWarnings("removal") 56 var stackWalker = AccessController.doPrivileged(pa); 57 STACK_WALKER = stackWalker; 58 } 59 60 private static final ClassValue<Hashes> HASHES = new ClassValue<>() { 61 @Override 62 protected Hashes computeValue(Class<?> type) { 63 return new Hashes(); 64 } 65 }; 66 67 @SuppressWarnings("serial") 68 private static class Hashes extends LinkedHashMap<Integer, Boolean> { 69 boolean add(int hash) { 70 return (putIfAbsent(hash, Boolean.TRUE) == null); 71 } 72 @Override 73 protected boolean removeEldestEntry(Map.Entry<Integer, Boolean> oldest) { 74 // limit number of hashes 75 return size() > 8; 76 } 77 } 78 79 /** 80 * Returns a hash of the given stack trace. The hash is based on the class, 81 * method and bytecode index. 82 */ 83 private static int hash(List<LiveStackFrame> stack) { 84 int hash = 0; 85 for (LiveStackFrame frame : stack) { 86 hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(), 87 frame.getMethodName(), 88 frame.getByteCodeIndex()); 89 } 90 return hash; 91 } 92 93 /** 94 * Returns true if the frame is native, a class initializer, or holds monitors. 95 */ 96 private static boolean isInterestingFrame(LiveStackFrame f) { 97 return f.isNativeMethod() 98 || "<clinit>".equals(f.getMethodName()) 99 || (f.getMonitors().length > 0); 100 } 101 102 /** 103 * Prints the current thread's stack trace. 104 * 105 * @param printAll true to print all stack frames, false to only print the 106 * frames that are native or holding a monitor 107 */ 108 static void printStackTrace(PrintStream out, Continuation.Pinned reason, boolean printAll) { 109 List<LiveStackFrame> stack = STACK_WALKER.walk(s -> 110 s.map(f -> (LiveStackFrame) f) 111 .filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class) 112 .collect(Collectors.toList()) 113 ); 114 Object lockObj = JIOPSA.lock(out); 115 if (lockObj instanceof InternalLock lock && lock.tryLock()) { 116 try { 117 // find the closest frame that is causing the thread to be pinned 118 stack.stream() 119 .filter(f -> isInterestingFrame(f)) 120 .map(LiveStackFrame::getDeclaringClass) 121 .findFirst() 122 .ifPresentOrElse(klass -> { 123 // print the stack trace if not already seen 124 int hash = hash(stack); 125 if (HASHES.get(klass).add(hash)) { 126 printStackTrace(out, reason, stack, printAll); 127 } 128 }, () -> printStackTrace(out, reason, stack, true)); // not found 129 130 } finally { 131 lock.unlock(); 132 } 133 } 134 } 135 136 private static void printStackTrace(PrintStream out, 137 Continuation.Pinned reason, 138 List<LiveStackFrame> stack, 139 boolean printAll) { 140 out.format("%s reason:%s%n", Thread.currentThread(), reason); 141 for (LiveStackFrame frame : stack) { 142 var ste = frame.toStackTraceElement(); 143 int monitorCount = frame.getMonitors().length; 144 if (monitorCount > 0) { 145 out.format(" %s <== monitors:%d%n", ste, monitorCount); 146 } else if (printAll || isInterestingFrame(frame)) { 147 out.format(" %s%n", ste); 148 } 149 } 150 } 151 }