目录
一、JVM对象创建的过程
1、创建对象的过程
①、类加载检查
判断有无加载过该类,有则直接进入下一步、没有则加载类对象。
②、分配内存
分配内存主要有两个问题:
Ⅰ、如何分配内存?
JVM虚拟机中给对象分配内存主要有两种方法:
- 1、指针碰撞法:该方法是JVM中的默认方法,它主要就是假设JVM中的内存是绝对规整的,使用过的内存和未使用过的内存分别放在两边,用一个指针来给他们做区分。如果要分配内存,只需要将指针向空闲的那一端移动对象大小的位置就好了。
- 2、空闲列表法:如果JAVA堆中的内存分配并不是规整的,那么久需要一张表来记录某些还未分配的内存大小,当需要进行内存分配的时候从表中找到足够大小的内存区域来完成分配,并更新列表上的区域。
Ⅱ、如何防止并发状态下指针被同时调用的问题?
- 1、CAS:虚拟机默认使用CAS配上重试失败的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。
- 2、本地线程分配缓冲:把内存分配的动作按照线程划分在不同的空间中进行。
③、初始化
分配完成之后虚拟机需要将分配完成的对象空间都初始化为零值(不包括对象头)
④、设置对象头
初始化零值之后、虚拟机要对对象做一些必要的设置,这些设置是用来帮助虚拟机管理这个对象的。
⑤、执行init方法
对象按照程序员的意愿进行初始化,也就是给属性赋值并执行构造方法。
2、堆内存中存储的对象实例是什么样的结构?
主要由三部分组成:
- 1、对象头(Object header):主要用于存储两部分数据,一部分用于存储对象自身的运行时数据,像哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分用于存储类型指针,即是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 2、实例数据(Instance Data):具体的对象实例数据。
- 3、对齐填充(Padding):保证对象是8个字节的整数倍。
3、对象指针压缩
①、为什么要进行指针压缩?
减少内存消耗:指针压缩可以减少每一个堆对象的大小,让同样的内存大小可以放更多的对象,这样就会使得内存存储的数据更多的情况下才会触发GC。(指针压缩在JDK1.6之后是默认开启的)
②、如何禁用指针压缩 ?
使用-XX:-UseCompressedOops参数指令。
注意:当堆内存小于4G时不需要启动指针压缩,JVM会直接去除高32位的地址,即使用低虚拟地址空间;当堆内存大于32G时,指针压缩会失效,JVM会强制使用64位(8字节)来对Java对象寻址
二、JVM对象的内存分配
1、校验该对象是否可以存放在栈中
JVM通过逃逸分析(JDK1.7后默认开启)来判断,如果对象会被外部引用,则表明该对象会逃逸,此时这种对象就不能放在栈中,因为后续有可能需要调用。但如果该对象实例没有被外部引用,则可以将它放到栈中,这样当对象使用完后会被直接销毁掉。就可以减少堆内存的压力。
对象在栈中的分配模式:标量替换
标量替换(JDK1.7后默认开启)是在对象可被拆分的时候,JVM不会创建对象,而是会将对象中的成员变量分解成该方法使用的成员变量,这样即使没有那么大的连续空间,也可以分片存储。
- 标量:是不可被进一步分解的量(JAVA中的基本数据类型)
- 聚合量:可以被进一步分解的量(JAVA中的对象)
2、校验该对象是否是大对象
如果是大对象则直接放入堆内存中的老年代堆内存中。
判断是否是大对象的方法:
- 可以通过-XX:PretenureSizeThreshold直接设置大对象的阈值(只有两种垃圾收集器可以支持设置该参数:Serial、ParNew)
3、进行TLAB分配
进入Eden去后,先给当前线程分配内存空间,再将对象放入给线程分配的内存空间中,如果没有通过TLAB校验,就会直接在Eden区使用CAS进行分配。
- 如果Eden区的内存不够,并且survivor区也放不下的话,就会直接将对象放入old区
4、Eden区满以后将对象存入suvivor区
Suvivor区有一个动态年龄判断机制,通过该机制可以将一批总大小大于suvivor区50%的对象中年龄最大的那批对象放到老年代中。
动态年龄判断机制:从年龄最小对象的开始总计,像是年龄1、年龄2、年龄3这样一直计算到对象内存大小达到suvivor区50%的年龄n,然后将年龄n及之后的更大年龄的对象全部放入老年代中
5、最终长期留存下来的对象都放在老年代中
old区(老年代)中会有一个老年代空间分配担保机制,来判断老年代的空间是否够用,不够用就进行Full gc,如果还是不够用就会触发OOM了。
老年代空间分配担保机制:在minor gc之前JVM会先检查老年代最大可用连续空间是否大于新生代存活对象所占的总空间,如果大于则可保证该次minor gc是安全的,如果不大于的话,虚拟机会查看HandlerPromotionFailure设置是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Monitor GC,尽管这次GC是有风险的。如果小于,或者HandlerPromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC了