虚拟机中的对象
new关键字对象分配
1.检查加载
首先检查这个指令参数是否在常量池中定位到一个类的符号引用,并且检查类是否已经被加载、解析和初始化过。
2.内存分配
JVM为新生对象分配内存。
分配内存有两种方式 :指针碰撞和空闲列表
a.指针碰撞
如果java堆中内存是绝对规整的,使用的内存在一边,空闲的内存在另一边,中间放一个指针作为分界的指示器,那分配内存就仅仅是将指针向空闲内存挪动一段与内存大小相等的距离。
b.空闲列表
如果java堆中内存凌乱,使用的内存和空闲内存交错,虚拟机就必须维护一个列表,记录了那些内存块是可用的,在分配的时候找到足够大的空间分配给实例对象,并更新列表的记录。
这两种方式由堆是否规整决定,而堆是否规整由垃圾收集器是否带有压缩整理为准。
并发安全
由于堆是线程共享的,就会出现线程不安全,解决这问题有两种方案
a.CAS机制
对分配内存空间同步进行,采用CAS失败重试的方式保证原子性。
b.本地线程分配缓冲(Thread Local Allocation Buffer/TLAB)
在线程初始化的时候就在堆上给线程分配一块大小指定的内存,只给当前线程使用,这样就不存在竞争了,当缓冲区不够用的时候重新申请一块缓冲区使用。
3.内存空间初始化
虚拟机需要将分配的内存空间初始化为零值(int =0,boolean = flase),这一步保证了对象的实例在java代码中可以不赋初始值就直接使用。
4.设置
虚拟机要对对象进行必要设置,对象头里面的信息
image.png
5.对象初始化
从虚拟机的视角看,一个新对象就产生了,从java视角看,创建对象才刚刚开始,所有字段还是零值。之后就把对象初始化(构造函数),一个可用的对象就产生了。
对象的访问定位
使用对象需要通过栈上(本地变量表)的引用数据来操作具体的对象。
a.句柄
堆中会划分一块内存作为句柄池,引用数据中存储的就是对象的句柄地址,句柄中包含了实例数据与类型数据的具体地址信息。
优点:应用数据中存储的就是稳定的句柄池,对象被移动的时,只会更改句柄中的实例数据指针。
b.直接指针
直接指针访问,引用数据存储的是对象地址。
优点:访问速度快
判断对象存活
在堆里存放着几乎所有的对象,垃圾回收之前,要做的事情就是确定哪些对象还“存活”,哪些已经“死去”。
引用计数法
在对象中添加一个引用计数器,每当引用它,计数器就加1,失去该引用计数器就减1。如果对象互相引用,这就需要额外的机制来处理。
可达性分析
一些列GcRoot的对象作为起点,从这些节点向下搜索,搜索的路径称为引用链,当一个对象到Gc Roots没有任何引用链时,则称为对象不可用(死去)。
Gc Roots包含以下:
1.虚拟机栈本地变量表中引用的对象;
2.方法区中静态属性引用的对象;
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象;;
5.jvm内部引用对象(class对象,异常对象Exception,系统类加载器);
6.所有被同步锁持有的对象;
7.jvm内部的JMXBean、JVMTI中注册的回调、本地代码缓存等;
8.jvm实现中的“临时性”对象,跨代引用对象;
Class要被回收必须满足一下(Class不是实例)
1.该类的所有实例都被回收,堆中不存在该类的任何实例
2.加载该类的ClassLoader被回收
3.该类对象的java.lang.Class对象没有被任何地方引用,无法在任何地方通过反射访问该类的方法
4.参数控制-Xnoclassgc没有被禁用
Finalize方法
即使通过可达性分析判断到不可达的对象,也不是“非死不可”,还有一个缓刑阶段。如果对象覆盖了finalize方法,就可以在finalize中去拯救。
finalize只会执行第一次,不会执行第二次。
方法执行缓慢、还没有完成拯救,就被垃圾回收器回收了
四种引用
强引用
根可达还在就不会回收的引用对象。
软引用 SoftReference
系统将要发生OOM之前,引用对象被回收。
弱引用 WeakReference
GC发生时,都会被回收。
虚引用 PhantomReference
最弱的引用,为了监控垃圾回收器是否正常工作
对象的分配策略
image.png栈上分配
逃逸分析
没有逃逸:分析对象动态作用域,当一个对象在方法中定义后,没有被外部方法引用;
方法逃逸:调用参数传递到其他方法中;
线程逃逸:如果被其他线程访问到;
如果逃逸分析出来的对象可以栈上分配的话,
对象优先在Eden区分配
大部分情况下,对象在新生代eden区中分配,当eden区没有足够空间分配时,虚拟机发生一次Minor GC
对象直接进入老年代
大对象是很长的字符串以及数组等,这样做1.避免内存大量复制;2避免提前垃圾回收
长期存活对象进入老年代
对象在Eden区出生,并且经历了第一次Minor GC后任然存活,并且能被survivor容纳的话,将其移动到survivor,记录年龄为1,以后每经历一次Minor GC,年龄就+1,当年龄增加到一定程度MaxTenuringThreshold( 并发垃圾回收器默认是15,CMS是6),就会被晋升到老年代。
对年龄动态判断
为了更好的适应不同程序的情况,并不是对象一定要达到MaxTenuringThreshold才能进入老年代,在survivor空间所有对象大小超过了survivor空间的一半,就可以直接进入老年代。
空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果担保失败则会进行一次Full GC;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
网友评论