JAVA虚拟机——对象由生(创建对象)到死(垃圾回收)的底层原理过程

创建对象

在Java编程语言中,创建对象通常是一个new关键字而已,但在虚拟机中,对象(此处对象仅代表普通的Java对象,不包括数组和Class对象等)的创建是如何呢?

①虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程。

②在类加载检查通过后,虚拟机将为新生对象分配内存,对象所需内存的大小在类加载完成后便可以确定。

③内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。若使用TLAB,这一工作过程也可提前至TLAB分配时进行,保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序访问字段的数据类型所对应的零值。

④虚拟机要对对象进行必要的设置(对象哈希码,GC分代年龄等信息)存放在对象的对象头(Object Header)之中。

⑤前几步完成后,从虚拟机的角度来看,一个新的对象产生了,但从Java程序的角度,对象创建才刚开始(init方法没有执行,所有字段都为零)。一般来说(由字节码中是否跟随invokespecial指令决定),执行new指令之后会接着执行init方法,把对象初始化。

分配内存

虚拟机为新生对象分配对象分配内存,等同于将一块确定大小的内存从Java堆中划分出来。

指针碰撞

假设java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。

空闲列表

如果java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交替,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”

选择哪种分配方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定的。

线程安全问题

除了如何划分可用空间之外,还有一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发的情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况,解决这个问题有两种方案:

扫描二维码关注公众号,回复: 4785073 查看本文章

一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性

另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。


回收对象

在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还‘存活’着,哪些已经‘死去’。

可达性分析算法

在主流的商用程序语言的主流实现中,都通过可达性分析算法来判定对象是否存活。 通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,这个对象已‘死’。

在Java语言中,GC Roots的对象有:

  • 栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

生存还是死亡

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。

如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链。 那它将会被第一次标记并进行一次筛选。

筛选的条件:此对象是否有必要执行finalize()方法。 当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过, 虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。

如果对象这时候还没逃脱,那基本上它就真的被 回收 了。

 

 

猜你喜欢

转载自blog.csdn.net/weixin_42230885/article/details/84310318
今日推荐