打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
System.arraycopy源码分析 | 勇点

之前在分析ArrayListVector源码的时候,发现Sun JDK版本中的ArrayListVector大量使用了System.arraycopy来操作数据,特别是同一数组内元素的移动及不同数组之间元素的复制。

在网上查到一些关于Java优化的资料里也推荐使用System.arraycopy来批量处理数组,其本质就是让处理器利用一条指令处理一个数组中的多条记录,有点像汇编语言里面的串操作指令(LODSBLODSWLODSBSTOSBSTOSWSTOSB),只需指定头指针然后就开始循环即可,执行一次指令,指针就后移一个位置。要操作多少个数据就循环多少次即可。

java.lang.System类的源码可见:

    public static native void arraycopy(Object src,  int  srcPos,                                        Object dest, int destPos,                                        int length);

arraycopy方法是一个本地方法。

在OpenJDK源码包中可以找到openjdk6-src/hotspot/src/share/vm/prims/jvm.cpp文件,其中的JVM_ArrayCopy函数入口是:

JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,                               jobject dst, jint dst_pos, jint length))  JVMWrapper("JVM_ArrayCopy");  // Check if we have null pointers  if (src == NULL || dst == NULL) {    THROW(vmSymbols::java_lang_NullPointerException());  }  arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));  arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));  assert(s->is_oop(), "JVM_ArrayCopy: src not an oop");  assert(d->is_oop(), "JVM_ArrayCopy: dst not an oop");  // Do copy  Klass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, thread);JVM_END

前面的一大段代码都是是用于验证参数的。只有最后一句调用copy_array函数才是真正处理数组复制的操作。而copy_array有两个版本,一个是针对类型数组的,一个是针对对象数组的。这里还是不是很理解类型数组和对象数组的区别,不过从两个版本的copy_array函数的具体代码看,类型数组应该是指Java的基本类型数组,对象数组就应该是除了基本类型之外的对象组成的数组。

openjdk6-src/hotspot/src/share/vm/oops/typeArrayKlass.cpp文件中找到的copy_array函数:

void typeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) {  assert(s->is_typeArray(), "must be type array");  // Check destination  if (!d->is_typeArray() || element_type() != typeArrayKlass::cast(d->klass())->element_type()) {    THROW(vmSymbols::java_lang_ArrayStoreException());  }  // Check is all offsets and lengths are non negative  if (src_pos < 0 || dst_pos < 0 || length < 0) {    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());  }  // Check if the ranges are valid  if  ( (((unsigned int) length + (unsigned int) src_pos) > (unsigned int) s->length())     || (((unsigned int) length + (unsigned int) dst_pos) > (unsigned int) d->length()) ) {    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());  }  // Check zero copy  if (length == 0)    return;  // This is an attempt to make the copy_array fast.  int l2es = log2_element_size();  int ihs = array_header_in_bytes() / wordSize;  char* src = (char*) ((oop*)s + ihs) + ((size_t)src_pos << l2es);  char* dst = (char*) ((oop*)d + ihs) + ((size_t)dst_pos << l2es);  Copy::conjoint_memory_atomic(src, dst, (size_t)length << l2es);}

前面的一大段还是在验证参数的正确性,不正确就抛出相应的异常。当最后5行代码便是先对数组进行转型,然后调用conjoint_memory_atomic函数,这才真正开始数组元素的操作。

conjoint_memory_atomic函数在openjdk6-src/hotspot/src/share/vm/utilities/copy.cpp文件中:

// Copy bytes; larger units are filled atomically if everything is aligned.void Copy::conjoint_memory_atomic(void* from, void* to, size_t size) {  address src = (address) from;  address dst = (address) to;  uintptr_t bits = (uintptr_t) src | (uintptr_t) dst | (uintptr_t) size;  // (Note:  We could improve performance by ignoring the low bits of size,  // and putting a short cleanup loop after each bulk copy loop.  // There are plenty of other ways to make this faster also,  // and it's a slippery slope.  For now, let's keep this code simple  // since the simplicity helps clarify the atomicity semantics of  // this operation.  There are also CPU-specific assembly versions  // which may or may not want to include such optimizations.)  if (bits % sizeof(jlong) == 0) {    Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof(jlong));  } else if (bits % sizeof(jint) == 0) {    Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof(jint));  } else if (bits % sizeof(jshort) == 0) {    Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof(jshort));  } else {    // Not aligned, so no need to be atomic.    Copy::conjoint_jbytes((void*) src, (void*) dst, size);  }}

conjoint_memory_atomic函数会根据所操作的数据所属的类型选择合适的操作方法,各个操作方法都很相似,这里就看看conjoint_jints_atomic函数的实现。首先在openjdk6-src/hotspot/src/share/vm/utilities/copy.hpp文件中可以找到:

  // jints,                 conjoint, atomic on each jint  static void conjoint_jints_atomic(jint* from, jint* to, size_t count) {    assert_params_ok(from, to, LogBytesPerInt);    pd_conjoint_jints_atomic(from, to, count);  }

继续查找pd_conjoint_jints_atomic函数,在openjdk6-src/hotspot/src/cpu/zero/vm/copy_zero.hpp中:

static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) {  _Copy_conjoint_jints_atomic(from, to, count);}

