第56篇-ProfileData与DataLayout

发布于 1970年 01月 01日 08:00

某些指令需要创建某些实例,如下:

指令 对应的DataLayout._struct._tag值

_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._struct._tag属性的值可能有多个可选,这样最终会根据实际情况选择一个。

 

之前介绍过,通过MethodData能够详细记录字节码的执行情况。如在回边计数中,向MethodData中的JumpData::taken_off_set处存储跳转次数,通过JumpData::displacement_off_set处的值来更新栈帧中的interpreter_frame_mdx_offset处的值,那么MethodData中是如何存储这些数据的,这些数据是什么意思,存储这些数据有什么作用呢?要理解这些,我们需要首先介绍下ProfileData和DataLayout。

在MethodData中声明与统计相关的字段如下:

class MethodData : public Metadata {
 
  // 这个值是为了更快的通过字节码下标索引找到对应data的指针(调用bci_to_dp()函数)
  // 或通过字节码下标索引找到对应的ProfileData(调用bci_to_data()函数)
  int     _hint_di;
 
  // 对回边和方法调用进行计数
  InvocationCounter _invocation_counter;
  InvocationCounter _backedge_counter;
 
  // 开始计数时的初始值
  int               _invocation_counter_start;
  int               _backedge_counter_start;
  
 
  int       _data_size;
 
 
  // 形参的data索引,如果没有,此值为-1
  int       _parameters_type_data_di;
 
  // 数据的起始地址
  intptr_t  _data[1];
   
  // ...
}

其中的_data属性的值很重要。其与ProfileData与DataLayout类的关系如下: 

通过上图能够看到,除去MethodData本身占用的内存外,还会额外分配不少内存,这些内存都会用来存储一些统计相关信息,而这些数据区域可被看成元素类型为intptr_t的数组,而MethodData::_data是这个数组的首地址。由于数组中会存储一些数据,这些数据的长度不一,例如有的可能需要占用数组中连续2个slot,有的可能是3个,但是仍然可以通过数组的下标标示出数据的首地址。

存储的数据的布局由DataLayout决定,按DataLayout布局的数据都对应着ProfileData,所以可以通过ProfileData操作数据。

定义的ProfileData类的定义如下:

class ProfileData : public ResourceObj {
  DataLayout* _data; // 规定了数据的存储格式
  // ...
}

如上类有非常多的子类,如下图所示。

这些具体的子类的布局和数据大小都不一样,但是同一个子类的不同实例大小和数据布局是一样的,如果我们能知道某个ProfileData的首地址,那么就能准确无误的读取出具体子类实例的数据。

DataLayout类的定义如下:

class DataLayout VALUE_OBJ_CLASS_SPEC {
private:
  union {
     // intptr_t类型占用4个字节
     intptr_t _bits;
     struct {
      u1 _tag;
          // flags的格式为[ recompile:1 | reason:3 | flags:4]
      u1 _flags;
      u2 _bci;
     } _struct;
  } _header;
 
 
  // 可以有许多个cells,首个cell的地址通过如下的_cells属性保存,
  // 具体的cells数组的大小还要根据具体的ProfileData来决定
  intptr_t _cells[1];
 
  // ...
}

如上类定义了每个ProfileData实例的数据布局。首先就是_tag属性,这个属性决定着ProfileData,也就是这一部分数据对应着哪个具体的ProfileData的子类,不同的子类其_cells数组的大小不同;_bci表示字节码索引,对应的当前的_cells中存储的就是关于这个字节码索引对应的字节码的一些统计相关信息。

_tag属性的值由枚举类定义,如下:

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
};

如上枚举类的常量值和ProfileData的具体子类有某种对应关系,通过data_in()函数就能看到这种对应关系。函数的实现如下:

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);
  };
}

如果要从MethodData::_data中获取某个具体的ProfileData实例,可以调用MethodData::data_at()函数,此函数的实现如下: 

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 {
    // 由于_data所指向的元素类型为intptr_t,占用8字节,所以加整数后每次会移动8字节
    return (DataLayout*) (   ((address)_data) + data_index  );
}

在调用MethodData::data_at()函数时,传递的data_index属性的值就是前面提到的数据区域,这些数据区域可被看成元素类型为intptr_t的数组,所以可以通过数组下标索引来指示出某个具体数据的首地址。

调用data_layout_at()获取到DataLayout指针后,可以直接根据布局中的_tag属性转换为ProfileData实例,这样就可以通过ProfileData实例操作_cells中存储的数据了。

我们之前介绍过,除了MethodData本身占用的内存之外,还会分配出不少内存,我们暂时叫数据区域,这一块区域可能需要存储最多4种类型的数据,我们从为MethodData开辟内存空间的计算方式和在构造函数的初始化过程中就能看出来。下面分别介绍一下为MethoData开辟存储空间和调用MethodData构造函数中的初始化过程。

