JVM虚拟机——JVM中的对象

虚拟机中的对象

对象的分配

当虚拟机遇到一条new指令时,会根据new的参数是否能在常量池中定位到一个类的符号引用,如果没有,说明还未定义该类,抛出ClassNotFoundException;

  • 检查加载:先执行相应的类加载过程,如果没有,则进行类加载。
  • 分配内存:根据方法区的信息确定为该类分配的内存空间大小。
    分配内存的方式有两种:
    指针碰撞:Java堆内存空间规整的情况下使用;
    空闲列表:Java堆空间不规整的情况下使用;
    使用哪种取决于Java堆是否规整,堆是否规整又取决于使用的垃圾收集器是否具有压缩整理的功能。
    除了如何划分可用空间外,还需要注意并发安全问题,对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针的位置,在并发情况下也并不是线程安全的。有可能给A划分了内存,指针还没来得及修改,B又使用了原来的指针分配内存。解决这个问题有两种方案:
    对分配内存空间的动作做同步处理:实际上虚拟机采用CAS加上失败重试的方式保证原子性。
    分配缓冲:本地线程分配缓冲(TLAB),在线程初始化时,会同时从堆申请一块内存(非常小,只有Eden区的1%左右),只给当前线程使用,这样每个线程就有自己的私有指针来分配空间。但存对象的内存空间还是给所有线程访问的。只是其他线程不能在这里分配而已。
    逃逸分析和栈上分配:如果一个对象的作用域没有逃出方法外,生命周期和方法的生命周期相同,这样的对象则为没有逃逸,会被分配在栈上。方法调用结束后,随着栈空间的回收而回收。不给GC增加负担。
    PS:对象分配时:首先尝试栈上分配,失败则尝试TLAB分配,失败后判断是否为大对象直接进入老年代,如果不是大对象则在Eden区分配。
  • 内存空间初始化
    这里不是指构造方法,内存分配完成后,虚拟机将分配到的内存空间都初始化为零值(如int为0,boolean为false等),保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。
  • 设置
    虚拟机对对象进行必要的设置,比如:这个对象是哪个类的实例、如何才能找到对象的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象的对象头中。
  • 对象初始化
    上面的工作都完成了以后,在虚拟机眼中,一个新的对象已经产生了,但从Java程序的视角,对象创建才刚刚开始,所有的字段都还是零值,所以,执行new指令之后会接着按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的内存布局

在HotSpot虚拟机中,对象在内存中的布局可以分为3块区域:对象头、实例数据、对齐填充。

  • 对象头:包括两部分信息,第一部分用于存储自己的运行时数据,如哈希码、GC标志、对象分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
  • 对齐填充:不是必然存在的,也没有特别的含义,仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象的大小必须是8字节的整数倍。如果不是则需要对齐填充来补全。

对象的访问定位

建立对象是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有句柄和直接指针两种:

  • 句柄:句柄访问时,Java堆中会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含对象实例数据和类型数据各自的具体地址。
  • 直接指针:使用直接指针时,reference中存储的直接就是对象地址。
    两种对象访问方式各有优势。使用句柄的好处在于reference中存储的是稳定的句柄地址,在对象移动时,只需要修改句柄中的实例数据指针,而reference不需要修改;使用直接指针的好处在于速度快,它相对句柄少了一次指针定位的开销,而Java中对象的访问还是比较频繁的。
    HotSpot使用的是直接指针进行对象访问的。

堆内存分配策略

Eden区

  • Eden区
  • Survivor(from)区:设置Survivor是为了减少送到老年代的对象。
  • Survivor(to)区:设置两个Survivor区是为了解决碎片化的问题。
    对象优先在Eden区分配,当Eden区没有足够空间分配时,发生一次Minor GC。
    虚拟机参数:

-Xms20m 堆空间初始20m
-Xmx20m 堆空间最大20m
-Xmn10m 新生代空间10m
-XX:+PrintGCDetails 打印垃圾回收日志,程序退出时输出当前内存的分配情况。

老年代

大对象直接进入老年代(最典型的大对象是很长的字符串和数组)。这样做的目的是:避免大量内存复制;避免提前进行垃圾回收。
长期存活的对象也会进入老年代。出生后的对象每经过一次Minor GC年龄就增加1,增加到一定程度就会晋升到老年代中(默认是15)。
如果在Survivor空间中相同年龄对象大小大于Servivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
虚拟机参数:

-Xms20m
-Xmx20m
-Xmn10m
-XX:+PrintGCDetails
-XX:PretenureSizeThreshold=4m 超过多少大小的对象直接进入老年代(此参数只对Serial和ParNew两款收集器有效)
-XX:+UseSerialGC

空间分配担保

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,那么Minor GC可以确保是安全的,如果小于,虚拟机会查看HandlePromotionFailure设置的值是否允许担保失败,如果允许,虚拟机会继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小,如果大于,会尝试进行Minor GC,尽管是有风险的,如果担保失败则会进行一次Full GC;如果小于,或者HandlePromotionFailure不允许担保失败,这是也会进行一次Full GC。
HotSpot默认是开启空间分配担保的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值