对象创建流程
类加载检查–>加载类(否就加载类,是就下一步)–>分配内存–>初始化–>设置对象头–>执行init方法
类加载检查–>加载类:之前的笔记有;
对像内存分配方式
指针碰撞:对象在内存中整齐的放置,分为两块有对象的一块,空闲的一块。以指针的方式隔开,当新建一个对象时,分配给对象一个适合的空间,指针则向空闲方向移动刚好这个对象的大小;
空闲列表:对象在内存中无规律的存放,jvm必须维护一个空闲位置的列表,新生成对象时去这个列表寻找一个恰当大小的区域放置进去;
内存分配并发
无论是指针碰撞还是空闲列表都会有存在并发时争抢内存区域的情况;
cas:允许他们去争抢内存,谁抢到就是谁的,争抢失败的继续去寻找下一个空闲内存(失败重试);
本地线程分配缓存:给每一个线程分配一个空间(可以设置),每一个线程先在这个空间内放置对象,如果放置满了接下来的存放就按照cas的方式去存放对象
初始化:对象成员变量赋予初始值(int 就是0,布尔就是false,引用类型就是null等等等)
设置对象头
Markword:32位系统下4字节
类型指针:堆中存放着新建的对象,但是类元信息是放在方法区里面(具体的代码);类型指针指向方法区里面的类元信息;(类元信息底层是由C写的,我们java程序员调用的时候都是调用堆中的对象)
类型指针的指针压缩:在32(2的32次方)位系统中刚好是4个字节可以表示磁盘中的位置,64位系统中我们的磁盘大小都大于这个值。如果是64位系统大小的话应该是8字节(当然不会做这么大);比如做到2的35次方这么大的时候,4字节就不能表示2的35次方的位置,必须用到更大于的字节大小。这就导致的需要的内存更大,更占用空间。我们就用指针压缩的方式去压缩指针,通过一定的方法压缩指针成4字节表示,需要用到时,再通过相应的方法还原成原来的大小去寻址。
数组长度:占用4字节,只有数组对象才用到;
init方法:给对象赋值真正的值;
对象对齐:8字节的倍数是目前寻址最快的方式,因此当我们的对象大小不足8字节大小的时候就需要补齐成8字节倍数的大小(例如对象大小为52,就会补齐到56);
对象栈上分配
逃逸分析(可设置):当一个对象在方法内就消亡了,作用域只在这个方法里面,那么就直接在栈里面给他分配内存,当使用完这个对象直接销毁,不需要gc以提高效率;
标量替换:一个逃逸分析后的对象需要在栈上分配空间,栈的空间本身不够大;这时没有一个连续的空间给这个对象存放,就会把他分成若干部分在栈中存放,并且标记这个部分为同一个对象;
对象在Eden区分配
如果比较大的对象Eden存放不下,会直接放到老年代,也可以设置多大的对象可以直接放在老年代里面(参数设置的方式只在ParNew和Serial中有效);
(gc时survivor区放不下也会放到老年区)举一个大对象例子(年轻代:80M;Eden 65M;survivor:7M2;老年代175M):新建了一个60M对象,几乎就把Eden填满了,再继续添加一个8M的对象,这时就会触发Major GC,survivor区放不下这么大的对象那么这些对象就会直接放到老年代里;
(抛开一些其他因素举一个例子:年轻代:1G;Eden 800M;survivor:100M
2;老年代4G):有一个订单订购系统,这个系统每秒有60M的对象产生,那么这些对象会因为订单的完成后马上就会变成垃圾对象。大概14秒左右后就会把Eden区填满,这时就会做minor gc,但是第14秒的那些60M对象不是垃圾对象根据动态年龄判断机制这个对象就已经超过了survivor一半的大小,这些对象会进入老年代;但是这些对象其实是马上就要被销毁的,但又没有被销毁,会很快的把老年代填满触发full gc;系统是尽量需要避免full gc的;这么频繁的触发full gc是不行的;这时我们只需要根据实际情况把年轻代调大或者survivor区调大就能解决这个问题;
长期存活的对象:可以参数设置,默认是15(max也是15),根据实际情况去设置;
对象动态年龄判断机制
当一批对象总大小大于等于survivor的一半进入survivor区时,所有大于等于这批对象年龄代最大值的对象都将被放到老年代中;动态年龄判断机制一般时minor gc产生;
老年代空间分配机制:每次做minor gc的时候都去判断老年代的空间是否存的下年轻代中所有的对象(包括垃圾对象);如果剩余空间不足以放下所有年轻代的对象;那么会继续去判断历史平均minor gc后会进入老年代的对象的平均大小。如果大于等于平均值那么直接做full gc,做完后回过头来继续做minor gc