system.arraycopy native源码解析

之前在分析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_**copy**64(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中的封装细节还不甚了解,在这里就不再详细分析了。

转载地址:http://www.360doc.com/content/14/0713/19/1073512_394157835.shtml

以上内容供参考了解

arraycopy本地源码解析的另一篇文章可以对比着看下---
地址:https://blog.csdn.net/u011642663/article/details/49512643

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 副标题Collection.toArray(new T[0]) 还是 Collection.toArray(new...
    余楚倩阅读 1,056评论 0 0
  • Java原子类中CAS的底层实现 - GoldArowana - 博客园 Java原子类中CAS的底层实现 从Ja...
    听一首老歌阅读 680评论 0 1
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,545评论 0 1
  • 1、Objective-C的类可以多重继承么?可以采用多个协议么? 答:不可以多重继承,可以采用多个协议。 2、#...
    Sang丶阅读 521评论 1 2
  • 1、Objective-C的类可以多重继承么?可以采用多个协议么?答:不可以多重继承,可以采用多个协议。2、#im...
    亦晴工作室阅读 622评论 0 3