重拾JVM--对象创建与内存分配

123 阅读7分钟

基于jdk1.8

除夕夜,预祝大伙新年快乐.喜上眉梢,兔年大吉.

开启JVM章节

对象创建

流程大致如下

image.png

  1. 对象加载

其实就是new对象的过程,虚拟机接收到指令,会检查是否存在该类,是否已被虚拟机加载/解析以及初始化,未加载,就执行类的加载过程

内存分配

在对象加载过后,虚拟机开始为对象分配必要的内存空间,类加载完成之后便会确定类大小,即会在java堆中划分.

内存划分两种方法:

1. 指针碰撞(默认方式)

如果堆内存规整,即一块用过的内存区间和另一块空闲内存区间,中间指针作为分界点

2. 空闲列表

如若不规整,使用过和空闲的都在同一区域,虚拟机会维护一个列表,记录哪些内存是空闲可用的.再分配时从列表中划分可足够的内存空间

解决并发问题方法:

1.CAS

2.本地线程分配(Thread Local Allocation Buffer --> TLAB)

按照线程划分,即每个线程分配一块内存空间.-XX:+/-UserTLAB参数指定是否使用(默认开启+UserTLAB);也可以指定TLAB大小(-XX:TLABSize)

初始化

分配完成后,虚拟机会给分配到的内存空间进行初始化.

对象头

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

HotSpot虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。

对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额 外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机 中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志 位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。

image.png

对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
原文链接:blog.csdn.net/MichaelSuns…

执行init

虚拟机执行初始化赋值和构造方法调用

对象分配

1.Eden区

    Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。

 2.Survival from to

    Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。

 3.年老代

    年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源

YoungGC(MinorGC)与FullGC(MajorGC)区别

YoungGC(MinorGC):新生代垃圾收集动作,回收速度快,一般来说比较频繁. FullGC(MajorGC):回收老年代,年轻代,方法取,回收速度较YoungGC慢(可能十倍以上). Eden与Survivor默认8:1:1

很多对象放在Eden区,满了之后会触发YoungGC,大量对象会被垃圾回收掉,剩余对象会挪到Survivor区,下一次满之后,又会触发YoungGC,对象会从s0挪到s1.新生代存活时间很短,基本上用JVM默认8:1:1即可.让Eden区尽量大.

长期存活对象将进入老年代

对象经过YoungGC之后仍然活着且能够在Survivor区,每移动一次则年龄+1,当年龄增加到一定程度(默认15,CMS默认6,不同垃圾收集可能不一致),就会挪到老年代.可通过参数控制,-XX:MaxTenuringThreshold设置--(该参数主要是控制新生代需要经历多少次GC晋升到老年代中的最大阈值)

动态年龄判断

-XX:TargetSurvivorRatio :对象动态年龄判断:当前放对象的Survivor区域中一批对象的总大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了;一般在YoungGC之后触发.

老年代空间分配担保机制

  JVM使用分代收集算法,将堆内存划分为年轻代和老年代,两块内存分别采用不同的垃圾回收算法,空间担保指的是老年代进行空间分配担保

  在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,

  如果大于,则此次Minor GC是安全的   如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。