JVM如何实际转换对象并发出ClassCastException?

Mat*_*ieu 5 java jvm casting runtime

将对象强制转换为特定类时会发生什么casted = (NewClass)obj;?我猜JVM以某种方式检查实际的类obj是否是子类NewClass,但是对象实例是否有办法知道它何时被"转换"?

一些JVM实现的文档/ FAQ的指针也很受欢迎,因为我还没有找到任何...

编辑"为什么一个物体在被铸造时应该知道?":

我最近在考虑实现一种既是一种InputStream又一种的管道OutputStream.因为这些是类而不是接口,所以它不能同时存在(因为Java不能扩展多个类),所以我想知道是否有一种方法可以让对象通过某种可截获的转换操作来显示自身的不同视图.

并不是说我想要实现它(好吧,我会用于测试和有趣的黑客目的;))因为它太危险了,并且允许所有类型的疯狂滥用和滥用.

ζ--*_*ζ-- 15

JVM有一个字节码,checkcast用于检查是否可以有效执行强制转换.实际的强制转换检查语义在JLS§5.5.3checkcast中描述,字节码的细节在JVM规范6.5中描述.举个例子,

public static void main(String args[]) {
   Number n = Integer.valueOf(66); // Autoboxing

   incr((Integer) n);

   System.out.println(n);
}
Run Code Online (Sandbox Code Playgroud)

产生

 public static void main(java.lang.String[]);
    Code:
       0: bipush        66
       2: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1
       6: aload_1
       7: checkcast     #4                  // class java/lang/Integer
      10: invokestatic  #5                  // Method incr:(Ljava/lang/Integer;)V
      13: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: aload_1
      17: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      20: return
Run Code Online (Sandbox Code Playgroud)

此外,通过深入研究热点的源代码,我们可以看到的两种实现方式checkcast,在生产中使用,另一个用于简单的测试和早期的港口之一.

首先显示的是基于生产模板的解释器(感谢apangin使我意识到它),它生成的代码对应于要进行强制检查的引用的空检查,类信息的加载,对子类型的调用检查,并可能跳转到抛出ClassCastException的代码:

void TemplateTable::checkcast() {
  transition(atos, atos);
  Label done, is_null, ok_is_subtype, quicked, resolved;
  __ testptr(rax, rax); // object is in rax
  __ jcc(Assembler::zero, is_null);

  // Get cpool & tags index
  __ get_cpool_and_tags(rcx, rdx); // rcx=cpool, rdx=tags array
  __ get_unsigned_2_byte_index_at_bcp(rbx, 1); // rbx=index
  // See if bytecode has already been quicked
  __ cmpb(Address(rdx, rbx,
                  Address::times_1,
                  Array<u1>::base_offset_in_bytes()),
          JVM_CONSTANT_Class);
  __ jcc(Assembler::equal, quicked);
  __ push(atos); // save receiver for result, and for GC
  call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::quicken_io_cc));
  // vm_result_2 has metadata result
  __ get_vm_result_2(rax, r15_thread);
  __ pop_ptr(rdx); // restore receiver
  __ jmpb(resolved);

  // Get superklass in rax and subklass in rbx
  __ bind(quicked);
  __ mov(rdx, rax); // Save object in rdx; rax needed for subtype check
  __ movptr(rax, Address(rcx, rbx,
                       Address::times_8, sizeof(ConstantPool)));

  __ bind(resolved);
  __ load_klass(rbx, rdx);

  // Generate subtype check.  Blows rcx, rdi.  Object in rdx.
  // Superklass in rax.  Subklass in rbx.
  __ gen_subtype_check(rbx, ok_is_subtype);

  // Come here on failure
  __ push_ptr(rdx);
  // object is at TOS
  __ jump(ExternalAddress(Interpreter::_throw_ClassCastException_entry));

  // Come here on success
  __ bind(ok_is_subtype);
  __ mov(rax, rdx); // Restore object in rdx

  // Collect counts on whether this check-cast sees NULLs a lot or not.
  if (ProfileInterpreter) {
    __ jmp(done);
    __ bind(is_null);
    __ profile_null_seen(rcx);
  } else {
    __ bind(is_null);   // same as 'done'
  }
  __ bind(done);
}
Run Code Online (Sandbox Code Playgroud)

简单的非生产解释器可以在bytecodeInterpreter.cpp2048行向我们展示另一个例子.实际上,当达到a 时,我们可以看到符合样本的字节码解释器中会发生什么checkcast:

  CASE(_checkcast):
      if (STACK_OBJECT(-1) != NULL) {
        VERIFY_OOP(STACK_OBJECT(-1));
        u2 index = Bytes::get_Java_u2(pc+1);
        if (ProfileInterpreter) {
          // needs Profile_checkcast QQQ
          ShouldNotReachHere();
        }
        // Constant pool may have actual klass or unresolved klass. If it is
        // unresolved we must resolve it
        if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
          CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
        }
        Klass* klassOf = (Klass*) METHOD->constants()->slot_at(index).get_klass();
        Klass* objKlassOop = STACK_OBJECT(-1)->klass(); //ebx
        //
        // Check for compatibilty. This check must not GC!!
        // Seems way more expensive now that we must dispatch
        //
        if (objKlassOop != klassOf &&
            !objKlassOop->is_subtype_of(klassOf)) {
          ResourceMark rm(THREAD);
          const char* objName = objKlassOop->external_name();
          const char* klassName = klassOf->external_name();
          char* message = SharedRuntime::generate_class_cast_message(
            objName, klassName);
          VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(), message);
        }
      } else {
        if (UncommonNullCast) {
            //              istate->method()->set_null_cast_seen();
            // [RGV] Not sure what to do here!

        }
      }
      UPDATE_PC_AND_CONTINUE(3);
Run Code Online (Sandbox Code Playgroud)

简而言之,它从堆栈中获取参数,从常量池中获取Class对象(必要时进行解析),并检查参数是否可分配给该类.如果没有,它将获取对象的类型和尝试强制转换的类的名称,构造异常消息,并使用该消息抛出ClassCastException.奇怪的是,抛出ClassCastException的机制与用于athrow字节码的机制不同(使用VM_JAVA_ERROR而不是set_pending_exception).

响应编辑:最好只使用类型系统和OOP原则而不是奇怪的Java内部.只要有一个Pipe类(延伸对象),其具有一个getInputStream和一个getOutputStream方法,其中的每一个返回一个对应的内部类的实例(即Pipe$PipeInputStreamPipe$PipeOutputStream,这两者的访问的私人/受保护的状态Pipe)

  • 既然你参考了`bytecodeInterpreter.cpp`,你必须知道这段代码在生产JVM中**从不执行**。这是一个简单的实施草案,仅用于实验和快速移植到新平台,与实际实施相去甚远。“真正的”解释器(又名模板解释器)是用特定于平台的宏汇编器编写的,其来源位于`src/cpu/&lt;arch&gt;/vm/templateInterpreter_&lt;arch&gt;.cpp` 和`src/cpu/&lt;arch&gt;/ vm/templateTable_&lt;arch&gt;.cpp`。 (2认同)
  • @apangin我不知道。谢谢你的澄清;我将很快更新代码示例和分析。 (2认同)