Java加载与存储指令之ldc与_fast_aldc指令

目录
  • 1、ldc字节码指令
  • 2、fast_aldc虚拟机内部字节码指令

ldc指令可以加载String、方法类型或方法句柄的符号引用,但是如果要加载String、方法类型或方法句柄的符号引用,则会在类连接过程中重写ldc字节码指令为虚拟机内部使用的字节码指令_fast_aldc。下面我们详细介绍ldc指令如何加载int、float类型和类类型的数据,以及_fast_aldc加载String、方法类型或方法句柄,还有为什么要进行字节码重写等问题。

1、ldc字节码指令

ldc指令将int、float或String型常量值从常量池中推送至栈顶。模板的定义如下:

def(Bytecodes::_ldc , ubcp|____|clvm|____, vtos, vtos, ldc ,  false );

ldc字节码指令的格式如下:

// index是一个无符号的byte类型数据,指明当前类的运行时常量池的索引
ldc index

调用生成函数TemplateTable::ldc(bool wide)。函数生成的汇编代码如下:  

第1部分代码:

// movzbl指令负责拷贝一个字节,并用0填充其目
// 的操作数中的其余各位,这种扩展方式叫"零扩展"
// ldc指定的格式为ldc index,index为一个字节
0x00007fffe1028530: movzbl 0x1(%r13),%ebx // 加载index到%ebx

// %rcx指向缓存池首地址、%rax指向类型数组_tags首地址
0x00007fffe1028535: mov    -0x18(%rbp),%rcx
0x00007fffe1028539: mov    0x10(%rcx),%rcx
0x00007fffe102853d: mov    0x8(%rcx),%rcx
0x00007fffe1028541: mov    0x10(%rcx),%rax

// 从_tags数组获取操作数类型并存储到%edx中
0x00007fffe1028545: movzbl 0x4(%rax,%rbx,1),%edx

// $0x64代表JVM_CONSTANT_UnresolvedClass,比较,如果类还没有链接,
// 则直接跳转到call_ldc
0x00007fffe102854a: cmp    $0x64,%edx
0x00007fffe102854d: je     0x00007fffe102855d   // call_ldc

// $0x67代表JVM_CONSTANT_UnresolvedClassInError,也就是如果类在
// 链接过程中出现错误,则跳转到call_ldc
0x00007fffe102854f: cmp    $0x67,%edx
0x00007fffe1028552: je     0x00007fffe102855d  // call_ldc

// $0x7代表JVM_CONSTANT_Class,表示如果类已经进行了连接,则
// 跳转到notClass
0x00007fffe1028554: cmp    $0x7,%edx
0x00007fffe1028557: jne    0x00007fffe10287c0  // notClass

// 类在没有连接或连接过程中出错,则执行如下的汇编代码
// -- call_ldc --

下面看一下调用call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::ldc), c_rarg1)函数生成的汇编代码,CAST_FROM_FN_PTR是宏,宏扩展后为( (address)((address_word)(InterpreterRuntime::ldc)) )。

在调用call_VM()函数时,传递的参数如下:

  • %rax现在存储类型数组首地址,不过传入是为了接收调用函数的结果值
  • adr是InterpreterRuntime::ldc()函数首地址
  • c_rarg1用rdi寄存器存储wide值,这里为0,表示为没有加wide前缀的ldc指令生成汇编代码

生成的汇编代码如下:

第2部分:

// 将wide的值移到%esi寄存器,为后续
// 调用InterpreterRuntime::ldc()函数准备第2个参数
0x00007fffe102855d: mov $0x0,%esi
// 调用MacroAssembler::call_VM()函数,通过此函数来调用HotSpot VM中用
// C++编写的函数,通过这个C++编写的函数来调用InterpreterRuntime::ldc()函数

0x00007fffe1017542: callq  0x00007fffe101754c
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳转到E1

// 调用MacroAssembler::call_VM_helper()函数
// 将栈顶存储的返回地址设置到%rax中,也就是将存储地址0x00007fffe1017547
// 的栈的slot地址设置到%rax中
0x00007fffe101754c: lea 0x8(%rsp),%rax

