Native code in Java

21 minute read
Created:
Updated:

在Java的源码里面,有些实现部分是使用native代码来实现的,其实也就是用C/CPP来实现。在Java的代码里面, 使用关键字native来标记一个方法是native code。我们最容易发现是由native代码来实现的函数,很可能就是 位于Object.java中的hashcode, clone这些会出现在经典书籍中的函数。

@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;  

clone()函数是一个很有趣的函数。实现Cloneable接口的类,仅仅需要调用super.clone()就可以生成一个自身的 对象的克隆实例。这里有趣的地方在于,真正实现的代码在Object这类里面,而创建的对象却是调用类的真实对象,克隆 对象会正确的包含调用类的instance fields。而当我们好奇的想看看是怎么实现的,你就会发现在Java的代码中,仅仅 声明了一个返回Object的protected native方法。而且这个native方法还可以抛出一个Java异常,有趣吧。

对于如何正确实现一个Cloneable类,可以参考Effective Java。

那么,实现clone()的native代码在哪里呢?

  • 首先,我们找到OpenJDK是如何组织native代码的。Java的native代码是基于JNI技术,在实现上native代码的对应 文件名是Java Class的.c版本。
  • 于是,我们可以在java.base模块发现好几个native目录,有各个平台依赖的native代码,也有share/native

clone()的实现就在Object.c中。不过这里并没有真正的代码实现,不过还是有点线索的。

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

从上面的代码可以看出,这部分C代码定义了JVM_Clone方法,并将其注册为clone方法的native方法。那么怎么找到JVM_Clone函数呢? 我们可以使用OpenGrok这样的代码搜索引擎来查找JVM_Clone这个函数。

我们会发现,真正的实现在hotspot/src/share/vm/prims/jvm.cpp, hotspot仓库的jvm.cpp实现里面。

// java.lang.Object ///////////////////////////////////////////////

JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
  JVMWrapper("JVM_Clone");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  const KlassHandle klass (THREAD, obj->klass());
  JvmtiVMObjectAllocEventCollector oam;

#ifdef ASSERT
  // Just checking that the cloneable flag is set correct
  if (obj->is_array()) {
    guarantee(klass->is_cloneable(), "all arrays are cloneable");
  } else {
    guarantee(obj->is_instance(), "should be instanceOop");
    bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
    guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
  }
#endif

  // Check if class of obj supports the Cloneable interface.
  // All arrays are considered to be cloneable (See JLS 20.1.5)
  if (!klass->is_cloneable()) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
  }

  // Make shallow object copy
  const int size = obj->size();
  oop new_obj_oop = NULL;
  if (obj->is_array()) {
    const int length = ((arrayOop)obj())->length();
    new_obj_oop = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
  } else {
    new_obj_oop = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
  }

  // 4839641 (4840070): We must do an oop-atomic copy, because if another thread
  // is modifying a reference field in the clonee, a non-oop-atomic copy might
  // be suspended in the middle of copying the pointer and end up with parts
  // of two different pointers in the field.  Subsequent dereferences will crash.
  // 4846409: an oop-copy of objects with long or double fields or arrays of same
  // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
  // of oops.  We know objects are aligned on a minimum of an jlong boundary.
  // The same is true of StubRoutines::object_copy and the various oop_copy
  // variants, and of the code generated by the inline_native_clone intrinsic.
  assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
  Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj_oop,
                               (size_t)align_object_size(size) / HeapWordsPerLong);
  // Clear the header
  new_obj_oop->init_mark();

  // Store check (mark entire object and let gc sort it out)
  BarrierSet* bs = Universe::heap()->barrier_set();
  assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
  bs->write_region(MemRegion((HeapWord*)new_obj_oop, size));

  Handle new_obj(THREAD, new_obj_oop);
  // Special handling for MemberNames.  Since they contain Method* metadata, they
  // must be registered so that RedefineClasses can fix metadata contained in them.
  if (java_lang_invoke_MemberName::is_instance(new_obj()) &&
      java_lang_invoke_MemberName::is_method(new_obj())) {
    Method* method = (Method*)java_lang_invoke_MemberName::vmtarget(new_obj());
    // MemberName may be unresolved, so doesn't need registration until resolved.
    if (method != NULL) {
      methodHandle m(THREAD, method);
      // This can safepoint and redefine method, so need both new_obj and method
      // in a handle, for two different reasons.  new_obj can move, method can be
      // deleted if nothing is using it on the stack.
      m->method_holder()->add_member_name(new_obj(), false);
    }
  }

  // Caution: this involves a java upcall, so the clone should be
  // "gc-robust" by this stage.
  if (klass->has_finalizer()) {
    assert(obj->is_instance(), "should be instanceOop");
    new_obj_oop = InstanceKlass::register_finalizer(instanceOop(new_obj()), CHECK_NULL);
    new_obj = Handle(THREAD, new_obj_oop);
  }

  return JNIHandles::make_local(env, new_obj());
JVM_END

整个的代码实现相对于其他Object的native方法要长很多,大概是因为有很多后续的操作吧。这里使用JVM_ENTRY宏组织 native方法,可以看出内存的拷贝使用很直接的方法。也可以找到抛出异常的代码。

以clone()这个函数为例子,我们大概就可以找到以后如何查询和分析native代码的思路了。

Categories:

Updated:

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...