Java system.arraycopy源码阅读

作者: Mhhhhhhy | 来源:发表于2020-03-01 17:41 被阅读0次

实验代码

public class ArrayCopy {
    private static void printArray(Object[] arr) {
        for (Object obj : arr) {
            System.out.println(obj.toString());
        }
    }
    private static class User {
        String name;
        public User(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "name: " + name;
        }
    }
    
    public static void main(String[] args) {
        int[] intvalues = new int[]{1000, 2000, 3000, 4000, 5000, 6000};
        int[] intcopy = new int[intvalues.length];
        System.arraycopy(intvalues, 0, intcopy, 0, intvalues.length);
        intvalues[0] = 400;
        System.out.println("intcopy:" + Arrays.toString(intcopy));

        Integer[] values = new Integer[]{1000, 2000, 3000, 4000};
        Integer[] copy = new Integer[values.length];
        System.arraycopy(values, 0, copy, 0, values.length);

        for (int i = 0; i < values.length; i++) {
            System.out.printf("values == copy : %s\n", values[i] == copy[I]);
        }

        values[0] = new Integer(990);

        System.out.println("values:" + Arrays.toString(values));
        System.out.println("copy:" + Arrays.toString(copy));

        User[] array = { new User("aa"), new User("bb") };
        User[] targetArr = new User[array.length];
        System.arraycopy(array, 0, targetArr, 0, targetArr.length);
        printArray(targetArr);
        array[0].name = "dd";
        printArray(targetArr);
        array[0] = new User("mm");
        printArray(targetArr);
    }
}

上面代码输出的结果如下

//int value array
intcopy:[1000, 2000, 3000, 4000, 5000, 6000]
//Integer array
values == copy : true
values == copy : true
values == copy : true
values == copy : true
values:[990, 2000, 3000, 4000]
copy:[1000, 2000, 3000, 4000]
//User array
name: aa
name: bb
name: dd
name: bb
name: dd
name: bb

调试过程

使用Xcode来调试jdk8,先关注原生类型int的调试过程

栈一

红框内会将src、dst等java对象转换为jvm中的oop对象,这里主要是typeArrayOopobjArrayOop

栈二

红色区域判断抛异常,栈二中走到了typeArrayClass.cpp文件中的copy_array方法

栈三

这里有几个分支,不同的类型走不同的处理方式

栈五

栈四省略了,就是一个简单的断言加逻辑跳转

栈六

到这里开始就是汇编代码了,于是乎在hotspot代码中搜索函数_Copy_conjoint_jshorts_atomic

栈七

找到不同平台的实现,继续向下看

找到了核心代码,如图所示

根据调用栈来判断,int、short等基本类型的复制在jvm中的处理类是TypeArrayKlass,我们可以在hotspot/src/share/vm/oops里面找到

我们可以在TypeArrayKlass.hpp文件中找到相关注释,typeArrayOop就是用于基本类型的数组描述;

众所周知,声明数组时int[] values = new int[]{1000, 2000, 3000, 4000},会在java栈中创建个引用对象values,指向堆中的数组对象;

当调用System.arraycopy,核心的代码类似于下面这段函数;

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

因为数组在内存中是顺序排列的,所以通过from++移动偏移指针来获取数组中的数据,在c语言中数组的头元素可以用from表示,而数组中第二个元素可以用from+2表示;
可以看到,*(to++) = *(from++);或者*(to--) = *(from--);的含义就是将from数组的每个元素所指向的地址(对象)分别赋值给to;

所以说当我们执行values[0] = 900;,是将values[0]指针指向的值设置为900,对于基本类型,不存在任何引用关系和所谓的深浅拷贝;

所以会输出

values:[990, 2000, 3000, 4000]
copy:[1000, 2000, 3000, 4000]

继续回到源码中可以看到_Copy_conjoint_jlongs_atomic方法处理和处理复制short和int类型有些不一样;

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

是通过atomic_copy64方法进行copy的,我们知道32位的java虚拟机(这里指hotspot)处理long、double这种占64位(8字节)的类型时,并不能保证是原子性的,这里也是一个体现;

  // Atomically copy 64 bits of data
  static void atomic_copy64(volatile void *src, volatile void *dst) {
#if defined(PPC32)
    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
  }

找到atomic_copy64的源码,通常而言现在我们都是使用64位的虚拟机,所以复制的操作应该是这行代码*(jlong *) dst = *(jlong *) src,同样是指针赋值;

