内存分配
过程
1、当执行new指令时,首先会检查new指令后的类是否已被加载,否则会触发加载动作。
2、类检测没问题后,开始分配内存,由于堆内有不同的区域,所以分配方式也会不同。
a.指针碰撞,用于内存区域是规整的,分界点一侧是已分配好的内存区域,另一侧是未分配的,只需要将分界点向未分配区域移动待分配长度即可。
b.空闲列表,若内存区域是散乱的,已经无法使用指针碰撞,就需要记录每一个空间区域,匹配一个足够大的区域进行分配,然后更新列表。
堆内区域,是所有线程共享的,每个线程都在分配内存,就会产生并发问题。jvm解决并发的方法:
a.CAS,通过CAS不停的尝试去获取内存区域
b.TLAB(thread local allocation buffer)本地线程分配缓存,在eden区为线程预先划分一小块缓存,避免线程竞争,当超出缓存时,就按之前的策略分配。该策略是默认开启的,也可关闭-XX:+/-UseTLAB。 -XX:TLABSize可指定缓存大小
3、初始化0值, 提前为分配到的空间赋0值,不包括对象头,即使直接使用对象,也不会报错。
4、设置对象头,对象头主要包含2大部分,若是数组对象,还会附带一个长度
com.maomaotou.test.Cat object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
//以下为头部区域==========================
//0-8 32位,MarkWord
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//该4位用于标记对象的类指针
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
//以下为对象主体区域==========================
//分别为name属性和age属性
12 4 java.lang.String Cat.name (object)
16 4 java.lang.Integer Cat.age null
//头+身,总计20字节,不满足8的倍数,需要对齐4字节
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
以上是32位操作系统的内存分配。
在64位操作系统中,除了MarkWord会加长很多外,其余差别不大, 主要是因为jvm使用了指针压缩技术。使指针继续保持32位的长度。
指针压缩
对应设置-XX:+/-UseCompressedOops 默认开启
如同操作系统,当堆内存小于4G时,使用32位指针就足够了,多余的高位指针抛弃即可。
当内存大于4G时,jvm通过压缩指针,继续保持32位,在到寄存器的时候进行解压还原指针。
压缩指针最大也只能压缩35位,也就是2^35 = 32G,所以当堆内存大于32G时,压缩指针将失效,jvm会使用64位指针,会造成内存膨胀,移动缓慢。所以尽量让堆内存不要超过32G。
JVM在内存分配时的优化
1、Eden区分配内存
新生对象会优先分配eden区,eden区内存规整,可使用指针碰撞,当eden区已满时会触发minor gc,minor gc效率较高,回收快。
eden承担主要分配工作,需要设置的较大,survive区用于存放未被回收的,够用即可。
eden区和survive区的比例默认是8:1:1,并且是动态的,可通过配置禁止动态
-XX:-UseAdaptiveSizePolicy
2、大对象直接分配到老年代
当对象较大时,为了防止大对象来回在eden区和survive区之间移动,可直接分配到老年代,大对象的阈值可通过参数设定
-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC
仅在Serial 和ParNew回收器有效。
3、对象年龄
对象每经过一次minor gc会在头部年龄+1,默认达到15时,会转移动老年代,可通过设置修改默认值
-XX:MaxTenuringThreshold
age在头部只有2位,最大也只能到15
4、对象年龄动态判断
当移入到survive区的总大小超过survive的50%时,部分对象会被直接移到老年代。
可通过设置设置比例-XX:TargetSurvivorRatio
5、老年代空间分配担保
每次minor gc时,都会计算老年代剩余可用空间
如果该空间小于现有年轻代对象只和。会根据参数执行不同策略-XX:-HandlePromotionFailure
有设置(默认有)
会用老年代剩余空间,和每次minor gc 平均进入老年代的大小做比较,如果剩余空间小,则会直接执行full gc,节约一次minor gc
未设置
直接和年轻代对象总和比。
内存回收
内存标记
引用计数法
可达性算法
四种引用
强,软,弱,虚
软,缓存常用,在GC无可用内存时会被回收。
finalize() 尽量不用
类的回收
需要满足条件
1、所有实例已被回收
2、类的classLoader已被回收
3、class没有被引用