对象的分配:
虚拟机遇到一个new指令时,就会去常量池里查找有没有该类的符号引用(比如:com.hgs.Test (类)的代号 :1号(就是符号)),
如果没有,说明没有定义,则抛ClassNotFoundException;
对象的分配顺序:
1.检查加载:先执行类加载过程,如果没有就先执行类加载;
2.分配内存:根据(符号引用找到类信息)方法区的信息确定该类分配的内存大小;
指针碰撞:内存结构绝对规整的(复制算法,标记整理),只需要根据指针移动划分大小确定的内存;
空闲列表:内存结构不规整(标记清除),可用不可用交错,维护一个可用内存的列表,根据对象大小找到一块合适的内存块;
采用哪种分配方式,是有虚拟机的垃圾回收算法决定的,算法会导致内存结构是否规整;
可能导致的问题:并发安全,两个线程同时分配了同一块内存区域;
解决方案:
CAS机制:对比交换,失败重试;
分配缓冲(Thread Local Allcation Buffer):会在分配线程栈中提前在堆中申请一块很小的私有的内存区域,
(默认占Eden 的1%)设置虚拟机参数(-XX:UserTLAB),这样就不存在竞争问题了。因为只申请了很小的一块
如果对象太大会失败,会采用CAS;
3.内存空间的初始化:内存空间的初始化比较,基本数据类型,int ,初始化为0,
保证了对象实例字段在java代码中不赋值也可以直接使用。
4.设置:虚拟机要对对象进行必要的设置,比如对象是那个实例,如何才能找到元数据信息,对象的哈希码,
对象的分代年龄等信息,这些信息在对 象 头中;
5.对象初始化:前面的工作完之后,才真的的产生一个对象,接下来就会按照java程序的设定初始化对象的信息。
对象的内存布局:对象在内存中存储的布局可以分成3块区域:对象头(header),
实例数据(Instance Data)和对齐填充(Padding)
对象的访问定位:
1.句柄:如果使用句柄访问的话,JAVA堆中会划分出一块内存区域来作为句柄池,refrence中存储的就是对象的句柄地址,
而句柄包含了对象的实例数据与类型数据各自的具体地址信息;
2.直接指针:如果使用直接指针访问,reference 中存储的直接就是对象地址;
堆的内存结构:分为--》新生代(Eden,From,To(survivor)),老年代(Old)
默认比例:新生代:老年代:--》1:2
新生代比例:Eden:From:To--》8:1:1 因为新生代的对象一般都是朝生夕死。
堆内存分配策略(重要):和垃圾回收算法,垃圾回收器息息相关;
1.对象优先在Eden区分配;
虚拟机参数:-Xms 堆最小内存
-Xmx 堆最大内存
-Xmn 新生代内存
-XX:+PrintGCDetails 打印垃圾回收日志,程序退出时输出当前内存的分配情况。
大多数对象会在Eden区中分配,如果不够就会执行MinGC一次;
2.大对象直接进入老年区:
设置参数:-XX:PretenureSizeThreshold=4m(大对象划分界限值)
-XX:+UseSerialGC(垃圾回收器对应Serial,ParNew)
PretenureSizeThreshold参数只对Serial和ParNew两款垃圾回收期有用(分别对应新生代单线程,多线程垃圾回收器)
3.长期存活的对象将进入老年代:
如果对象经过每经过一次MinGC还存活,就会在From区和To区移动一次年龄就加一(对象头信息),
当达到默认的15次就会进入老年区。
4.动态年龄判断:
如果在Survivor空间中相同年龄对象中的大小大于Survivor空间的一半,年龄大于或等于的对象就会移动到老年代;
5.空间分配担保:
在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间
如果条件成立,说明MinorGC可以确保安全,
如果不成立,则虚拟机会查看HandlePromotionFailure设置值,是否允许担保失败。
如果允许,那么会继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小
如果大于,将尝试一次MinorGC,尽管这次MinorGC有风险,如果失败还会执行一次FullGC;
如果不允许担保失败则会执行FullGC;