// 调用InterpreterMacroAssembler::call_VM_base()函数
// 存储bcp到栈中特定位置
0x00007fffe1017551: mov %r13,-0x38(%rbp)

// 调用MacroAssembler::call_VM_base()函数
// 将r15中的值移动到rdi寄存器中,也就是为函数调用准备第一个参数
0x00007fffe1017555: mov   %r15,%rdi
// 只有解释器才必须要设置fp
// 将last_java_fp保存到JavaThread类的last_java_fp属性中
0x00007fffe1017558: mov   %rbp,0x200(%r15)
// 将last_java_sp保存到JavaThread类的last_java_sp属性中
0x00007fffe101755f: mov   %rax,0x1f0(%r15)   

// ... 省略调用MacroAssembler::call_VM_leaf_base()函数

// 重置JavaThread::last_java_sp与JavaThread::last_java_fp属性的值
0x00007fffe1017589: movabs $0x0,%r10
0x00007fffe1017593: mov %r10,0x1f0(%r15)
0x00007fffe101759a: movabs $0x0,%r10
0x00007fffe10175a4: mov %r10,0x200(%r15)

// check for pending exceptions (java_thread is set upon return)
0x00007fffe10175ab: cmpq  $0x0,0x8(%r15)
// 如果没有异常则直接跳转到ok
0x00007fffe10175b3: je    0x00007fffe10175be
// 如果有异常则跳转到StubRoutines::forward_exception_entry()获取的例程入口
0x00007fffe10175b9: jmpq  0x00007fffe1000420

// -- ok --
// 将JavaThread::vm_result属性中的值存储到%rax寄存器中并清空vm_result属性的值
0x00007fffe10175be: mov     0x250(%r15),%rax
0x00007fffe10175c5: movabs  $0x0,%r10
0x00007fffe10175cf: mov     %r10,0x250(%r15)

// 结束调用MacroAssembler::call_VM_base()函数

// 恢复bcp与locals
0x00007fffe10175d6: mov   -0x38(%rbp),%r13
0x00007fffe10175da: mov   -0x30(%rbp),%r14

// 结束调用MacroAssembler::call_VM_helper()函数

0x00007fffe10175de: retq
// 结束调用MacroAssembler::call_VM()函数

下面详细解释如下汇编的意思。  

call指令相当于如下两条指令:

push %eip
jmp  addr

而ret指令相当于:

pop %eip

所以如上汇编代码:

0x00007fffe1017542: callq  0x00007fffe101754c
0x00007fffe1017547: jmpq   0x00007fffe10175df // 跳转
...
0x00007fffe10175de: retq

调用callq指令将jmpq的地址压入了表达式栈,也就是压入了返回地址x00007fffe1017547,这样当后续调用retq时,会跳转到jmpq指令执行,而jmpq又跳转到了0x00007fffe10175df地址处的指令执行。

通过调用MacroAssembler::call_VM()函数来调用HotSpot VM中用的C++编写的函数,call_VM()函数还会调用如下函数:

MacroAssembler::call_VM_helper
InterpreterMacroAssembler::call_VM_base()
MacroAssembler::call_VM_base()
MacroAssembler::call_VM_leaf_base()

在如上几个函数中,最重要的就是在MacroAssembler::call_VM_base()函数中保存rsp、rbp的值到JavaThread::last_java_spJavaThread::last_java_fp属性中,然后通过MacroAssembler::call_VM_leaf_base()函数生成的汇编代码来调用C++编写的InterpreterRuntime::ldc()函数,如果调用InterpreterRuntime::ldc()函数有可能破坏rsp和rbp的值(其它的%r13、%r14等的寄存器中的值也有可能破坏,所以在必要时保存到栈中,在调用完成后再恢复,这样这些寄存器其实就算的上是调用者保存的寄存器了),所以为了保证rsp、rbp,将这两个值存储到线程中,在线程中保存的这2个值对于栈展开非常非常重要,后面我们会详细介绍。

由于如上汇编代码会解释执行,在解释执行过程中会调用C++函数,所以C/C++栈和Java栈都混在一起,这为我们查找带来了一定的复杂度。

