對於基本類型,是直接進行值拷貝,也就是深拷貝。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 是更加高效的。數組越大致現地越明顯。