之前在分析ArrayList
和Vector
源码的时候,发现Sun JDK版本中的ArrayList
和Vector
大量使用了**System.**arraycopy
来操作数据,特别是同一数组内元素的移动及不同数组之间元素的复制。
在网上查到一些关于Java优化的资料里也推荐使用**System.**arraycopy
来批量处理数组,其本质就是让处理器利用一条指令处理一个数组中的多条记录,有点像汇编语言里面的串操作指令(LODSB
,LODSW
,LODSB
,STOSB
,STOSW
,STOSB
),只需指定头指针然后就开始循环即可,执行一次指令,指针就后移一个位置。要操作多少个数据就循环多少次即可。
从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
}
这里利用到了汇编指令,其中的lfd
,stfd
等指令就是本文开头所指的那类能过一个指令就可以批量处理多个数组数据的指令。
而conjoint_memory_atomic
函数中最后一个分支中调用的conjoint_jbytes
函数,其实就是用了C语言标准库中string.h
的memmove
函数:
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