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