diff --git a/src/hotspot/share/interpreter/bytecodeTracer.cpp b/src/hotspot/share/interpreter/bytecodeTracer.cpp index 4578a3eec4e..cbe28e92c51 100644 --- a/src/hotspot/share/interpreter/bytecodeTracer.cpp +++ b/src/hotspot/share/interpreter/bytecodeTracer.cpp @@ -40,21 +40,17 @@ #include "utilities/align.hpp" // Prints the current bytecode and its attributes using bytecode-specific information. +// Printing bytecodes is not an idempotent operation, as this relies on modifying state. +// An instance of this class is thus intended to only print a single bytecode. All state +// shared between multiple bytecodes needs to be in BytecodeTracerData. class BytecodePrinter { private: - // %%% This field is not GC-ed, and so can contain garbage - // between critical sections. Use only pointer-comparison - // operations on the pointer, except within a critical section. - // (Also, ensure that occasional false positives are benign.) - Method* _current_method; - bool _is_wide; - Bytecodes::Code _code; - address _next_pc; // current decoding position - int _flags; - bool _use_cp_cache; + BytecodeTracerData* _data; + int _flags; + Bytecodes::Code _raw_code; // note: some methods translate this to the Java bytecode + address _next_pc; // current decoding position, destructive - bool use_cp_cache() const { return _use_cp_cache; } void align() { _next_pc = align_up(_next_pc, sizeof(jint)); } int get_byte() { return *(jbyte*) _next_pc++; } // signed int get_index_u1() { return *(address)_next_pc++; } // returns 0x00 - 0xff as an int @@ -65,11 +61,22 @@ class BytecodePrinter { int get_Java_index_u2() { int i = Bytes::get_Java_u2 (_next_pc); _next_pc += 2; return i; } int get_Java_index_u4() { int i = Bytes::get_Java_u4 (_next_pc); _next_pc += 4; return i; } int get_index_special() { return (is_wide()) ? get_Java_index_u2() : get_index_u1(); } - Method* method() const { return _current_method; } - bool is_wide() const { return _is_wide; } - Bytecodes::Code raw_code() const { return Bytecodes::Code(_code); } - ConstantPool* constants() const { return method()->constants(); } - ConstantPoolCache* cpcache() const { assert(use_cp_cache(), "must be"); return constants()->cache(); } + + // Warning: only do pointer comparison between critical sections. + Method* method() const { return _data->current_method(); } + void set_method(Method* current) { _data->set_current_method(current); } + + // Warning: should only be used for comparison and not dereferenced. + intptr_t* fp() const { return _data->current_fp(); } + void set_fp(intptr_t* current) { _data->set_current_fp(current); } + + bool is_wide() const { return _data->is_wide(); } + void set_wide(bool wide) { _data->set_wide(wide); } + + ConstantPool* constants() const { return method()->constants(); } + // This may be called during linking after bytecodes are rewritten to point to the cpCache. + bool use_cp_cache() const { return constants()->cache() != nullptr; } + ConstantPoolCache* cpcache() const { assert(use_cp_cache(), "must be"); return constants()->cache(); } void print_constant(int i, outputStream* st); void print_cpcache_entry(int cpc_index, outputStream* st); @@ -81,35 +88,32 @@ class BytecodePrinter { void print_method_data_at(int bci, outputStream* st); public: - BytecodePrinter(int flags = 0) : _is_wide(false), _code(Bytecodes::_illegal), _flags(flags) {} + BytecodePrinter(BytecodeTracerData* data, int flags = 0) : + _data(data), + _flags(flags), + _raw_code(Bytecodes::_illegal), + _next_pc(nullptr) {} #ifndef PRODUCT - BytecodePrinter(Method* prev_method) : BytecodePrinter(0) { - _current_method = prev_method; - } - // This method is called while executing the raw bytecodes, so none of // the adjustments that BytecodeStream performs applies. - void trace(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) { + void trace(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) { + assert(_raw_code == Bytecodes::_illegal, "invariant"); ResourceMark rm; - bool method_changed = _current_method != method(); - _current_method = method(); - _use_cp_cache = method->constants()->cache() != nullptr; + // Method changes can be another method getting called, or a self-recursive call. + bool method_changed = (this->method() != method()) || (this->fp() != fp); + set_method(method()); + set_fp(fp); assert(method->method_holder()->is_linked(), "this function must be called on methods that are already executing"); - + // If the method changed (new method call, return to previous method after call finishes), + // the signature needs to be re-printed for interpretability. if (method_changed) { - // Note 1: This code will not work as expected with true MT/MP. - // Need an explicit lock or a different solution. - // It is possible for this block to be skipped, if a garbage - // _current_method pointer happens to have the same bits as - // the incoming method. We could lose a line of trace output. - // This is acceptable in a debug-only feature. - st->cr(); st->print("[%zu] ", Thread::current()->osthread()->thread_id_for_printing()); method->print_name(st); st->cr(); } + Bytecodes::Code code; if (is_wide()) { // bcp wasn't advanced if previous bytecode was _wide. @@ -117,14 +121,13 @@ class BytecodePrinter { } else { code = Bytecodes::code_at(method(), bcp); } - _code = code; + _raw_code = code; _next_pc = is_wide() ? bcp+2 : bcp+1; + + bool is_terminal = Bytecodes::is_terminal(code); // Trace each bytecode unless we're truncating the tracing output, then only print the first - // bytecode in every method as well as returns/throws that pop control flow - if (!TraceBytecodesTruncated || method_changed || - code == Bytecodes::_athrow || - code == Bytecodes::_return_register_finalizer || - (code >= Bytecodes::_ireturn && code <= Bytecodes::_return)) { + // bytecode in every method as well as returns/throws that pop control flow. + if (!TraceBytecodesTruncated || method_changed || is_terminal) { int bci = (int)(bcp - method->code_base()); st->print("[%zu] ", Thread::current()->osthread()->thread_id_for_printing()); if (Verbose) { @@ -138,8 +141,16 @@ class BytecodePrinter { } // Set is_wide for the next one, since the caller of this doesn't skip // the next bytecode. - _is_wide = (code == Bytecodes::_wide); - _code = Bytecodes::_illegal; + set_wide(code == Bytecodes::_wide); + // Finished using the code, reset to illegal. + _raw_code = Bytecodes::_illegal; + + // Invalidate the current method to force a signature change. In some + // rare cases, the method and frame pointers aren't enough to determine + // a new method invocation, so this ensures the signature is re-printed. + if (is_terminal) { + set_method(nullptr); + } if (TraceBytecodesStopAt != 0 && BytecodeCounter::counter_value() >= TraceBytecodesStopAt) { TraceBytecodes = false; @@ -150,17 +161,16 @@ class BytecodePrinter { // Used for Method::print_codes(). The input bcp comes from // BytecodeStream, which will skip wide bytecodes. void trace(const methodHandle& method, address bcp, outputStream* st) { - _current_method = method(); - // This may be called during linking after bytecodes are rewritten to point to the cpCache. - _use_cp_cache = method->constants()->cache() != nullptr; + assert(_raw_code == Bytecodes::_illegal, "invariant"); + set_method(method()); ResourceMark rm; Bytecodes::Code code = Bytecodes::code_at(method(), bcp); // Set is_wide - _is_wide = (code == Bytecodes::_wide); + set_wide(code == Bytecodes::_wide); if (is_wide()) { code = Bytecodes::code_at(method(), bcp+1); } - _code = code; + _raw_code = code; int bci = (int)(bcp - method->code_base()); // Print bytecode index and name if (ClassPrinter::has_mode(_flags, ClassPrinter::PRINT_BYTECODE_ADDR)) { @@ -180,26 +190,20 @@ class BytecodePrinter { }; #ifndef PRODUCT -// We need a global instance to keep track of the method being printed so we can report that -// the method has changed. If this method is redefined and removed, that's ok because the method passed -// in won't match, and this will print the method passed in again. Racing threads changing this global -// will result in reprinting the method passed in again. -static Method* _method_currently_being_printed = nullptr; - -void BytecodeTracer::trace_interpreter(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) { +void BytecodeTracer::trace_interpreter(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) { if (TraceBytecodes && BytecodeCounter::counter_value() >= TraceBytecodesAt) { - BytecodePrinter printer(AtomicAccess::load_acquire(&_method_currently_being_printed)); - stringStream buf; - printer.trace(method, bcp, tos, tos2, &buf); - st->print("%s", buf.freeze()); - // Save method currently being printed to detect when method printing changes. - AtomicAccess::release_store(&_method_currently_being_printed, method()); + BytecodeTracerData* data = JavaThread::current()->bytecode_tracer_data(); + BytecodePrinter printer(data); + printer.trace(method, fp, bcp, tos, tos2, st); } } #endif void BytecodeTracer::print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered) { - BytecodePrinter method_printer(flags); + // Debug builds can't re-use the data in the Java Thread as that is used for tracing + // the current bytecodes, rather than to print diagnostic information as is the + // case here. Always stack-allocate for this printing. + BytecodeTracerData data; BytecodeStream s(method); s.set_interval(from, to); @@ -207,6 +211,7 @@ void BytecodeTracer::print_method_codes(const methodHandle& method, int from, in stringStream ss; outputStream* out = buffered ? &ss : st; while (s.next() >= 0) { + BytecodePrinter method_printer(&data, flags); method_printer.trace(method, s.bcp(), out); } if (buffered) { @@ -345,7 +350,7 @@ void BytecodePrinter::print_bsm(int cp_index, outputStream* st) { void BytecodePrinter::print_attributes(int bci, outputStream* st) { // Show attributes of pre-rewritten codes - Bytecodes::Code code = Bytecodes::java_code(raw_code()); + Bytecodes::Code code = Bytecodes::java_code(_raw_code); // If the code doesn't have any fields there's nothing to print. // note this is ==1 because the tableswitch and lookupswitch are // zero size (for some reason) and we want to print stuff out for them. @@ -366,7 +371,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) { case Bytecodes::_ldc: { int cp_index; - if (Bytecodes::uses_cp_cache(raw_code())) { + if (Bytecodes::uses_cp_cache(_raw_code)) { assert(use_cp_cache(), "fast ldc bytecode must be in linked classes"); int obj_index = get_index_u1(); cp_index = constants()->object_to_cp_index(obj_index); @@ -381,7 +386,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) { case Bytecodes::_ldc2_w: { int cp_index; - if (Bytecodes::uses_cp_cache(raw_code())) { + if (Bytecodes::uses_cp_cache(_raw_code)) { assert(use_cp_cache(), "fast ldc bytecode must be in linked classes"); int obj_index = get_native_index_u2(); cp_index = constants()->object_to_cp_index(obj_index); @@ -533,7 +538,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) { cp_index = method_entry->constant_pool_index(); print_field_or_method(cp_index, st); - if (raw_code() == Bytecodes::_invokehandle && + if (_raw_code == Bytecodes::_invokehandle && ClassPrinter::has_mode(_flags, ClassPrinter::PRINT_METHOD_HANDLE)) { assert(use_cp_cache(), "invokehandle is only in rewritten methods"); method_entry->print_on(st); diff --git a/src/hotspot/share/interpreter/bytecodeTracer.hpp b/src/hotspot/share/interpreter/bytecodeTracer.hpp index ab66030b6cd..03340fbe5bf 100644 --- a/src/hotspot/share/interpreter/bytecodeTracer.hpp +++ b/src/hotspot/share/interpreter/bytecodeTracer.hpp @@ -25,21 +25,54 @@ #ifndef SHARE_INTERPRETER_BYTECODETRACER_HPP #define SHARE_INTERPRETER_BYTECODETRACER_HPP +#include "interpreter/bytecodes.hpp" #include "memory/allStatic.hpp" #include "utilities/globalDefinitions.hpp" -// The BytecodeTracer is a helper class used by the interpreter for run-time -// bytecode tracing. If TraceBytecodes turned on, trace_interpreter() will be called -// for each bytecode. - +class Method; class methodHandle; class outputStream; - class BytecodeClosure; + +// The BytecodeTracer is a helper class used by the interpreter for run-time +// bytecode tracing. If TraceBytecodes is turned on, trace_interpreter() will be called +// for each bytecode. class BytecodeTracer: AllStatic { public: - NOT_PRODUCT(static void trace_interpreter(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st);) + NOT_PRODUCT(static void trace_interpreter(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st);) static void print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered = true); }; +// Provides tracing-centric context whose lifespan exceeds the printing of +// a single bytecode. For instance, it is needed to determine method switches +// in order to print the appropriate signature once a switch happens. +class BytecodeTracerData { + private: + Method* _current_method; // for method switches + intptr_t* _current_fp; // for self-recursion + bool _is_wide; // to parse the next bytecode properly + + public: + BytecodeTracerData() : _current_method(nullptr), + _current_fp(nullptr), + _is_wide(false) {} + + // The current method may point to a stale/garbage Method. While pointer + // comparison is safe, it should only be dereferenced while guaranteed to + // be valid. For example, if the current method is set to the result of a + // methodHandle call, current_method() may be dereferenced while the handle + // is live. It is always up to the caller to ensure that current_method() + // is safe to dereference. + Method* current_method() const { return _current_method; } + void set_current_method(Method* current) { _current_method = current; } + + // The frame pointer should only ever be used for pointer comparison and may + // never be dereferenced. + intptr_t* current_fp() const { return _current_fp; } + void set_current_fp(intptr_t* current) { _current_fp = current; } + + bool is_wide() const { return _is_wide; } + void set_wide(bool wide) { _is_wide = wide; } +}; + #endif // SHARE_INTERPRETER_BYTECODETRACER_HPP diff --git a/src/hotspot/share/interpreter/bytecodes.hpp b/src/hotspot/share/interpreter/bytecodes.hpp index 629cca706ae..6f77e95f5bd 100644 --- a/src/hotspot/share/interpreter/bytecodes.hpp +++ b/src/hotspot/share/interpreter/bytecodes.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -418,6 +418,9 @@ class Bytecodes: AllStatic { static bool is_zero_const (Code code) { return (code == _aconst_null || code == _iconst_0 || code == _fconst_0 || code == _dconst_0); } static bool is_return (Code code) { return (_ireturn <= code && code <= _return); } + static bool is_terminal (Code code) { return is_return(code) || + code == _return_register_finalizer || + code == _athrow; } static bool is_invoke (Code code) { return (_invokevirtual <= code && code <= _invokedynamic); } static bool is_field_code (Code code) { return (_getstatic <= java_code(code) && java_code(code) <= _putfield); } static bool has_receiver (Code code) { assert(is_invoke(code), ""); return code == _invokevirtual || diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index cd0a062ebc8..dd183f36ea2 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -1517,7 +1517,9 @@ JRT_LEAF(intptr_t, InterpreterRuntime::trace_bytecode(JavaThread* current, intpt LastFrameAccessor last_frame(current); assert(last_frame.is_interpreted_frame(), "must be an interpreted frame"); methodHandle mh(current, last_frame.method()); - BytecodeTracer::trace_interpreter(mh, last_frame.bcp(), tos, tos2, tty); + stringStream st; + BytecodeTracer::trace_interpreter(mh, last_frame.get_frame().real_fp(), last_frame.bcp(), tos, tos2, &st); + tty->print("%s", st.freeze()); return preserve_this_value; JRT_END #endif // !PRODUCT diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index c96ec9a7c0d..5a29a668a54 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -37,6 +37,7 @@ #include "gc/shared/oopStorage.hpp" #include "gc/shared/oopStorageSet.hpp" #include "gc/shared/tlab_globals.hpp" +#include "interpreter/bytecodeTracer.hpp" #include "jfr/jfrEvents.hpp" #include "jvm.h" #include "jvmtifiles/jvmtiEnv.hpp" @@ -432,6 +433,8 @@ JavaThread::JavaThread(MemTag mem_tag) : _visited_for_critical_count(false), #endif + NOT_PRODUCT(_bytecode_tracer_data{} COMMA) + _terminated(_not_terminated), _in_deopt_handler(0), _doing_unsafe_access(false), diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 755a6abe0d8..bc6c0a3a4fd 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -26,6 +26,9 @@ #ifndef SHARE_RUNTIME_JAVATHREAD_HPP #define SHARE_RUNTIME_JAVATHREAD_HPP +#ifndef PRODUCT +#include "interpreter/bytecodeTracer.hpp" +#endif // PRODUCT #include "jni.h" #include "memory/allocation.hpp" #include "oops/oop.hpp" @@ -291,6 +294,16 @@ class JavaThread: public Thread { } #endif // ASSERT +#ifndef PRODUCT + private: + BytecodeTracerData _bytecode_tracer_data; + + public: + BytecodeTracerData* bytecode_tracer_data() { + return &_bytecode_tracer_data; + } +#endif // PRODUCT + // JavaThread termination support public: enum TerminatedTypes { diff --git a/test/hotspot/jtreg/runtime/interpreter/TraceBytecodesSignatures.java b/test/hotspot/jtreg/runtime/interpreter/TraceBytecodesSignatures.java new file mode 100644 index 00000000000..a7881582d8c --- /dev/null +++ b/test/hotspot/jtreg/runtime/interpreter/TraceBytecodesSignatures.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @bug 8370102 + * @requires vm.debug + * @library / /test/lib + * @summary Test to ensure the signatures in -XX:+TraceBytecodes are printed in edge cases + * @run driver TraceBytecodesSignatures + */ + +import java.io.IOException; +import java.util.List; + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TraceBytecodesSignatures { + + public static void main(String[] args) + throws InterruptedException, IOException { + if (args.length == 1 && "worker".equals(args[0])) { + runTest(); + return; + } + // Enable byteocde tracing but disable compilation of the key methods + // that we will trace. + String[] processArgs = new String[] { + "-XX:+TraceBytecodes", + exclude("runTest"), + exclude("testSameMethod"), + exclude("testSameMethodHelper"), + excludeConstructor(), + exclude("testSelfRecursive"), + exclude("testSelfRecursiveHelper"), + klass(), + "worker" + }; + // Create a VM process and trace its bytecodes. + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(processArgs); + OutputAnalyzer oa = new OutputAnalyzer(pb.start()) + .shouldHaveExitValue(0); + analyze(oa.stdoutAsLines()); + } + + private static void runTest() { + testSameMethod(true); + testSameMethod(false); + new TestConstructor(); + new TestConstructor(); + testSelfRecursive(3); + } + + // Expect 3 signature prints: + // 1. The first call + // 2. The first call switch back after the helper is called + // 3. The second call + private static final int EXPECTED_SAME_METHOD = 3; + + // Expect 2 signature prints: + // 1. The first call of the no-arg constructor + // 2. Switch back after calling the 1-arg constructor + // 3. The second call of the no-arg constructor + // 4. Switch back again + private static final int EXPECTED_CONSTRUCTOR = 4; + + // Expect 10 signature prints: + // 1. The first recursive call + // 2. The first switch back after the helper is called. + // 3. The second recursive call + // 4. The second switch back + // 5. The third recursive call + // 6. The third switch back + // 7. The fourth recursive call: base case hit + // 8-10. Switch back to i=1, i=2, i=3, respectively + private static final int EXPECTED_SELF_RECURSIVE_METHOD = 10; + + // Ensure that the expected signatures are printed the correct nr of times. + // The signatures here need to be changed if any of the test code is changed. + private static void analyze(List lines) { + System.out.println("Analyzing " + lines.size() + " lines"); + int testSameMethodCount = 0; + int testConstructorCount = 0; + int testSelfRecursiveCount = 0; + for (String line : lines) { + if (line.contains("static void TraceBytecodesSignatures.testSameMethod(jboolean)")) { + testSameMethodCount++; + } else if (line.contains("virtual void TraceBytecodesSignatures$TestConstructor.()")) { + testConstructorCount++; + } else if (line.contains("static void TraceBytecodesSignatures.testSelfRecursive(jint)")) { + testSelfRecursiveCount++; + } + } + if (testSameMethodCount != EXPECTED_SAME_METHOD) { + throw new RuntimeException("testSameMethod: " + + EXPECTED_SAME_METHOD + + " != " + + testSameMethodCount); + } + if (testConstructorCount != EXPECTED_CONSTRUCTOR) { + throw new RuntimeException("testConstructor: " + + EXPECTED_CONSTRUCTOR + + " != " + + testConstructorCount); + } + if (testSelfRecursiveCount != EXPECTED_SELF_RECURSIVE_METHOD) { + throw new RuntimeException("testSelfRecursive: " + + EXPECTED_SELF_RECURSIVE_METHOD + + " != " + + testSelfRecursiveCount); + } + } + + // Use a variable to do some arithmetic on to represent work. + // This work should not produce method invocations, hence integer arithmetic. + private static int globalState = 0; + + private static void testSameMethod(boolean other) { + globalState = 1; + if (other) { + testSameMethodHelper(); + } + globalState = 1; + } + + private static void testSameMethodHelper() { + globalState = 5; + } + + private static final class TestConstructor { + // Represents some kind of mutable state, not necessarily a object attribute. + private static String foo = "test"; + + public TestConstructor() { + this("bar"); + } + + public TestConstructor(String other) { + foo = other; + } + } + + private static void testSelfRecursive(int i) { + if (i == 0) { + return; + } + globalState += 2; + // Ensure to generate another method call within the recursive method. + // This will trigger the method switches more often. + testSelfRecursiveHelper(); + globalState -= 2; + testSelfRecursive(i - 1); + } + + private static void testSelfRecursiveHelper() { + globalState = -1; + } + + // CompileCommand is accepted even if the VM does not use JIT compilation. + private static String exclude(String methodName) { + return "-XX:CompileCommand=exclude," + klass() + "." + methodName; + } + + private static String excludeConstructor() { + return "-XX:CompileCommand=exclude," + klass() + "$TestConstructor."; + } + + private static String klass() { + return TraceBytecodesSignatures.class.getName(); + } +}