03-VIP-JVM对象创建与内存分配机制深度剖析.pdf
对象创建的主要流程
解决划分内存的并发问题的方法-
1、CAS+失败重试:虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。(大家都去抢这个内存,没抢到的就去抢下一个位置的内存空间)
2、本地线程分配缓存区
TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。
通过-XX:+/UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启XX:+UseTLAB),XX:TLABSize 指定TLAB大小。
设置对象头:
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。
Mark Word标记字段:
Kiass Pointer类型指针:一个对象new出来放在堆里面,在对象头部区域有一个指针指向我们在方法区对象所属的那个类信息(下图的compute方法通过类型指针找到上面的类信息)

mathClass指类对象,存储在堆里面
数组对象内部结构(32位):
有参对象内部结构(32位):
执行
为属性赋值(注意,这与上面的初始化步骤中的赋零值不同,这是由程序员赋的值),和执行构造方法。
什么是java对象的指针压缩:
1.jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩
2.jvm配置参数:UseCompressedOops,compressed压缩、oop(ordinary object pointer)对象指针
3.启用指针压缩:XX:+UseCompressedOops(默认开启),禁止指针压缩:XX:UseCompressedOops
为什么要进行指针压缩?
1.在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据, 占用较大宽带,同时GC也会承受较大压力
2.为了减少64位平台下内存的消耗,启用指针压缩功能
3.在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm 只用32位地址就可以支持更大的内存配置(小于等于32G)
4.堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
5.堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现通过指针压缩原本只需要用4个字节存储的对象,现在要用8个字节去进行存储导致1的问题(占用较大宽带,同时GC也会承受较大压力 ),所以堆内存不要大于32G为好(堆内存不是越大越好)
‐XX:+UseCompressedOops 默认开启的压缩所有指针
‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer
对象内存分配
对象逃逸分析
就是分析对象动态作用域,下图test1方法中的User对象会被外部使用,即对象动态作用域不确定;test2方法中的user对象我们可以确定当方法结束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内存一起被回收掉。
JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)
标量替换:通过系统分析,对象不被外部所访问,可以考虑放在栈帧里面;栈帧通常都很小,有可能放不下这个对象 ,但还有一些碎片化空间,也就是说栈帧剩余的总大小是超过这个对象的,但是没有一整块连续的空间容纳这个对象。针对于这总情况标量替换就是在此基础上提起的优化方案,即使没有一整块连续的空间来容纳这个对象,当然这里的容纳这个对象并不是new 对象出来,只是在系统判断此对象可以通过对象逃逸分析确认此对象不逃逸,则此时不是真正new一个对象放进去,而是可以将此对象的成员变量放进去,有几个成员变量(静态变量不存这里)把他拆开,分开存,此时,栈帧有很多不连续的内存碎片,只存储对象被拆分的成员变量,不放整个对象。总结:此对象可以通过对象逃逸分析确认此对象不逃逸,而栈帧又存不下,则可以将对象拆分出来的成员变量(都有此对象的标识),存进栈帧(那些不连续的碎片空间),此为标量替换。
标量与聚合量:标量即不可以被进一步分解的量,比如JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及reference类型等);聚合量即对立面,可以被进一步分解的量,比如对象。
对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的。
老年代空间分配担保机制
年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间
如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象),有可能全部都不是辣鸡对象
就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了
如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。
如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生”OOM”当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”