调用的MacroAssembler::call_VM_leaf_base()函数生成的汇编代码如下:

第3部分汇编代码:

// 调用MacroAssembler::call_VM_leaf_base()函数
0x00007fffe1017566: test  $0xf,%esp          // 检查对齐
// %esp对齐的操作,跳转到 L
0x00007fffe101756c: je    0x00007fffe1017584
// %esp没有对齐时的操作
0x00007fffe1017572: sub   $0x8,%rsp
0x00007fffe1017576: callq 0x00007ffff66a22a2  // 调用函数,也就是调用InterpreterRuntime::ldc()函数
0x00007fffe101757b: add   $0x8,%rsp
0x00007fffe101757f: jmpq  0x00007fffe1017589  // 跳转到E2
// -- L --
// %esp对齐的操作
0x00007fffe1017584: callq 0x00007ffff66a22a2  // 调用函数,也就是调用InterpreterRuntime::ldc()函数

// -- E2 --

// 结束调用
MacroAssembler::call_VM_leaf_base()函数

在如上这段汇编中会真正调用C++函数InterpreterRuntime::ldc(),由于这是一个C++函数,所以在调用时,如果要传递参数,则要遵守C++调用约定,也就是前6个参数都放到固定的寄存器中。这个函数需要2个参数,分别为threadwide,已经分别放到了%rdi和%rax寄存器中了。InterpreterRuntime::ldc()函数的实现如下:

// ldc负责将数值常量或String常量值从常量池中推送到栈顶
IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))
  ConstantPool* pool = method(thread)->constants();
  int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread, Bytecodes::_ldc);
  constantTag tag = pool->tag_at(index);

  Klass* klass = pool->klass_at(index, CHECK);
  oop java_class = klass->java_mirror(); // java.lang.Class通过oop来表示
  thread->set_vm_result(java_class);
IRT_END

函数将查找到的、当前正在解释执行的方法所属的类存储到JavaThread类的vm_result属性中。我们可以回看第2部分汇编代码,会将vm_result属性的值设置到%rax中。

接下来继续看TemplateTable::ldc(bool wide)函数生成的汇编代码,此时已经通过调用call_VM()函数生成了调用InterpreterRuntime::ldc()这个C++的汇编,调用完成后值已经放到了%rax中。

// -- E1 --
0x00007fffe10287ba: push   %rax  // 将调用的结果存储到表达式中
0x00007fffe10287bb: jmpq   0x00007fffe102885e // 跳转到Done

// -- notClass --
// $0x4表示JVM_CONSTANT_Float
0x00007fffe10287c0: cmp    $0x4,%edx
0x00007fffe10287c3: jne    0x00007fffe10287d9 // 跳到notFloat
// 当ldc字节码指令加载的数为float时执行如下汇编代码
0x00007fffe10287c5: vmovss 0x58(%rcx,%rbx,8),%xmm0
0x00007fffe10287cb: sub    $0x8,%rsp
0x00007fffe10287cf: vmovss %xmm0,(%rsp)
0x00007fffe10287d4: jmpq   0x00007fffe102885e // 跳转到Done

// -- notFloat --
// 当ldc字节码指令加载的为非float,也就是int类型数据时通过push加入表达式栈
0x00007fffe1028859: mov    0x58(%rcx,%rbx,8),%eax
0x00007fffe102885d: push   %rax

// -- Done --

由于ldc指令除了加载String外,还可能加载intfloat,如果是int,直接调用push压入表达式栈中,如果是float,则在表达式栈上开辟空间,然后移到到这个开辟的slot中存储。注意,float会使用%xmm0寄存器。

2、fast_aldc虚拟机内部字节码指令

下面介绍_fast_aldc指令,这个指令是虚拟机内部使用的指令而非虚拟机规范定义的指令。_fast_aldc指令的模板定义如下:

def(Bytecodes::_fast_aldc , ubcp|____|clvm|____, vtos, atos, fast_aldc ,  false );

生成函数为TemplateTable::fast_aldc(bool wide),这个函数生成的汇编代码如下:

// 调用InterpreterMacroAssembler::get_cache_index_at_bcp()函数生成
// 获取字节码指令的操作数,这个操作数已经指向了常量池缓存项的索引,在字节码重写
// 阶段已经进行了字节码重写
0x00007fffe10243d0: movzbl 0x1(%r13),%edx

// 调用InterpreterMacroAssembler::load_resolved_reference_at_index()函数生成

// shl表示逻辑左移,相当于乘4,因为ConstantPoolCacheEntry的大小为4个字
0x00007fffe10243d5: shl    $0x2,%edx

// 获取Method*
0x00007fffe10243d8: mov    -0x18(%rbp),%rax
// 获取ConstMethod*
0x00007fffe10243dc: mov    0x10(%rax),%rax
// 获取ConstantPool*
0x00007fffe10243e0: mov    0x8(%rax),%rax
// 获取ConstantPool::_resolved_references属性的值,这个值
// 是一个指向对象数组的指针
0x00007fffe10243e4: mov    0x30(%rax),%rax

// JNIHandles::resolve(obj)
0x00007fffe10243e8: mov    (%rax),%rax

// 从_resolved_references数组指定的下标索引处获取oop,先进行索引偏移
0x00007fffe10243eb: add    %rdx,%rax

// 要在%rax上加0x10,是因为数组对象的头大小为2个字,加上后
// %rax就指向了oop
0x00007fffe10243ee: mov    0x10(%rax),%eax

获取_resolved_references属性的值,涉及到的2个属性在ConstantPool类中的定义如下:

// Array of resolved objects from the constant pool and map from resolved
// object index to original constant pool index
jobject              _resolved_references; // jobject是指针类型
Array<u2>*           _reference_map;

关于_resolved_references指向的其实是Object数组。在ConstantPool::initialize_resolved_references()函数中初始化这个属性。调用链如下:

ConstantPool::initialize_resolved_references()  constantPool.cpp   
Rewriter::make_constant_pool_cache()  rewriter.cpp
Rewriter::Rewriter()                  rewriter.cpp
Rewriter::rewrite()                   rewriter.cpp
InstanceKlass::rewrite_class()        instanceKlass.cpp
InstanceKlass::link_class_impl()      instanceKlass.cpp

后续如果需要连接ldc等指令时,可能会调用如下函数:(我们只讨论ldc加载String类型数据的问题,所以我们只看往_resolved_references属性中放入表示String的oop的逻辑,MethodTypeMethodHandle将不再介绍,有兴趣的可自行研究)

oop ConstantPool::string_at_impl(
 constantPoolHandle this_oop,
 int    which,
 int    obj_index,
 TRAPS
) {
  oop str = this_oop->resolved_references()->obj_at(obj_index);
  if (str != NULL)
      return str;

  Symbol* sym = this_oop->unresolved_string_at(which);
  str = StringTable::intern(sym, CHECK_(NULL));

  this_oop->string_at_put(which, obj_index, str);

  return str;
}

void string_at_put(int which, int obj_index, oop str) {
  // 获取类型为jobject的_resolved_references属性的值
  objArrayOop tmp = resolved_references();
  tmp->obj_at_put(obj_index, str);
}

在如上函数中向_resolved_references数组中设置缓存的值。

大概的思路就是:如果ldc加载的是字符串,那么尽量通过_resolved_references数组中一次性找到表示字符串的oop,否则要通过原常量池下标索引找到Symbol实例(Symbol实例是HotSpot VM内部使用的、用来表示字符串),根据Symbol实例生成对应的oop,然后通过常量池缓存下标索引设置到_resolved_references中。当下次查找时,通过这个常量池缓存下标缓存找到表示字符串的oop

获取到_resolved_references属性的值后接着看生成的汇编代码,如下:

// ...
// %eax中存储着表示字符串的oop
0x00007fffe1024479: test   %eax,%eax
// 如果已经获取到了oop,则跳转到resolved
0x00007fffe102447b: jne    0x00007fffe1024481

// 没有获取到oop,需要进行连接操作,0xe5是_fast_aldc的Opcode
0x00007fffe1024481: mov    $0xe5,%edx  

调用call_VM()函数生成的汇编代码如下:

// 调用InterpreterRuntime::resolve_ldc()函数
0x00007fffe1024486: callq  0x00007fffe1024490
0x00007fffe102448b: jmpq   0x00007fffe1024526

// 将%rdx中的ConstantPoolCacheEntry项存储到第1个参数中

// 调用MacroAssembler::call_VM_helper()函数生成
0x00007fffe1024490: mov    %rdx,%rsi
// 将返回地址加载到%rax中
0x00007fffe1024493: lea    0x8(%rsp),%rax

// 调用call_VM_base()函数生成
// 保存bcp
0x00007fffe1024498: mov    %r13,-0x38(%rbp)

// 调用MacroAssembler::call_VM_base()函数生成

// 将r15中的值移动到c_rarg0(rdi)寄存器中,也就是为函数调用准备第一个参数
0x00007fffe102449c: mov    %r15,%rdi
// Only interpreter should have to set fp 只有解释器才必须要设置fp
0x00007fffe102449f: mov    %rbp,0x200(%r15)
0x00007fffe10244a6: mov    %rax,0x1f0(%r15)

// 调用MacroAssembler::call_VM_leaf_base()生成
0x00007fffe10244ad: test   $0xf,%esp
0x00007fffe10244b3: je     0x00007fffe10244cb
0x00007fffe10244b9: sub    $0x8,%rsp
0x00007fffe10244bd: callq  0x00007ffff66b27ac
0x00007fffe10244c2: add    $0x8,%rsp
0x00007fffe10244c6: jmpq   0x00007fffe10244d0
0x00007fffe10244cb: callq  0x00007ffff66b27ac
0x00007fffe10244d0: movabs $0x0,%r10
// 结束调用MacroAssembler::call_VM_leaf_base()

0x00007fffe10244da: mov    %r10,0x1f0(%r15)
0x00007fffe10244e1: movabs $0x0,%r10

// 检查是否有异常发生
0x00007fffe10244eb: mov    %r10,0x200(%r15)
0x00007fffe10244f2: cmpq   $0x0,0x8(%r15)
// 如果没有异常发生,则跳转到ok
0x00007fffe10244fa: je     0x00007fffe1024505
// 有异常发生,则跳转到StubRoutines::forward_exception_entry()
0x00007fffe1024500: jmpq   0x00007fffe1000420

// ---- ok ----

// 将JavaThread::vm_result属性中的值存储到oop_result寄存器中并清空vm_result属性的值
0x00007fffe1024505: mov    0x250(%r15),%rax
0x00007fffe102450c: movabs $0x0,%r10
0x00007fffe1024516: mov    %r10,0x250(%r15)

// 结果调用MacroAssembler::call_VM_base()函数

// 恢复bcp和locals
0x00007fffe102451d: mov    -0x38(%rbp),%r13
0x00007fffe1024521: mov    -0x30(%rbp),%r14

// 结束调用InterpreterMacroAssembler::call_VM_base()函数
// 结束调用MacroAssembler::call_VM_helper()函数

0x00007fffe1024525: retq   

// 结束调用MacroAssembler::call_VM()函数,回到
// TemplateTable::fast_aldc()函数继续看生成的代码,只
// 定义了resolved点

// ---- resolved ----  

调用的InterpreterRuntime::resolve_ldc()函数的实现如下:

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(
 JavaThread* thread,
 Bytecodes::Code bytecode)
) {
  ResourceMark rm(thread);
  methodHandle m (thread, method(thread));
  Bytecode_loadconstant  ldc(m, bci(thread));
  oop result = ldc.resolve_constant(CHECK);

  thread->set_vm_result(result);
}
IRT_END

这个函数会调用一系列的函数,相关调用链如下:

ConstantPool::string_at_put()   constantPool.hpp
ConstantPool::string_at_impl()  constantPool.cpp
ConstantPool::resolve_constant_at_impl()     constantPool.cpp
ConstantPool::resolve_cached_constant_at()   constantPool.hpp
Bytecode_loadconstant::resolve_constant()    bytecode.cpp
InterpreterRuntime::resolve_ldc()            interpreterRuntime.cpp   

 调用的resolve_constant()函数的实现如下:

