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

时间:2016-06-15 16:32:46

标签: java casting jvm runtime

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

对于一些JVM实现的文档/常见问题解答的指示也是受欢迎的,因为我还没有找到任何...

编辑"为什么对象应该知道它何时被投射?":

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

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

2 个答案:

答案 0 :(得分:12)

JVM有一个字节码checkcast,用于检查是否可以有效执行强制转换。实际的强制转换检查语义在JLS§5.5.3中描述,checkcast字节码的详细信息在JVM spec§6.5中描述。例如,

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

   incr((Integer) n);

   System.out.println(n);
}

产生

 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

此外,通过深入研究Hotspot的源代码,我们可以看到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);
}

简单的非制作翻译可以在bytecodeInterpreter.cpp line 2048向我们展示另一个例子。实际上,当达到checkcast时,我们可以看到符合 sample 的字节码解释器中会发生什么:

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

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

对编辑的反应:最好只使用类型系统和OOP原则而不是奇怪的Java内部。只需要一个Pipe类(扩展Object),它具有getInputStreamgetOutputStream方法,每个方法都返回一个相应内部类的实例(即Pipe$PipeInputStream和{ {1}},两者都访问私有/受保护状态Pipe$PipeOutputStream

答案 1 :(得分:1)

这种检查在编译器编译期间和jvm运行期间发生两次。

在编译期间,编译器会查看元素是否属于同一层次结构(例如:Vehicle-&gt; Car-&gt; BMW),并且只有在运行时jvm才会看到对象的确是什么。编译器处理引用和jvm处理对象。

现在,在转发异常期间,错误转换的元素位于同一层次结构中,因此编译成功发生,但运行期间的jvm会看到对象并识别错误的转换并抛出异常。