再找到openjdk6-src/hotspot/src/os_cpu/linux_zero/vm/os_linux_zero.cpp文件:

  void _Copy_conjoint_jints_atomic(jint* from, jint* to, size_t count) {    if (from > to) {      jint *end = from + count;      while (from < end)        *(to++) = *(from++);    }    else if (from < to) {      jint *end = from;      from += count - 1;      to   += count - 1;      while (from >= end)        *(to--) = *(from--);    }  }

找到这里,_Copy_conjoint_jints_atomic函数就是一个很经典的内存块处理代码了。

而在同一个文件中可以找到_Copy_conjoint_jlongs_atomic函数:

  void _Copy_conjoint_jlongs_atomic(jlong* from, jlong* to, size_t count) {    if (from > to) {      jlong *end = from + count;      while (from < end)        os::atomic_copy64(from++, to++);    }    else if (from < to) {      jlong *end = from;      from += count - 1;      to   += count - 1;      while (from >= end)        os::atomic_copy64(from--, to--);    }  }

这个函数和上面的差不多,只是将*(to++) = *(from++)换成了os::atomic_copy64(from++, to++)这个处理是为了适应64位数据的。这个函数可以在同一目录下的os_linux_zero.hpp文件中找到:

  // Atomically copy 64 bits of data  static void atomic_copy64(volatile void *src, volatile void *dst) {#if defined(PPC) && !defined(_LP64)    double tmp;    asm volatile ("lfd  %0, 0(%1)n"                  "stfd %0, 0(%2)n"                  : "=f"(tmp)                  : "b"(src), "b"(dst));#elif defined(S390) && !defined(_LP64)    double tmp;    asm volatile ("ld  %0, 0(%1)n"                  "std %0, 0(%2)n"                  : "=r"(tmp)                  : "a"(src), "a"(dst));#else    *(jlong *) dst = *(jlong *) src;#endif  }

这里利用到了汇编指令,其中的lfdstfd等指令就是本文开头所指的那类能过一个指令就可以批量处理多个数组数据的指令。

conjoint_memory_atomic函数中最后一个分支中调用的conjoint_jbytes函数,其实就是用了C语言标准库中string.hmemmove函数:

static void pd_conjoint_bytes(void* from, void* to, size_t count) {  memmove(to, from, count);}

而另一个版本的copy_array函数在openjdk6-src/hotspot/src/share/vm/oops/objArrayKlass.cpp文件中找到,其中的原理与上述的有点不同。由于本人对Java对象在JVM中的封装细节还不甚了解,在这里就不再详细分析了。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Arrays.copyof(···)与System.arraycopy(···)区别
[Java] arraycopy 数组复制
C# 内存操作常用函数
python shutil模块详解
JAVA中复制数组的方法
System提供的静态方法arraycopy()
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服