oop Bytecode_loadconstant::resolve_constant(TRAPS) const {
  int index = raw_index();
  ConstantPool* constants = _method->constants();
  if (has_cache_index()) {
    return constants->resolve_cached_constant_at(index, THREAD);
  } else {
    return constants->resolve_constant_at(index, THREAD);
  }
}

调用的resolve_cached_constant_at()resolve_constant_at()函数的实现如下:

oop resolve_cached_constant_at(int cache_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, _no_index_sentinel, cache_index, THREAD);
}

oop resolve_possibly_cached_constant_at(int pool_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, pool_index, _possible_index_sentinel, THREAD);
}

调用的resolve_constant_at_impl()函数的实现如下:

oop ConstantPool::resolve_constant_at_impl(
 constantPoolHandle this_oop,
 int index,
 int cache_index,
 TRAPS
) {
  oop result_oop = NULL;
  Handle throw_exception;

  if (cache_index == _possible_index_sentinel) {
    cache_index = this_oop->cp_to_object_index(index);
  }

  if (cache_index >= 0) {
    result_oop = this_oop->resolved_references()->obj_at(cache_index);
    if (result_oop != NULL) {
      return result_oop;
    }
    index = this_oop->object_to_cp_index(cache_index);
  }

  jvalue prim_value;  // temp used only in a few cases below

  int tag_value = this_oop->tag_at(index).value();

  switch (tag_value) {
  // ...
  case JVM_CONSTANT_String:
    assert(cache_index != _no_index_sentinel, "should have been set");
    if (this_oop->is_pseudo_string_at(index)) {
      result_oop = this_oop->pseudo_string_at(index, cache_index);
      break;
    }
    result_oop = string_at_impl(this_oop, index, cache_index, CHECK_NULL);
    break;
  // ...
  }

  if (cache_index >= 0) {
    Handle result_handle(THREAD, result_oop);
    MonitorLockerEx ml(this_oop->lock());
    oop result = this_oop->resolved_references()->obj_at(cache_index);
    if (result == NULL) {
      this_oop->resolved_references()->obj_at_put(cache_index, result_handle());
      return result_handle();
    } else {
      return result;
    }
  } else {
    return result_oop;
  }
}

通过常量池的tags数组判断,如果常量池下标index处存储的是JVM_CONSTANT_String常量池项,则调用string_at_impl()函数,这个函数在之前已经介绍过,会根据表示字符串的Symbol实例创建出表示字符串的oop。在ConstantPool::resolve_constant_at_impl()函数中得到oop后就存储到ConstantPool::_resolved_references属性中,最后返回这个oop,这正是ldc需要的oop。 

通过重写fast_aldc字节码指令,达到了通过少量指令就直接获取到oop的目的,而且oop是缓存的,所以字符串常量在HotSpot VM中的表示唯一,也就是只有一个oop表示。  

C++函数约定返回的值会存储到%rax中,根据_fast_aldc字节码指令的模板定义可知,tos_outatos,所以后续并不需要进一步操作。