// bytes,       conjoint, atomic on each byte (not that it matters)
static void conjoint_jbytes_atomic(void* from, void* to, size_t count) {
    pd_conjoint_bytes(from, to, count);
}

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

对于bytes类型的复制则是调用系统函数memmove,实现其实和上面的差不多;

对于引用类型比如UserInteger等,实现方法则是在ObjArrayKlass类中的copy_array方法中,可以观察其函数栈;

栈二 栈三

把核心复制操作do_copy的代码贴出来

//oop或narrowoop取决于我们的压缩操作。
template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,
                               arrayOop d, T* dst, int length, TRAPS) {

  BarrierSet* bs = Universe::heap()->barrier_set();
  //... 删掉断言
  if (s == d) {
    // 如果源地址和目标地址是相等的,我们不需要转换检查。
    bs->write_ref_array_pre(dst, length);
    Copy::conjoint_oops_atomic(src, dst, length);
  } else {
    // 这里必须确保src和dst内的元素是一致的
    Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();
    Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();
    if (stype == bound || stype->is_subtype_of(bound)) {
      // 元素保证是其子类型(包括本类),因此不需要进行检查
      bs->write_ref_array_pre(dst, length);
      Copy::conjoint_oops_atomic(src, dst, length);
    } else {
      // 类型不一致,需要单独检查子类型
      T* from = src;
      T* end = from + length;
      for (T* p = dst; from < end; from++, p++) {
        // 慢操作
        T element = *from;
        bool element_is_null = oopDesc::is_null(element);
        oop new_val = element_is_null ? oop(NULL)
                                      : oopDesc::decode_heap_oop_not_null(element);
        if (element_is_null ||
            (new_val->klass())->is_subtype_of(bound)) {
          bs->write_ref_field_pre(p, new_val);
          *p = element;
        } else {
          const size_t pd = pointer_delta(p, dst, (size_t)heapOopSize);
          assert(pd == (size_t)(int)pd, "length field overflow");
          bs->write_ref_array((HeapWord*)dst, pd);
          THROW(vmSymbols::java_lang_ArrayStoreException());
          return;
        }
      }
    }
  }
  bs->write_ref_array((HeapWord*)dst, length);
}

可以看到,对于引用类型的arraycopy操作代码还是有点多的,需要检查源数组和目标数组的类型是否一致,如果不一致,进行copy操作是比较耗时的;

在类型一致的情况下,走的是以下流程

static void conjoint_oops_atomic(oop* from, oop* to, size_t count) {
  assert_params_ok(from, to, LogBytesPerHeapOop);
  pd_conjoint_oops_atomic(from, to, count);
}

static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) {
#ifdef AMD64
  assert(BytesPerLong == BytesPerOop, "jlongs and oops must be the same size");
  _Copy_conjoint_jlongs_atomic((jlong*)from, (jlong*)to, count);
#else
  assert(HeapWordSize == BytesPerOop, "heapwords and oops must be the same size");
  // pd_conjoint_words is word-atomic in this implementation.
  pd_conjoint_words((HeapWord*)from, (HeapWord*)to, count);
#endif // AMD64
}

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

static void pd_conjoint_words(HeapWord* from, HeapWord* to, size_t count) {
  memmove(to, from, count * HeapWordSize);
}

最后执行的函数是和基本类型一样,属于指针赋值;

所以如果数组中保存的对象是User对象,User对象内有个String类型的字段name,那么当我们执行array[0].name = "dd";时,是将User(name:aa)对象中name指向的常量改成了dd

所以打印values和copy时都能看到dd字符;当执行array[0] = new User("mm");时和上面Integer类似,是将values[0]指向新建的User对象,copy是不会受到影响的所以打印输出不变;对于引用类型,数组内的对象是浅拷贝;

结论

System.arraycopy在JDK里面大量的使用,jvm肯定是会对其进行专门的优化,性能方面肯定是好过对数组进行for循环然后分别clone(),对于基本类型,不存在深浅拷贝,但是对于引用类型,源码中体现了对数组内的元素拷贝属于浅拷贝(指针赋值,所以在执行==比较地址时是相等的);

参考

https://blog.csdn.net/wangyangzhizhou/article/details/79504818

相关文章

网友评论

    本文标题:Java system.arraycopy源码阅读

    本文链接:https://www.haomeiwen.com/subject/yavlhhtx.html