1、为MethodData开辟存储空间

在分配MethodData实例时,需要调用如下函数计算分配的内存空间的大小:

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); // 以字为单位的内存占用大小
}
 
int MethodData::compute_allocation_size_in_bytes(methodHandle method) {
  // 第1部分
  int data_size = 0;
  BytecodeStream   stream(method);
  Bytecodes::Code  c;
  int empty_bc_count = 0;  // 统计出方法中的哪些字节码不需要统计信息,也就是不会有对应的ProfileData实例
  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;
 
  // 第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);
 
  // 第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);
 
  // 第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;
}

在创建MethodData实例时,除了要开辟自身占用的内存空间外,还要额外开辟另外的4部分空间,如下图所示。

 

下面详细介绍一下这几部分实现。

第1部分  

对Java方法中的所有字节码调用compute_data_size()函数计算大小,如下:

// 为当前的字节码计算需要的统计信息占用的内存大小
int MethodData::compute_data_size(BytecodeStream* stream) {
  // 有些字节码需要对应的ProfileData,返回所有需要的ProfileData需要占用的内存大小
  int cell_count = bytecode_cell_count(stream->code());
 
  if (cell_count == no_profile_data) {
    return 0;
  }
 
  // 当为invokestatic、invokeinterface、invokedynamic、tableswitch和lookupswitch时
  // 调用bytecode_cell_count()函数可能会返回variable_cell_count,因为这几个字节码指令
  // 需要的ProfileData的大小是可变的,需要下面的逻辑继续进行计算
  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;
    // 方法动态分派指令invokevirtual和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_cell_count()函数的实现如下:

int MethodData::bytecode_cell_count(Bytecodes::Code code) {
  switch (code) {
  case Bytecodes::_checkcast:
  case Bytecodes::_instanceof:
  case Bytecodes::_aastore:
    if (TypeProfileCasts) { // 默认会走的分支
      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 { // 默认会走的分支
      return VirtualCallData::static_cell_count();
    }
  case Bytecodes::_invokedynamic:
    if (MethodData::profile_arguments() || MethodData::profile_return()) {
      return variable_cell_count;
    } else { // 默认会走的分支
      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;
}

对于分支的指令才可能开辟空间,然后开辟出空间来存储统计信息。当为invokestatic、invokeinterface、invokedynamic、tableswitch和lookupswitch时,调用bytecode_cell_count()函数可能会返回variable_cell_count,因为这几个字节码指令需要的ProfileData的大小是可变的,需要在MethodData::compute_data_size()函数中继续进行计算。

对于方法调用来说,有时候还需要统计方法的参数或返回值,MethodData::profile_arguments()函数的实现如下:

bool MethodData::profile_arguments() { // 函数默认返回false
  return profile_arguments_flag() > no_type_profile   &&
          profile_arguments_flag() <= type_profile_all;
}
 
int MethodData::profile_arguments_flag() {
  return TypeProfileLevel % 10; // TypeProfileLevel默认的值为0
}
 
 
int MethodData::profile_return_flag() {
  return (TypeProfileLevel % 100) / 10; // TypeProfileLevel的值为0
}
 
bool MethodData::profile_return() { // 函数默认返回false
  return profile_return_flag() > no_type_profile   &&
          profile_return_flag() <= type_profile_all;
}

其中的TypeProfilieLevel的值可指定为XYZ,其中的X、Y和Z的意思如下:

Z:在调用时的实参类型统计;

Y:在调用时的返回值类型统计;

X:形参的类型统计。

在每一位上都有3个值可以取,通过枚举类常量定义,如下:

enum {
    no_type_profile = 0,
    type_profile_jsr292 = 1,
    type_profile_all = 2
};

回看MethodData::compute_data_size()函数中计算invokestatic、invokeinterface、invokedynamic、tableswitch和lookupswitch需要的内存大小,如计算invokespecial的大小,如下:

static int compute_cell_count(BytecodeStream* stream) {
   // 调用static_cell_count()的值为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_arguments()函数进行判断,默认会返回false
  if (arguments_profiling_enabled()) {
    args_cell = TypeStackSlotEntries::compute_cell_count(inv.signature(), false, TypeProfileArgsLimit);
  }
 
  int ret_cell = 0;
  // 当调用MethodData::profile_return()函数返回true,并且返回类型为对象类型时需要统计信息时,if的body体会执行。不过默认条件下不会执行
  if (return_profiling_enabled() && (inv.result_type() == T_OBJECT || inv.result_type() == T_ARRAY)) {
    ret_cell = ReturnTypeEntry::static_cell_count();
  }
 
  // 如果要记录统计信息,必须要有头
  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
) {
  // 当为实例方法时,还必须要考虑this参数
  int args_count = include_receiver ? 1 : 0;
  ResourceMark rm;
  SignatureStream ss(signature);
  // 统计参数中对象类型的数量
  args_count += ss.reference_parameter_count();
  args_count = MIN2(args_count, max);
  return args_count * per_arg_cell_count;
}

MethodData::compute_data_size()函数中调用的DataLayout::compute_size_in_bytes()函数的实现如下:

static int compute_size_in_bytes(int cell_count) {
    // 8 + cell_count * 8
    return header_size_in_bytes() + cell_count * cell_size;
}

计算出需要的字节大小。每个字节码需要的cell_count不同,这样DataLayout的大小就不同,最终对应的ProfileData实例的大小就不同。  

第2部分

第2部分的实现代码如下:  

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. 多于3%的、不需要MDP的BCIs将会分配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;
  }
}

这一部分和逆优化相关。编译器可以根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立,如加载了新类后类型继承结构出现变化、出现 “罕见陷阱”(Uncommon Trap)时可以通过逆优化(Deoptimization)退回到解释状态继续执行。如上的数据就是用来跟踪这些陷阱的。 现在还看不到应用,后面再详细介绍。

第4部分

第3部分非常简单,不介绍。直接介绍第4部分,第4部分的代码实现如下:  

int ParametersTypeData::compute_cell_count(Method* m) {
  if (!MethodData::profile_parameters_for_method(m)) {
    return 0;
  }
  int max = TypeProfileParmsLimit == -1 ? INT_MAX : TypeProfileParmsLimit; // TypeProfileParmsLimit的默认值为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_cell_count()函数计算出方法参数中的对象类型。不过默认情况下,这个函数返回的是0。  

2、调用MethodData构造函数

通过上面开辟完MethodData内存空间后就需要调用MethodData构造函数了,构造函数的实现如下:

MethodData::MethodData(methodHandle method, int size, TRAPS) {
  // ...
 
  init();
   
 
  // 第1部分
  // 迭代字节码指令并分配和初始化对应的data cell
  int data_size = 0;
  int empty_bc_count = 0;  // 不需要data cell的字节码数量
  _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;
 
  // 跳过第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);
 
  // 第3部分
  int arg_size = method->size_of_parameters();
  // 调用的函数的声明如下:
  //  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;
 
  // 第4部分,当方法没有形参时,_parameter_type_data_di的值为-1,否则为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;
  }
 
  // 这个值有利于我们通过字节码索引快速找到对应的data cell
  _hint_di = first_di();
 
  post_initialize(&stream);
 
  set_size(object_size);
}

之前介绍过,在创建MethodData实例时,首先要开辟相关空间,而如上函数的几部分代码正好在初始化之前几个部分开辟出的空间。

第1部分

调用如下函数对字节码对应的data cell初始化,函数的实现如下: 

// 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) { // TypeProfileCasts默认的值为true
      cell_count = ReceiverTypeData::static_cell_count(); // 值为5
      tag = DataLayout::receiver_type_data_tag;
    } else {
      cell_count = BitData::static_cell_count(); // 值为0
      tag = DataLayout::bit_data_tag;
    }
    break;
  case Bytecodes::_invokespecial:
  case Bytecodes::_invokestatic: {
    int counter_data_cell_count = CounterData::static_cell_count(); // 值为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(); // 值为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(); // 值为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);
    // 如下函数的计算方式为:4 + cell_count * 4
    return DataLayout::compute_size_in_bytes(cell_count);
  } else {
    assert(!bytecode_has_profile(c), "agree w/ !BHP");
    return 0;
  }
}

函数初始化对应的data cell并返回data cell的大小。

第3与第4部分

DataLayout::initialize()函数的实现如下:

// 初始化一般性的属性,对于一些特定属性的初始化,通过调用
// ProfileData::post_initialize()函数完成
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;
}

可以看到为DataLayout中的各个属性赋值的具体实现。不过这里只会对_cells中的部分slot进行赋值,具体这些slot中存储什么样的值,还要看具体存储的ProfileData的具体类型。我们在下一篇给一个具体的实例。

可以根据《深入解析HotSpot》来研究Template::branch()的实现。

即使解释器通过循环体的回边探测到一个热点方法,并对该方法进行了编译,但是main方法只执行一次,被编译后的main方法根本没有执行的机会。为了解决这个问题,需要一种机制:在运行main方法的过程中(而非运行main()方法之后)使用编译后的方法替换当前执行,这样的机制称为OSR。OSR用于方法从低层次向高层次执行变换。发生OSR的时机是遇到回边字节码,而回边又是在branch中体现的。

公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流