到此这篇关于Java加载与存储指令之ldc与_fast_aldc指令的文章就介绍到这了,更多相关Java的ldc与_fast_aldc指令内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java虚拟机指令dup详解

    本文实例为大家介绍了java虚拟机指令dup,供大家参考,具体内容如下 举个例子: public class ExceptionTest{ void cantBeZero(int i) throws Exception{ throw new Exception(); } } 上面代码编译后的字节码指令如下: void cantBeZero(int) throws java.lang.Exception; descriptor: (I)V flags: Code: stack=2, locals=

  • 浅谈java指令重排序的问题

    指令重排序是个比较复杂.觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下例子,这是实实在在可以重现的,重排序的概率还是挺高的),有个感性的认识 /** * 一个简单的展示Happen-Before的例子. * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给 a=1,然后flag=true. * 如果按照有序的话,那么在ThreadB中如果if(flag)成功的话,则应该a=1,而a=a*1之后a仍然为1,下方的if(a==0)应该永远不会为 * 真,

  • Java字节码指令集的使用详细

    Java虚拟机指令由一个字节长度的.代表某种特定含义的操作码(Opcode)以及其后的零个至多个代表此操作参数的操作数构成.虚拟机中许多指令并不包含操作数,只有一个操作码.若忽略异常,JVM解释器使用一下为代码即可有效工作. 复制代码 代码如下: do{    自动计算PC寄存器以及从PC寄存器的位置取出操作码    if(存在操作数) 取出操作数;    执行操作码所定义的操作;}while(处理下一次循环) 操作数的数量以及长度,取决于操作码,若一个操作数长度超过了一个字节,将会以Big-E

  • Java语言中cas指令的无锁编程实现实例

    最开始接触到相关的内容应该是从volatile关键字开始的吧,知道它可以保证变量的可见性,而且利用它可以实现读与写的原子操作...但是要实现一些复合的操作volatile就无能为力了...最典型的代表是递增和递减的操作.... 我们知道,在并发的环境下,要实现数据的一致性,最简单的方式就是加锁,保证同一时刻只有一个线程可以对数据进行操作....例如一个计数器,我们可以用如下的方式来实现: public class Counter { private volatile int a = 0; pub

  • 谈谈Java类型中ParameterizedType,GenericArrayType,TypeVariabl,WildcardType

    (1). 和反射+泛型有关的接口类型 java.lang.reflect.Type:java语言中所有类型的公共父接口 java.lang.reflect.ParameterizedType java.lang.reflect.GenericArrayType java.lang.reflect.WildcardType 1. Type直接子接口 ParameterizedType,GenericArrayType,TypeVariable和WildcardType四种类型的接口 Paramet

  • Java中invokedynamic字节码指令问题

    1. 方法引用和invokedynamic invokedynamic是jvm指令集里面最复杂的一条.本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Method reference)的. 具体言之,有这样一个方法引用: interface Encode { void encode(Derive person); } class Base { public void encrypt() { System.out.println("Base::speak");

  • Java基础之常用的命令行指令

    1.进入某盘,直接打出要进入的存储盘再冒号回车即可 例如:从C盘中的\Users\Administrator目录下进入d盘 打出"d:"再回车就欧克啦~ 2. "dir"显示某目录或某盘下的所有文件目录 例如:1.显示D盘下的文件目录 打出"dir"再回车 3. "md"新建文件夹 例如:1.在d盘下创建一个叫JavaEE的文件[也可在d盘的文件目录下建立文件夹] 格式是md javaEE再回车就可以啦.我们可以去我的电脑d盘

  • idea ssm项目java程序向串口发送指令并且使用十六进制 rxtx包的方法

    idea ssm项目 java程序向串口发送指令并且使用十六进制 rxtx包 1.前提 idea ssm项目环境要配好,这里就不多赘述了,自己配好 2.下载配置rxtx包 官网下载:http://rxtx.qbang.org/wiki/index.php/Download 根据需要下载,本章记录的是windows10下64位系统的配置 3.踩过的坑 如果和我一样是windows10系统下64位操作系统,不要去用别的博客说的comm2.0.jar串口开发包,因为在配置后会出现问题:Can't lo

  • Java加载与存储指令之ldc与_fast_aldc指令

    目录 1.ldc字节码指令 2.fast_aldc虚拟机内部字节码指令 ldc指令可以加载String.方法类型或方法句柄的符号引用,但是如果要加载String.方法类型或方法句柄的符号引用,则会在类连接过程中重写ldc字节码指令为虚拟机内部使用的字节码指令_fast_aldc.下面我们详细介绍ldc指令如何加载int.float类型和类类型的数据,以及_fast_aldc加载String.方法类型或方法句柄,还有为什么要进行字节码重写等问题. 1.ldc字节码指令 ldc指令将int.floa

  • Java加载JDBC驱动程序实例详解

    本文实例说明了Java加载JDBC驱动程序的方法,运行本文实例代码后,如果连接成功就会显示如下一条语句:sun.jdbc.odbc.JdbcOdbcDriver@6ec12,如果连接不成功,则显示加载数据库驱动程序出现异常. Java加载JDBC的实现方法: 通过调用Class.forName()方法可以显式地加载一个驱动程序.该方法的入口参数为要加载的驱动程序.例如:Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")语句加载了SUN 公司开发的

  • java加载properties文件的六种方法总结

    java加载properties文件的六种方法总结 java加载properties文件的六中基本方式实现 java加载properties文件的方式主要分为两大类:一种是通过import java.util.Properties类中的load(InputStream in)方法加载: 另一种是通过import java.util.ResourceBundle类的getBundle(String baseName)方法加载. 注意:一定要区分路径格式 实现代码如下: package com.ut

  • python用pandas数据加载、存储与文件格式的实例

    数据加载.存储与文件格式 pandas提供了一些用于将表格型数据读取为DataFrame对象的函数.其中read_csv和read_talbe用得最多 pandas中的解析函数: 函数 说明 read_csv 从文件.URL.文件型对象中加载带分隔符的数据,默认分隔符为逗号 read_table 从文件.URL.文件型对象中加载带分隔符的数据.默认分隔符为制表符("\t") read_fwf 读取定宽列格式数据(也就是说,没有分隔符) read_clipboard 读取剪贴板中的数据,

  • Java加载property文件配置过程解析

    这篇文章主要介绍了java加载property文件配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1 properties简介: properties是一种文本文件,内容格式为: key = value #单行注释 适合作为简单配置文件使用,通常作为参数配置.国际化资源文件使用. 对于复杂的配置,就需要使用XML.YML.JSON等了 2 java加载Properties: java加载properties主要通过2个util包下的

  • java加载属性配置properties文件的方法

    什么是properties文件 属性配置文件,后缀名为 .properties 文件中严格按照key=value进行数据参数的填写 中文参数值需要转为Unicode编码 不区分基本数据类型 一个编辑好的aaa.properties文件如下图所示 username=root flag=true xm =\u4f60\u597d age=18 为什么要使用properties文件 设想这么一种场景,当你项目发布上线后,比如连接mysql数据库的端口号需要调整,难道需要重写改代码,打包,发布么?对于一

  • Java加载properties文件实现方式详解

    java加载properties文件的方式主要分为两大类:一种是通过import java.util.Properties类中的load(InputStream in)方法加载: 另一种是通过import java.util.ResourceBundle类的getBundle(String baseName)方法加载. 注意:一定要区分路径格式 实现代码如下: package com.util; import java.io.FileInputStream; import java.io.Fil

  • 在android中如何用Java加载解析so

    理论基础 so的加载是一种解析式装载,这与dex有一定区别,dex是先加载进行优化验证生成odex,再去解析odex文件,而so更像边解析边装载,在加载过程中主要解析是load段. 下面主要是以java层的so加载进行从源码上进行解析加载流程. java层的so加载流程分析 System.loadLibrary入口点 在java层我们知道加载so文件是通过System.loadLibrary函数其实现的,下面就以其作为入口点进行分析它的调用关系和实现. System.loadLibrary在的函

  • Java加载资源文件时的路径问题的解决办法

    加载资源文件比较常用的有两种: 一.用ClassLoader,说到这里就不得不提一下ClassLoader的分类,java内置的ClassLoader主要有三种, 第一种是根类加载器(bootstrap class loader),用C++来编写,负责将一些关键的Java类,如java.lang.Object和其他一些运行时代码先加载进内存中. 所负责加载的包:BootStrp------>JRE/lib/rt.jar 第二种是扩展类加载器(ExtClassLoader),由java类编写,负责

  • 实现一个简单的vue无限加载指令方法

    vue 中的自定义指令是对底层 dom 进行操作,下面以实现滚动到底部加载数据,实现无限加载来介绍如何自定义一个简单的指令. 无限加载的原理是通过对滚动事件的监听,每一次滚动都要获取到已滚动的距离,如果滚动的距离加上浏览器窗口高度,会大于等于内容高度,就触发函数加载数据. 先介绍不使用 vue 的情况如何实现无限加载. 不使用框架 首先是html: <!DOCTYPE html><html lang="en"> <head><meta char

随机推荐