一、jvm中对象的创建过程
1.1 对象创建过程图解
1.2 对象创建过程说明
- 类加载检查。当java虚拟机遇到一个new指令时,首先会去常量池中去查找是否有该类的符号引用,并且检查该符号引用对应的类元信息是否已被加载、解析、初始化过,如果没有加载,则走类的加载过程。
- 内存分配。通过类的加载检查后,虚拟机会开始为新对象分配内存,当类被加载完成后,对象的大小可以被完全确定,实际就是从内存中划分出一块区域用来存放新对象。内存分配的方法主要有两种:一是指针碰撞(默认使用指针碰撞),当jvm中内存是规整的,所有用过的内存在一边,没有用过的在一边,中间以一个指针指向区分界限,那么下次分配只需要维护指针的位置即可,但是会存在并发问题(有并发解决方案)。第二种分配方式是空闲列表,就是虚拟机中的内存不是规整的,是零散的,那么就无法使用指针碰撞的方式进行内存分配了,需要用一个列表记录哪块内存已经被使用,哪块内存还没有被使用,在分配的使用,在列表中查找位置进行分配,如果对象过大,没有零散的足够大的内存可分配,则会出发gc。
- 初始化。虚拟机将分配到的内存空间都初始化为零值(除了对象头---此时还没有对象头)
- 设置对象头。初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 执行init方法。即对象按照程序员的意图进行初始化。
二、对象的栈上分配
我们知道的对象的分配都是在堆内存中进行分配,实际上,jvm为了节省内存和减少gc,优化出了对象的栈上分配。栈上分配依赖与对象的逃逸分析和标量替换。
- 逃逸分析。(jvm会分析该对象是否会逃逸出当前方法,会不会被外部所访问来进行分析),比如在一个方法内存定义的对象,该方法无返回值,该对象则会被虚拟机定义为没有逃逸出该方法,触发栈上分配。jdk1.7后默认是开启了逃逸分析。
- 标量替换。当方法的栈空间不足以分配给对象存储时,jvm会使用标量替换,将对象的各个信息打散存储在栈的各个零散的内存中,使用一个指针标记这些信息属于哪个对象。这就是标量替换。
三、对象在堆中分配
- 对象分配在eden区分配。大多数对象都是在新生代中进行分配。当eden区没有足够的内存进行分配时,会触发一次minor gc,回收掉垃圾对象,释放出内存空间。并且将可用对象复制到survivor区,eden区与survivor区默认比例是8:1:1(该比例可通过配置参数调整)。
- 大对象提前进入老年代。对象的分配需要一块连续的内存空间,当分配对象空间时,如果对象所需内存空间eden区无法分配(gc操作时对象过大复制会大大影响效率),那么我们可以配置大对象提前进入老年代,减少eden区的压力,减少gc。
- 长期存活的对象进入老年代。jvm虚拟机中,默认是分代年龄等于15才会进入到老年代。实际中,我们可以按业务场景调整该值,不用等待年龄到15才将对象放入老年代。
- 动态年龄判断机制。当前放对象的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,fullgc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”