I'm using Ubuntu 14 64 bit, Intel Core i5.
I wrote the following simple program to understand how object allocation works in Java:
public class App {
public static void main(String[] args) {
for(int i = 0; i < Integer.MAX_VALUE; i++)
testObjectCreationCompiled();
}
public static void testObjectCreationCompiled() {
Object obj = new Object();
if (obj.hashCode() == System.nanoTime()) {
System.out.print("");
}
}
}
I run this program as follows:
java -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*App.testObjectCreationCompiled -server -jar target/test-1.0.0.jar
After looking at the compiled code I was wondering how the object allocation happened here (fragment of compiled code):
0x00007f60651165e4: mov %r12d,0xc(%rsi) ;*new ; - com.test.App::testObjectCreationCompiled@0 (line 13)
0x00007f60651165e8: mov (%rsi),%r10
0x00007f60651165eb: mov %r10,%r11
0x00007f60651165ee: and $0x7,%r11
0x00007f60651165f2: cmp $0x1,%r11
0x00007f60651165f6: jne 0x7f606511662a
0x00007f60651165f8: shr $0x8,%r10
0x00007f60651165fc: mov %r10d,%ebp
0x00007f60651165ff: and $0x7fffffff,%ebp
0x00007f6065116605: test %ebp,%ebp
0x00007f6065116607: je 0x7f606511662a ;*invokevirtual hashCode
; - com.test.App::testObjectCreationCompiled@9 (line 14)
0x00007f6065116609: movabs $0x7f6079d5d440,%r10
0x00007f6065116613: callq %r10 ;*invokestatic nanoTime
; - com.test.App::testObjectCreationCompiled@13 (line 14)
0x00007f6065116616: movsxd %ebp,%r10 ;*i2l ; - com.test.App::testObjectCreationCompiled@12 (line 14)
0x00007f6065116619: cmp %rax,%r10
0x00007f606511661c: je 0x7f6065116649 ;*ifne
; - com.test.App::testObjectCreationCompiled@17 (line 14)
0x00007f606511661e: add $0x10,%rsp
0x00007f6065116622: pop %rbp
0x00007f6065116623: test %eax,0x15f3d9d7(%rip) ; {poll_return}
0x00007f6065116629: retq
0x00007f606511662a: nop
0x00007f606511662b: callq 0x7f6065046020 ; OopMap{off=144}
;*invokevirtual hashCode
; - com.test.App::testObjectCreationCompiled@9 (line 14)
; {optimized virtual_call}
0x00007f6065116630: mov %eax,%ebp
0x00007f6065116632: jmp 0x7f6065116609
0x00007f6065116634: movabs $0x100000f28,%rsi ; {metadata('java/lang/Object')}
0x00007f606511663e: nop
0x00007f606511663f: callq 0x7f6065100fa0 ; OopMap{off=164}
;*new ; - com.test.App::testObjectCreationCompiled@0 (line 13)
; {runtime_call}
Here is the new Object()
is marked as just mov %r12d,0xc(%rsi)
.
It looks like the memory was already allocated at this point with the address at r12d
.
The question is why do we mov
the address to [rsi+0xc]
memory location.
But as far as I know to allocate some memory in linux we have to perform sys_brk
syscall. What we do here is just simple mov
instruction, I dont see any syscall
here ever. Why simple mov
means new Object()
?
How does object allocation work in JVM?
The given fragment is incomplete. The actual allocation code should be right above this fragment.
mov %r12d,0xc(%rsi)
is the last instruction of allocation sequence - it just zeroes the last padding word of the new object.
I've already decribed how object allocation works in HotSpot in this, this and this answers. You won't see any syscalls there since JVM does not rely on system allocator. It uses its own memory management in the preallocated region - Java Heap.
Here is how allocation sequence typically looks like in C2-compiled code. The comments are mine.
mov 0x60(%r15),%rdx ; obj = currentThread.tlab_top
mov %rdx,%r10
add $0x10,%r10 ; r10 = obj + sizeof(java/lang/Object)
cmp 0x70(%r15),%r10 ; if (r10 >= currentThread.tlab_end)
jae 0x00000000030ad2f4 ; goto slow_case
mov %r10,0x60(%r15) ; currentThread.tlab_top = r10
prefetchnta 0xc0(%r10) ; prefetch memory next to tlab_top into CPU caches
; to make subsequent allocations faster
mov $0x200001e5,%r10d ; r10 = VMKlass of java/lang/Object
shl $0x3,%r10
mov 0xa8(%r10),%r10 ; r10 = Header prototype for java/lang/Object
mov %r10,(%rdx) ; obj[0] = r10 (header prototype)
movl $0x200001e5,0x8(%rdx) ; obj[8] = VMKlass of java/lang/Object
mov %r12d,0xc(%rdx) ; obj[12] = 0 (padding to 8-byte boundary)