Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
运行时数据区域
1、方法区:线程共享,存放类信息、常量、静态变量等数据。
2、堆:线程共享,对象实例在堆上分配内存。堆也是jvm进行垃圾回收的区域。
3、java虚拟机栈:线程私有,线程每调用一个方法便会创建一个栈帧入栈,方法运行结束对应着一个栈帧出栈。java虚拟机栈的栈帧存储了局部变量表、操作数栈、动态连接、方法出口等信息。局部变量表保存了编译期可知的基本类型、对象引用、returnAddress类型的数据。
4、本地方法栈:线程私有,作用基本对标java虚拟机栈,只是执行的方法类型不同。
5、程序计数器:线程私有,指示当前线程执行的字节码行号,线程恢复执行时可以根据程序计数器知道上次线程执行到了哪个位置。
内存分配安全性问题
为对象分配内存时可能会出现线程安全性问题,类比多个线程执行全局变量i++的情形,如果不进行任何的同步处理可能会导致两个对象分配到同一块内存区域,这是极大的安全隐患。
jvm针对这种情况提供了两种解决方案:
1、分配内存时基于CAS操作。
2、预先为每个线程分配一定的空间(TLAB,Thread Local Allocation Buffer),线程每次申请内存会先在自己的TLAB上进行分配,TLAB空间不够再采用同步的方式分配新的缓存区。
对象的内存布局
对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头
每个对象都有自己的对象头,也称Mark Word。在32位虚拟机中占32比特,64位虚拟机中占64比特。如果想要理解jvm的一些锁优化机制,如偏向锁、锁升级过程等。对象头的组成是有必要知道的。
以32bit为例,一般情况下,Mark Word存放了这些数据:
25bit:保存对象的hash码
4bit:保存对象的分代年龄
2bit:锁标志位
1bit:固定空位,存放一个0,将对象头填充至32bit。
随着锁标志位的变化,Mark Word前29bit存放的数据也会随之不同,所有情况如下:
实例数据
存储了对象真正的有效信息,即我们在程序代码里面所定义的各种类型的字段内容。
对齐填充
HotSpot虚拟机要求对象的起始地址必须是8字节的整数倍,换句话说,任何对象的大小都必须是8字节的整数倍。当对象大小不是8字节的整数倍时,那么需要进行填充。
以int型数据为例。int型数据占32bit,int a = 1;虽然在我们看来a其实一个bit就足够存放了,但还是需要在高位补充31个0才能使a变成一个有效的int值,这也算一种对齐填充。