preface
In Java programming, arrays are often copied. There are generally four ways to copy arrays.
for
Traversal, traversing the source array and assigning each element to the target array.clone
Method, the original array callclone
Method to clone a new object and assign it to the target array. For further cloning, see the previous articleObject cloning from a JDK perspective”.System.arraycopy
JVM provides array copy implementation.Arrays.copyof
, is actually calledSystem.arraycopy
.
For traversal
In this case, a for loop is written in the Java layer to iterate over and copy each element of the array. If not optimized by the compiler, it corresponds to the bytecode of the array operation, and the execution engine uses this bytecode to loop over each element of the array and execute the copy operation.
The use of arraycopy
It is very simple to use, such as the following way to copy the array.
int size = 10000;
int[] src = new int[size];
int[] des = new int[size];
System.arraycopy(src, 0, des, 0, size);
Copy the code
Arraycopy method
This method is used to copy an element of the specified length from a specified source array to a specified destination array. This method is a local method declared as follows:
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
Copy the code
About @ HotSpotIntrinsicCandidate
This annotation is a standard HotSpot VM annotation. The method marked by it indicates that it is native to HotSpot VM, and HotSpot VM does enhancements to it to improve its performance, such as perhaps manually writing an assembly or manually writing a compiler intermediate language to replace the implementation of the method. Although this is declared as a native method, it differs from other native methods implemented in the JDK in that the native methods are implemented within the JVM, while others are implemented in JDK libraries. On the invocation side, you also save overhead by calling the internal JVM implementation directly instead of going through a regular JNI Lookup.
Local ArrayCopy methods
Java’s System class has a static block that executes on class loading, corresponding to the registerNatives local method.
public final class System { private static native void registerNatives(); static { registerNatives(); }}Copy the code
In the corresponding system. c Java_java_lang_System_registerNatives method, you can see that there are three native methods bound to the JVM, one of which is ArrayCopy. Its corresponding function is (void *) &jVM_arrayCopy.
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},
};
Copy the code
JVM_ArrayCopy = JVM_ArrayCopy = JVM_ArrayCopy = JVM_ArrayCopy = JVM_ArrayCopy Then we convert the source array object and the target array object to arrayOop, which is an array object description. Assert is used to determine whether they are objects. The final s->klass()->copy_array is the true array copy operation.
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
Copy the code
Basic and common types
S ->klass()->copy_array (s->klass()->copy_array) The corresponding JVMS are TypeArrayKlass and ObjArrayKlass respectively.
TypeArrayKlass
In order to improve the efficiency of the assignment operation, we change the start and end positions to char*. Log2_element_size is the log value that calculates the length of the array element type. After that, we can quickly calculate the position through displacement. Array_header_in_bytes calculates the offset of the first element.
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 the code
Next comes the Copy:: conjoinT_memory_atomic function, whose main logic is to determine which primitive type the element belongs to and then call the respective function. Because there are already starting and ending Pointers, you can do fast memory manipulation depending on the type. In this example of an integer type, the Copy:: conjoinT_jints_atomic function is called.
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); }}Copy the code
The conjoint_jints_atomic function mainly calls the pd_conjoint_jints_atomic function. This function has its own implementation in different operating systems. Here is the implementation of 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);
}
Copy the code
The main logic is that there are two types of replication: forward and backward. And the assignment is done by traversing the array with a pointer, doing what some people call “deep copying.”
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--; }}}Copy the code
Similar processing is done for long, short, byte, and so on, but assembly is used on some CPU architectures on some operating systems.
ObjArrayKlass
Looking at the copying of objects of ordinary type as array elements, here we remove some of the verification source code, leaving the core code. The UseCompressedOops flag represents compression of Java object Pointers in the JVM, primarily indicating whether to use 32-bit or 64-bit as object Pointers. Ignore it here and go straight to the uncompressed case, where the do_copy< OOP > function is called.
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); }}Copy the code
This is a longer piece of code, and again, I cut out some of the code, leaving a little bit of code that makes sense. The reason why s==d is determined here is that the source array and the target array may be equal, and if they are not, it is necessary to determine whether the element type of the source array is the same as the element type of the target array. If so, similar processing is also done. In addition, the judgment whether the source array is a subclass is added here. In both cases the core assignment algorithm is 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); }Copy the code
Oop is an object class for the JVM layer, and it does not override the operator= operator. By default, it copies the address, so they still point to the same block of memory. This is also reflected in the Java layer. This is known as “shallow copy”.
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--; }}}Copy the code
conclusion
System.arraycopy is a method built into the JVM that makes a copy of Java arrays by hand-writing assembly or other optimizations more efficient than doing a for loop or clone directly in Java. The larger the array, the more obvious it is.
————- Recommended reading ————
My 2017 article summary – Machine learning
My 2017 article summary – Java and Middleware
My 2017 article summary – Deep learning
My 2017 article summary — JDK source code article
My 2017 article summary – Natural Language Processing
My 2017 Article Round-up — Java Concurrent Article
—————— advertising time —————-
The public menu has been divided into “distributed”, “machine learning”, “deep learning”, “NLP”, “Java depth”, “Java concurrent core”, “JDK source”, “Tomcat kernel” and so on, there may be a suitable for your appetite.
My new book “Analysis of Tomcat kernel Design” has been sold on JINGdong, friends in need can buy. Thank you all.
Why to write “Analysis of Tomcat Kernel Design”
Welcome to: