Some instructions need to create some instances, as follows:
instructions | Corresponding datalayout_ struct._ Tag value |
_checkcast,_instanceof,_aastore |
receiver_type_data_tag bit_data_tag |
_invokespecial,_invokestatic |
call_type_data_tag counter_data_tag |
_goto,_goto_w,_jsr,_jsr_w | jump_data_tag |
_invokevirtual,_invokeinterface |
virtual_call_type_data_tag virtual_call_data_tag |
_invokedynamic |
call_type_data_tag counter_data_tag |
_ret | ret_data_tag |
_ifeq,_ifne,_iflt,_ifge,_ifgt,_ifle, _if_icmpeq,_if_icmpne,_if_icmplt,_if_icmpge, _if_icmpgt,_if_icmple,_if_acmpeq,_if_acmpne, _ifnull,_ifnonnull |
branch_data_tag |
_lookupswitch,_tableswitch | multi_branch_data_tag |
Datalayout in the table above_ struct._ The value of tag attribute may have multiple options, so one will be selected according to the actual situation.
As mentioned earlier, the execution of bytecode can be recorded in detail through MethodData. For example, in the back edge count, add jumpdata:: token to the MethodData_ off_ Store the number of jumps at set through jumpdata:: display_ off_ Set to update the interpreter in the stack frame_ frame_ mdx_ For the value at offset, how to store these data in MethodData, what does these data mean, and what is the purpose of storing these data? To understand this, we need to first introduce ProfileData and DataLayout.
Declare the fields related to statistics in MethodData as follows:
class MethodData : public Metadata { // This value is used to find the corresponding data pointer through the bytecode subscript index faster (call the bci_to_dp() function) // Or find the corresponding ProfileData through bytecode subscript index (call bci_to_data() function) int _hint_di; // Counts back edges and method calls InvocationCounter _invocation_counter; InvocationCounter _backedge_counter; // Initial value at start of count int _invocation_counter_start; int _backedge_counter_start; int _data_size; // The data index of the formal parameter. If not, the value is - 1 int _parameters_type_data_di; // Starting address of data intptr_t _data[1]; // ... }
Among them_ The value of the data attribute is important. Its relationship with ProfileData and DataLayout classes is as follows:
As can be seen from the above figure, in addition to the memory occupied by MethodData itself, a lot of additional memory will be allocated. These memory will be used to store some statistics related information, and these data areas can be regarded as the element type is intptr_t, and MethodData::_data is the first address of this array. Because some data will be stored in the array, the length of these data is different. For example, some may need to occupy two consecutive slot s in the array, some may be three, but the first address of the data can still be marked by the subscript of the array.
The layout of stored data is determined by DataLayout. The data laid out by DataLayout corresponds to ProfileData, so the data can be operated through ProfileData.
The defined ProfileData class is defined as follows:
class ProfileData : public ResourceObj { DataLayout* _data; // Specifies the storage format of data // ... }
The above class has many subclasses, as shown in the following figure.
The layout and data size of these specific subclasses are different, but the different instance sizes and data layout of the same subclass are the same. If we can know the first address of a ProfileData, we can accurately read the data of the instance of the issuing subclass.
The DataLayout class is defined as follows:
class DataLayout VALUE_OBJ_CLASS_SPEC { private: union { // intptr_t type takes up 4 bytes intptr_t _bits; struct { u1 _tag; // The format of flags is [recompile:1 | reason:3 | flags:4] u1 _flags; u2 _bci; } _struct; } _header; // There can be many cells, and the address of the first cell is through the following_ Save the cells attribute, // The size of the specific cells array should also be determined according to the specific ProfileData intptr_t _cells[1]; // ... }
The above class defines the data layout of each ProfileData instance. The first is_ tag attribute, which determines ProfileData, that is, which specific subclass of ProfileData this part of data corresponds to, and different subclasses have_ The size of cells array is different_ bci represents the bytecode index, corresponding to the current_ Cells stores some statistical information about the bytecode corresponding to the bytecode index.
_ The value of tag attribute is defined by enumeration class, as follows:
enum { no_tag, bit_data_tag, counter_data_tag, jump_data_tag, receiver_type_data_tag, virtual_call_data_tag, ret_data_tag, branch_data_tag, multi_branch_data_tag, arg_info_data_tag, call_type_data_tag, virtual_call_type_data_tag, parameters_type_data_tag };
As mentioned above, the constant value of the enumeration class has a certain correspondence with the specific subclass of ProfileData_ The in () function can see this correspondence. The function is implemented as follows:
ProfileData* DataLayout::data_in() { switch (tag()) { case DataLayout::no_tag: default: ShouldNotReachHere(); return NULL; case DataLayout::bit_data_tag: return new BitData(this); case DataLayout::counter_data_tag: return new CounterData(this); case DataLayout::jump_data_tag: return new JumpData(this); case DataLayout::receiver_type_data_tag: return new ReceiverTypeData(this); case DataLayout::virtual_call_data_tag: return new VirtualCallData(this); case DataLayout::ret_data_tag: return new RetData(this); case DataLayout::branch_data_tag: return new BranchData(this); case DataLayout::multi_branch_data_tag: return new MultiBranchData(this); case DataLayout::arg_info_data_tag: return new ArgInfoData(this); case DataLayout::call_type_data_tag: return new CallTypeData(this); case DataLayout::virtual_call_type_data_tag: return new VirtualCallTypeData(this); case DataLayout::parameters_type_data_tag: return new ParametersTypeData(this); }; }
If you want to from methoddata::_ To get a specific ProfileData instance from data, you can call MethodData::data_at() function. The implementation of this function is as follows:
ProfileData* MethodData::data_at(int data_index) const { DataLayout* data_layout = data_layout_at(data_index); return data_layout->data_in(); } DataLayout* data_layout_at(int data_index) const { // Because_ The element type pointed to by data is intptr_t. It occupies 8 bytes, so it will move 8 bytes each time after adding an integer return (DataLayout*) ( ((address)_data) + data_index ); }
Calling methoddata:: Data_ Data passed when the at() function_ The value of the index attribute is the previously mentioned data area, which can be regarded as the element type intptr_t, so the first address of a specific data can be indicated by the array subscript index.
Call data_ layout_ After at() obtains the DataLayout pointer, you can directly_ The tag attribute is converted to a ProfileData instance so that it can be operated through the ProfileData instance_ The data stored in cells.
As we mentioned earlier, in addition to the memory occupied by MethodData itself, a lot of memory will be allocated. We temporarily call it data area. This area may need to store up to four types of data. We can see from the calculation method of opening up memory space for MethodData and the initialization process of constructor function. The following describes the initialization process of opening up storage space for MethodData and calling MethodData constructor.
1. Open up storage space for MethodData
When allocating MethodData instances, you need to call the following functions to calculate the size of the allocated memory space:
int MethodData::compute_allocation_size_in_words(methodHandle method) { int byte_size = compute_allocation_size_in_bytes(method); int word_size = align_size_up(byte_size, BytesPerWord) / BytesPerWord; return align_object_size(word_size); // Memory footprint in words } int MethodData::compute_allocation_size_in_bytes(methodHandle method) { // Part 1 int data_size = 0; BytecodeStream stream(method); Bytecodes::Code c; int empty_bc_count = 0; // Count which bytecodes in the method do not need statistical information, that is, there will be no corresponding ProfileData instance while ((c = stream.next()) >= 0) { int size_in_bytes = compute_data_size(&stream); data_size += size_in_bytes; if (size_in_bytes == 0) empty_bc_count += 1; } int object_size = in_bytes(data_offset()) + data_size; // Part 2 // Add some extra DataLayout cells (at least one) to track stray traps. int extra_data_count = compute_extra_data_count(data_size, empty_bc_count); object_size += extra_data_count * DataLayout::compute_size_in_bytes(0); // Part 3 // Add a cell to record information about modified arguments. int arg_size = method->size_of_parameters(); object_size += DataLayout::compute_size_in_bytes(arg_size+1); // Part 4 // Reserve room for an area of the MDO dedicated to profiling of parameters int args_cell = ParametersTypeData::compute_cell_count(method()); if (args_cell > 0) { object_size += DataLayout::compute_size_in_bytes(args_cell); } return object_size; }
When creating a MethodData instance, in addition to the memory space occupied by itself, another four parts of space should be opened up, as shown in the following figure.
The implementation of these parts is described in detail below.
Part 1
Call compute on all bytecodes in the Java method_ data_ The size() function calculates the size as follows:
// The amount of memory used to calculate the required statistics for the current bytecode int MethodData::compute_data_size(BytecodeStream* stream) { // Some bytecodes require corresponding ProfileData, which returns the memory size occupied by all required ProfileData int cell_count = bytecode_cell_count(stream->code()); if (cell_count == no_profile_data) { return 0; } // When it is invokestatic, invokeinterface, invokedynamic, tableswitch, and lookupswitch // Call bytecode_ cell_ The count() function may return variable_cell_count, because these bytecode instructions // The size of the required ProfileData is variable, and the following logic is required to continue the calculation if (cell_count == variable_cell_count) { switch (stream->code()) { case Bytecodes::_lookupswitch: case Bytecodes::_tableswitch: cell_count = MultiBranchData::compute_cell_count(stream); break; case Bytecodes::_invokespecial: case Bytecodes::_invokestatic: case Bytecodes::_invokedynamic: if (profile_arguments_for_invoke(stream->method(), stream->bci()) || profile_return_for_invoke(stream->method(), stream->bci())) { cell_count = CallTypeData::compute_cell_count(stream); } else { cell_count = CounterData::static_cell_count(); } break; // Methods dynamically dispatch instructions invokevirtual and invokeinterface case Bytecodes::_invokevirtual: case Bytecodes::_invokeinterface: { if (profile_arguments_for_invoke(stream->method(), stream->bci()) || profile_return_for_invoke(stream->method(), stream->bci())) { cell_count = VirtualCallTypeData::compute_cell_count(stream); } else { cell_count = VirtualCallData::static_cell_count(); } break; } default: fatal("unexpected bytecode for var length profile data"); } } return DataLayout::compute_size_in_bytes(cell_count); }
Bytecode called_ cell_ The implementation of count() function is as follows:
int MethodData::bytecode_cell_count(Bytecodes::Code code) { switch (code) { case Bytecodes::_checkcast: case Bytecodes::_instanceof: case Bytecodes::_aastore: if (TypeProfileCasts) { // Default branch return ReceiverTypeData::static_cell_count(); } else { return BitData::static_cell_count(); } case Bytecodes::_invokespecial: case Bytecodes::_invokestatic: if (MethodData::profile_arguments() || MethodData::profile_return()) { return variable_cell_count; } else { return CounterData::static_cell_count(); } case Bytecodes::_goto: case Bytecodes::_goto_w: case Bytecodes::_jsr: case Bytecodes::_jsr_w: return JumpData::static_cell_count(); case Bytecodes::_invokevirtual: case Bytecodes::_invokeinterface: if (MethodData::profile_arguments() || MethodData::profile_return()) { return variable_cell_count; } else { // Default branch return VirtualCallData::static_cell_count(); } case Bytecodes::_invokedynamic: if (MethodData::profile_arguments() || MethodData::profile_return()) { return variable_cell_count; } else { // Default branch return CounterData::static_cell_count(); } case Bytecodes::_ret: return RetData::static_cell_count(); case Bytecodes::_ifeq: case Bytecodes::_ifne: case Bytecodes::_iflt: case Bytecodes::_ifge: case Bytecodes::_ifgt: case Bytecodes::_ifle: case Bytecodes::_if_icmpeq: case Bytecodes::_if_icmpne: case Bytecodes::_if_icmplt: case Bytecodes::_if_icmpge: case Bytecodes::_if_icmpgt: case Bytecodes::_if_icmple: case Bytecodes::_if_acmpeq: case Bytecodes::_if_acmpne: case Bytecodes::_ifnull: case Bytecodes::_ifnonnull: return BranchData::static_cell_count(); case Bytecodes::_lookupswitch: case Bytecodes::_tableswitch: return variable_cell_count; } return no_profile_data; }
For branch instructions, it is possible to open up space, and then open up space to store statistics. Bytecode is called when invokestatic, invokeinterface, invokedynamic, tableswitch, and lookupswitch are called_ cell_ The count() function may return variable_cell_count, because the size of ProfileData required by these bytecode instructions is variable, it needs to be in methoddata:: compute_ data_ Continue the calculation in the size() function.
For method calls, it is sometimes necessary to count the parameters or return values of the method. Methoddata:: Profile_ The arguments() function is implemented as follows:
bool MethodData::profile_arguments() { // The function returns false by default return profile_arguments_flag() > no_type_profile && profile_arguments_flag() <= type_profile_all; } int MethodData::profile_arguments_flag() { return TypeProfileLevel % 10; // The default value of TypeProfileLevel is 0 } int MethodData::profile_return_flag() { return (TypeProfileLevel % 100) / 10; // The value of TypeProfileLevel is 0 } bool MethodData::profile_return() { // The function returns false by default return profile_return_flag() > no_type_profile && profile_return_flag() <= type_profile_all; }
The value of typeprofilelevel can be specified as XYZ, where X, Y and Z mean as follows:
Z: Statistics of argument types at call time;
Y: Statistics of return value types during call;
10: Type statistics of formal parameters.
There are three values on each bit, which can be defined by enumerating class constants, as follows:
enum { no_type_profile = 0, type_profile_jsr292 = 1, type_profile_all = 2 };
Look back at methoddata:: compute_ data_ In the size() function, calculate the memory size required for invokestatic, invokeinterface, invokedynamic, tableswitch and lookupswitch, such as the size of invokespecial, as follows:
static int compute_cell_count(BytecodeStream* stream) { // Call static_ cell_ The value of count() is 1 return CounterData::static_cell_count() + TypeEntriesAtCall::compute_cell_count(stream); } int TypeEntriesAtCall::compute_cell_count(BytecodeStream* stream) { Bytecode_invoke inv(stream->method(), stream->bci()); int args_cell = 0; // Methoddata:: profile will also be called_ The arguments() function will return false by default if (arguments_profiling_enabled()) { args_cell = TypeStackSlotEntries::compute_cell_count(inv.signature(), false, TypeProfileArgsLimit); } int ret_cell = 0; // When calling methoddata:: Profile_ The return() function returns true. If statistics are required when the return type is object type, the body of if will execute. However, it will not be executed by default if (return_profiling_enabled() && (inv.result_type() == T_OBJECT || inv.result_type() == T_ARRAY)) { ret_cell = ReturnTypeEntry::static_cell_count(); } // If you want to record statistics, you must have a header int header_cell = 0; if (args_cell + ret_cell > 0) { header_cell = header_cell_count(); } return header_cell + args_cell + ret_cell; } int TypeStackSlotEntries::compute_cell_count( Symbol* signature, bool include_receiver, int max ) { // When it is an instance method, you must also consider the this parameter int args_count = include_receiver ? 1 : 0; ResourceMark rm; SignatureStream ss(signature); // Counts the number of object types in the parameter args_count += ss.reference_parameter_count(); args_count = MIN2(args_count, max); return args_count * per_arg_cell_count; }
MethodData::compute_ data_ DataLayout: (compute_) called in size () function size_ in_ The bytes() function is implemented as follows:
static int compute_size_in_bytes(int cell_count) { // 8 + cell_count * 8 return header_size_in_bytes() + cell_count * cell_size; }
Calculate the required byte size. Cell required for each bytecode_ Count is different, so the size of DataLayout is different, and the size of the corresponding ProfileData instance is different.
Part 2
The implementation code of Part 2 is as follows:
int MethodData::compute_extra_data_count(int data_size, int empty_bc_count) { if (ProfileTraps) { // Assume that up to 3% of BCIs with no MDP will need to allocate one. More than 3% of BCIs that do not require MDP will allocate data int extra_data_count = (uint)(empty_bc_count * 3) / 128 + 1; // If the method is large, let the extra BCIs grow numerous (to ~1%). int one_percent_of_data = (uint)data_size / (DataLayout::header_size_in_bytes()*128); if (extra_data_count < one_percent_of_data){ extra_data_count = one_percent_of_data; } if (extra_data_count > empty_bc_count){ extra_data_count = empty_bc_count; // no need for more } return extra_data_count; } else { return 0; } }
This part is related to inverse optimization. The compiler can choose some optimization methods that can improve the running speed most of the time according to the probability. When the assumption of radical optimization is not tenable, for example, when the type inheritance structure changes and "Uncommon Trap" occurs after loading a new class, it can return to the interpretation state and continue to execute through inverse optimization. The above data is used to track these traps. I can't see the application yet. I'll explain it in detail later.
Part 4
Part 3 is very simple and will not be introduced. Part 4 is introduced directly. The code implementation of Part 4 is as follows:
int ParametersTypeData::compute_cell_count(Method* m) { if (!MethodData::profile_parameters_for_method(m)) { return 0; } int max = TypeProfileParmsLimit == -1 ? INT_MAX : TypeProfileParmsLimit; // The default value of TypeProfileParmsLimit is 2 int obj_args = TypeStackSlotEntries::compute_cell_count(m->signature(), !m->is_static(), max); if (obj_args > 0) { return obj_args + 1; // 1 cell for array len } return 0; }
Typestackslotentries:: compute called_ cell_ The count () function calculates the object type in the method parameter. By default, however, this function returns 0.
2. Call MethodData constructor
After opening up the MethodData memory space above, you need to call the MethodData constructor. The implementation of the constructor is as follows:
MethodData::MethodData(methodHandle method, int size, TRAPS) { // ... init(); // Part 1 // Iterate bytecode instructions and allocate and initialize the corresponding data cell int data_size = 0; int empty_bc_count = 0; // The number of bytecodes of data cell is not required _data[0] = 0; BytecodeStream stream(method); Bytecodes::Code c; while ((c = stream.next()) >= 0) { int size_in_bytes = initialize_data(&stream, data_size); data_size += size_in_bytes; if (size_in_bytes == 0) empty_bc_count += 1; } _data_size = data_size; int object_size = in_bytes(data_offset()) + data_size; // Skip Part 2 int extra_data_count = compute_extra_data_count(data_size, empty_bc_count); int extra_size = extra_data_count * DataLayout::compute_size_in_bytes(0); DataLayout *dp = data_layout_at(data_size + extra_size); // Part 3 int arg_size = method->size_of_parameters(); // The function called is declared as follows: // DataLayout::initialize(u1 tag, u2 bci, int cell_count) dp->initialize(DataLayout::arg_info_data_tag, 0, arg_size+1); int arg_data_size = DataLayout::compute_size_in_bytes(arg_size+1); object_size += extra_size + arg_data_size; // Part 4, when the method has no formal parameters_ parameter_ type_ data_ The value of Di is - 1, otherwise it is data index int args_cell = ParametersTypeData::compute_cell_count(method()); if (args_cell > 0) { object_size += DataLayout::compute_size_in_bytes(args_cell); _parameters_type_data_di = data_size + extra_size + arg_data_size; DataLayout *dp = data_layout_at(data_size + extra_size + arg_data_size); dp->initialize(DataLayout::parameters_type_data_tag, 0, args_cell); } else { _parameters_type_data_di = -1; } // This value helps us quickly find the corresponding data cell through bytecode index _hint_di = first_di(); post_initialize(&stream); set_size(object_size); }
As mentioned earlier, when creating a MethodData instance, you must first open up the relevant space, and the code of the above function just opens up the space before initialization.
Part 1
Call the following function to initialize the data cell corresponding to the bytecode. The implementation of the function is as follows:
// Initialize an individual data segment. Returns the size of // the segment in bytes. int MethodData::initialize_data( BytecodeStream* stream, int data_index ) { int cell_count = -1; int tag = DataLayout::no_tag; DataLayout* data_layout = data_layout_at(data_index); Bytecodes::Code c = stream->code(); switch (c) { case Bytecodes::_checkcast: case Bytecodes::_instanceof: case Bytecodes::_aastore: if (TypeProfileCasts) { // The default value of TypeProfileCasts is true cell_count = ReceiverTypeData::static_cell_count(); // The value is 5 tag = DataLayout::receiver_type_data_tag; } else { cell_count = BitData::static_cell_count(); // The value is 0 tag = DataLayout::bit_data_tag; } break; case Bytecodes::_invokespecial: case Bytecodes::_invokestatic: { int counter_data_cell_count = CounterData::static_cell_count(); // The value is 1 if ( profile_arguments_for_invoke(stream->method(), stream->bci()) || profile_return_for_invoke(stream->method(), stream->bci()) ) { cell_count = CallTypeData::compute_cell_count(stream); } else { cell_count = counter_data_cell_count; } if (cell_count > counter_data_cell_count) { tag = DataLayout::call_type_data_tag; } else { tag = DataLayout::counter_data_tag; } break; } case Bytecodes::_goto: case Bytecodes::_goto_w: case Bytecodes::_jsr: case Bytecodes::_jsr_w: cell_count = JumpData::static_cell_count(); tag = DataLayout::jump_data_tag; break; case Bytecodes::_invokevirtual: case Bytecodes::_invokeinterface: { int virtual_call_data_cell_count = VirtualCallData::static_cell_count(); if ( profile_arguments_for_invoke(stream->method(), stream->bci()) || profile_return_for_invoke(stream->method(), stream->bci()) ) { cell_count = VirtualCallTypeData::compute_cell_count(stream); } else { cell_count = virtual_call_data_cell_count; } if (cell_count > virtual_call_data_cell_count) { tag = DataLayout::virtual_call_type_data_tag; } else { tag = DataLayout::virtual_call_data_tag; } break; } case Bytecodes::_invokedynamic: { // %%% should make a type profile for any invokedynamic that takes a ref argument int counter_data_cell_count = CounterData::static_cell_count(); if ( profile_arguments_for_invoke(stream->method(), stream->bci()) || profile_return_for_invoke(stream->method(), stream->bci()) ) { cell_count = CallTypeData::compute_cell_count(stream); } else { cell_count = counter_data_cell_count; } if (cell_count > counter_data_cell_count) { tag = DataLayout::call_type_data_tag; } else { tag = DataLayout::counter_data_tag; } break; } case Bytecodes::_ret: cell_count = RetData::static_cell_count(); // The value is 7 tag = DataLayout::ret_data_tag; break; case Bytecodes::_ifeq: case Bytecodes::_ifne: case Bytecodes::_iflt: case Bytecodes::_ifge: case Bytecodes::_ifgt: case Bytecodes::_ifle: case Bytecodes::_if_icmpeq: case Bytecodes::_if_icmpne: case Bytecodes::_if_icmplt: case Bytecodes::_if_icmpge: case Bytecodes::_if_icmpgt: case Bytecodes::_if_icmple: case Bytecodes::_if_acmpeq: case Bytecodes::_if_acmpne: case Bytecodes::_ifnull: case Bytecodes::_ifnonnull: cell_count = BranchData::static_cell_count(); // The value is 3 tag = DataLayout::branch_data_tag; break; case Bytecodes::_lookupswitch: case Bytecodes::_tableswitch: cell_count = MultiBranchData::compute_cell_count(stream); tag = DataLayout::multi_branch_data_tag; break; } if (cell_count >= 0) { data_layout->initialize(tag, stream->bci(), cell_count); // The calculation method of the following function is: 4 + cell_count * 4 return DataLayout::compute_size_in_bytes(cell_count); } else { assert(!bytecode_has_profile(c), "agree w/ !BHP"); return 0; } }
The function initializes the corresponding data cell and returns the size of the data cell.
Parts 3 and 4
The DataLayout::initialize() function is implemented as follows:
// Initialize general attributes. For the initialization of some specific attributes, call // ProfileData::post_ The initialize() function is complete void DataLayout::initialize(u1 tag, u2 bci, int cell_count) { _header._bits = (intptr_t)0; _header._struct._tag = tag; _header._struct._bci = bci; for (int i = 0; i < cell_count; i++) { set_cell_at(i, (intptr_t)0); } if (needs_array_len(tag)) { set_cell_at(ArrayData::array_len_off_set, cell_count - 1); // -1 for header. } if (tag == call_type_data_tag) { CallTypeData::initialize(this, cell_count); } else if (tag == virtual_call_type_data_tag) { VirtualCallTypeData::initialize(this, cell_count); } } bool DataLayout::needs_array_len(u1 tag) { return (tag == multi_branch_data_tag) || (tag == arg_info_data_tag) || (tag == parameters_type_data_tag); } void set_cell_at(int index, intptr_t value) { _cells[index] = value; }
You can see the concrete implementation of assigning values to various attributes in DataLayout. But it's only right here_ Some slots in cells are assigned values. What values are stored in these slots depends on the specific type of ProfileData stored. We will give a concrete example in the next article.
You can study the implementation of Template::branch() according to in-depth analysis of HotSpot.
Even if the interpreter detects a hot method through the back edge of the loop body and compiles the method, the main method is executed only once, and the compiled main method has no chance to execute at all. In order to solve this problem, a mechanism is needed: when running the main method (rather than after running the main() method), the compiled method is used to replace the current execution. This mechanism is called OSR. OSR is used to transform the method from low level to high level. The OSR occurs when the edge returning bytecode is encountered, and the edge returning is reflected in the branch.
The official account is analyzed in depth. Java virtual machine HotSpot has updated the VM source code to analyze the related articles to 60+. Welcome to the attention. If there are any problems, add WeChat mazhimazh, pull you into the virtual cluster communication.