之前在分析ArrayList和Vector源码的时候,发现Sun JDK版本中的ArrayList和Vector大量使用了System.arraycopy来操作数据,特别是同一数组内元素的移动及不同数组之间元素的复制。
在网上查到一些关于Java优化的资料里也推荐使用System.arraycopy来批量处理数组,其本质就是让处理器利用一条指令处理一个数组中的多条记录,有点像汇编语言里面的串操作指令(LODSB,LODSW,LODSB,STOSB,STOSW,STOSB),只需指定头指针然后就开始循环即可,执行一次指令,指针就后移一个位置。要操作多少个数据就循环多少次即可。
从java.lang.System类的源码可见:
1 | public static native void arraycopy(Object src, int srcPos, |
2 | Object dest, int destPos, |
arraycopy方法是一个本地方法。
在OpenJDK源码包中可以找到“openjdk6-src\hotspot\src\share\vm\prims\jvm.cpp”文件,其中的“JVM_ArrayCopy”函数入口是:
01 | JVM_ENTRY( void , JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos, |
02 | jobject dst, jint dst_pos, jint length)) |
03 | JVMWrapper( "JVM_ArrayCopy" ); |
05 | if (src == NULL || dst == NULL) { |
06 | THROW(vmSymbols::java_lang_NullPointerException()); |
08 | arrayOop s = arrayOop(JNIHandles::resolve_non_null(src)); |
09 | arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst)); |
10 | assert (s->is_oop(), "JVM_ArrayCopy: src not an oop" ); |
11 | assert (d->is_oop(), "JVM_ArrayCopy: dst not an oop" ); |
13 | Klass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, thread ); |
前面的一大段代码都是是用于验证参数的。只有最后一句调用copy_array函数才是真正处理数组复制的操作。而copy_array有两个版本,一个是针对类型数组的,一个是针对对象数组的。这里还是不是很理解类型数组和对象数组的区别,不过从两个版本的copy_array函数的具体代码看,类型数组应该是指Java的基本类型数组,对象数组就应该是除了基本类型之外的对象组成的数组。
在“openjdk6-src\hotspot\src\share\vm\oops\typeArrayKlass.cpp”文件中找到的copy_array函数:
01 | void typeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) { |
02 | assert (s->is_typeArray(), "must be type array" ); |
05 | if (!d->is_typeArray() || element_type() != typeArrayKlass::cast(d->klass())->element_type()) { |
06 | THROW(vmSymbols::java_lang_ArrayStoreException()); |
10 | if (src_pos < 0 || dst_pos < 0 || length < 0) { |
11 | THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); |
14 | if ( (((unsigned int ) length + (unsigned int ) src_pos) > (unsigned int ) s->length()) |
15 | || (((unsigned int ) length + (unsigned int ) dst_pos) > (unsigned int ) d->length()) ) { |
16 | THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); |
23 | int l2es = log2_element_size(); |
24 | int ihs = array_header_in_bytes() / wordSize; |
25 | char * src = ( char *) ((oop*)s + ihs) + (( size_t )src_pos << l2es); |
26 | char * dst = ( char *) ((oop*)d + ihs) + (( size_t )dst_pos << l2es); |
27 | 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”文件中:
02 | void Copy::conjoint_memory_atomic( void * from, void * to, size_t size) { |
03 | address src = (address) from; |
04 | address dst = (address) to; |
05 | uintptr_t bits = ( uintptr_t ) src | ( uintptr_t ) dst | ( uintptr_t ) size; |
15 | if (bits % sizeof (jlong) == 0) { |
16 | Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof (jlong)); |
17 | } else if (bits % sizeof (jint) == 0) { |
18 | Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof (jint)); |
19 | } else if (bits % sizeof (jshort) == 0) { |
20 | Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof (jshort)); |
23 | Copy::conjoint_jbytes(( void *) src, ( void *) dst, size); |
conjoint_memory_atomic函数会根据所操作的数据所属的类型选择合适的操作方法,各个操作方法都很相似,这里就看看conjoint_jints_atomic函数的实现。首先在“openjdk6-src\hotspot\src\share\vm\utilities\copy.hpp”文件中可以找到:
2 | static void conjoint_jints_atomic(jint* from, jint* to, size_t count) { |
3 | assert_params_ok(from, to, LogBytesPerInt); |
4 | pd_conjoint_jints_atomic(from, to, count); |
继续查找pd_conjoint_jints_atomic函数,在“openjdk6-src\hotspot\src\cpu\zero\vm\copy_zero.hpp”中:
1 | static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) { |
2 | _Copy_conjoint_jints_atomic(from, to, count); |
再找到“openjdk6-src\hotspot\src\os_cpu\linux_zero\vm\os_linux_zero.cpp”文件:
01 | void _Copy_conjoint_jints_atomic(jint* from, jint* to, size_t count) { |
03 | jint *end = from + count; |
找到这里,_Copy_conjoint_jints_atomic函数就是一个很经典的内存块处理代码了。
而在同一个文件中可以找到_Copy_conjoint_jlongs_atomic函数:
01 | void _Copy_conjoint_jlongs_atomic(jlong* from, jlong* to, size_t count) { |
03 | jlong *end = from + count; |
05 | os::atomic_copy64(from++, to++); |
12 | os::atomic_copy64(from--, to--); |
这个函数和上面的差不多,只是将“*(to++) = *(from++)”换成了“os::atomic_copy64(from++, to++)”这个处理是为了适应64位数据的。这个函数可以在同一目录下的“os_linux_zero.hpp”文件中找到:
02 | static void atomic_copy64( volatile void *src, volatile void *dst) { |
03 | #if defined(PPC) && !defined(_LP64) |
05 | asm volatile ( "lfd %0, 0(%1)\n" |
08 | : "b" (src), "b" (dst)); |
09 | #elif defined(S390) && !defined(_LP64) |
11 | asm volatile ( "ld %0, 0(%1)\n" |
14 | : "a" (src), "a" (dst)); |
16 | *(jlong *) dst = *(jlong *) src; |
这里利用到了汇编指令,其中的lfd,stfd等指令就是本文开头所指的那类能过一个指令就可以批量处理多个数组数据的指令。
而conjoint_memory_atomic函数中最后一个分支中调用的conjoint_jbytes函数,其实就是用了C语言标准库中“string.h”的memmove函数:
1 | static void pd_conjoint_bytes( void * from, void * to, size_t count) { |
2 | memmove (to, from, count); |
而另一个版本的copy_array函数在“openjdk6-src\hotspot\src\share\vm\oops\objArrayKlass.cpp”文件中找到,其中的原理与上述的有点不同。由于本人对Java对象在JVM中的封装细节还不甚了解,在这里就不再详细分析了。