对于基本类型,是直接进行值拷贝,也就是深拷贝。java
而对于对象类型,也是直接经过指针遍历赋值,并且也没有重写operator=操做符的,默认状况下是拷贝地址的,因此它们仍是指向同一块内存,这反应到 Java 层也是这样的。即所谓的“浅拷贝”。算法
前言
在 Java 编程中常常会遇到数组拷贝操做,通常会有以下四种方式对数组进行拷贝。
* for遍历,遍历源数组并将每一个元素赋给目标数组。
* clone方法,原数组调用clone方法克隆新对象赋给目标数组,更深刻的克隆能够看以前的文章《从JDK角度看对象克隆》。
* System.arraycopy,JVM 提供的数组拷贝实现。
* Arrays.copyof,实际也是调用System.arraycopy。编程
for遍历
这种状况下是在 Java 层编写 for 循环遍历数组每一个元素并进行拷贝,若是没有被编译器优化,它对应的就是遍历数组操做的字节码,执行引擎就根据这些字节码循环获取数组的每一个元素再执行拷贝操做。windows
arraycopy的使用
使用很简单,好比以下方式进行数组拷贝。数组
int size = 10000; int[] src = new int[size]; int[] des = new int[size]; System.arraycopy(src, 0, des, 0, size);
arraycopy方法
该方法用于从指定源数组中进行拷贝操做,能够指定开始位置,拷贝指定长度的元素到指定目标数组中。该方法是一个本地方法,声明以下:架构
@HotSpotIntrinsicCandidate public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
关于@HotSpotIntrinsicCandidate
这个注解是 HotSpot VM 标准的注解,被它标记的方法代表它为 HotSpot VM 的固有方法, HotSpot VM 会对其作一些加强处理以提升它的执行性能,好比可能手工编写汇编或手工编写编译器中间语言来替换该方法的实现。虽然这里被声明为 native 方法,可是它跟 JDK 中其余的本地方法实现地方不一样,固有方法会在 JVM 内部实现,而其余的会在 JDK 库中实现。在调用方面,因为直接调用 JVM 内部实现,不走常规 JNI lookup,因此也省了开销。app
本地arraycopy方法
Java 的 System 类有个静态块在类加载时会执行,它对应执行了 registerNatives 本地方法。函数
public final class System { private static native void registerNatives(); static { registerNatives(); } }
而在对应的 System.c 中的 Java_java_lang_System_registerNatives方法以下,能够看到有三个本地方法绑定到 JVM 的固有方法了,其中一个就是 arraycopy,它对应的函数为(void *)&JVM_ArrayCopy。oop
JNIEXPORT void JNICALL Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls) { (*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])); } #define OBJ "Ljava/lang/Object;" static JNINativeMethod methods[] = { {"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis}, {"nanoTime", "()J", (void *)&JVM_NanoTime}, {"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy}, };
那么经过以上就将arraycopy方法绑定到下面的JVM_ArrayCopy函数,前面的逻辑主要用于检查源数组和目标数组是否为空,为空则抛空指针;接着分别将源数组对象和目标数组对象转换成arrayOop,即数组对象描述,assert用于判断它们是否为对象;最后的s->klass()->copy_array才是真正的数组拷贝操做。性能
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 s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread); JVM_END
基本类型和普通类型
上面说到的经过s->klass()->copy_array完成拷贝操做,处理过程根据Java的不一样类型其实有不一样的处理,数组根据里面元素类型可分为基本类型和普通类型,对应到 JVM 分别为TypeArrayKlass和ObjArrayKlass。
TypeArrayKlass
这里将一些校验源码去掉,留下核心代码,这里由于涉及到内存中指针的移动,因此为了提升赋值操做的效率将起始结束位置转成char*,log2_element_size就是计算数组元素类型长度的log值,后面经过位移操做能快速计算位置。而array_header_in_bytes计算第一个元素的偏移。
void TypeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) { .... 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); }
接着到Copy::conjoint_memory_atomic函数,这个函数的主要逻辑就是判断元素属于哪一种基本类型,再调用各自的函数。由于已经有起始和结尾的指针,因此能够根据不一样类型进行快速的内存操做。这里以整型类型为例,将调用Copy::conjoint_jints_atomic函数。
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; 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_jints_atomic函数主要是调用pd_conjoint_jints_atomic函数,该函数在不一样的操做系统有本身的实现,这里看下windows_x86的实现,
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); }
主要逻辑是分红两种状况复制:向前复制和向后复制。而且是经过指针遍历数组来赋值,这里进行的是值拷贝,有些人称之为所谓的“深拷贝”。
static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) { if (from > to) { while (count-- > 0) { // Copy forwards *to++ = *from++; } } else { from += count - 1; to += count - 1; while (count-- > 0) { // Copy backwards *to-- = *from--; } } }
对于long、short、byte等类型也是作相似的处理,但在某些操做系统的某些cpu架构上会使用汇编来实现。
ObjArrayKlass
再看普通类型对象做为数组元素时候的拷贝操做,这里将一些校验源码去掉,留下核心代码。UseCompressedOops标识表示对 JVM 中Java对象指针压缩,主要表示用32位仍是64位做为对象指针。这里忽略它,直接看未压缩的状况,即会调用do_copy<oop>函数。
void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) { ... if (UseCompressedOops) { narrowOop* const src = objArrayOop(s)->obj_at_addr<narrowOop>(src_pos); narrowOop* const dst = objArrayOop(d)->obj_at_addr<narrowOop>(dst_pos); do_copy<narrowOop>(s, src, d, dst, length, CHECK); } else { oop* const src = objArrayOop(s)->obj_at_addr<oop>(src_pos); oop* const dst = objArrayOop(d)->obj_at_addr<oop>(dst_pos); do_copy<oop> (s, src, d, dst, length, CHECK); } }
这块代码较长,一样地,我去掉一部分代码,留下能说明问题的一小部分代码。这里会进行s==d的判断是由于源数组和目标数组多是相等的,而若是不相等的状况下则要判断源数组元素类型是否和目标数组元素类型同样,若是同样的话处理也作相似处理,另外这里还添加了是否为子类的判断。以上两种状况核心赋值算法都是Copy::conjoint_oops_atomic。
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 { 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 { ... } } bs->write_ref_array((HeapWord*)dst, length); }
该函数也跟操做系统和cpu架构相关,这里看windows_x86的实现,很简单也是直接经过指针遍历赋值,oop是JVM层的对象类,并且该类也没有重写operator=操做符的,默认状况下是拷贝地址的,因此它们仍是指向同一块内存,这反应到 Java 层也是这样的。即所谓的“浅拷贝”。
static void conjoint_oops_atomic(oop* from, oop* to, size_t count) { pd_conjoint_oops_atomic(from, to, count); } static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) { if (from > to) { while (count-- > 0) { *to++ = *from++; } } else { from += count - 1; to += count - 1; while (count-- > 0) { // Copy backwards *to-- = *from--; } } }
总结 System.arraycopy为 JVM 内部固有方法,它经过手工编写汇编或其余优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大致现地越明显。