对象的分配及垃圾回收机制——JVM(2)

本文深入探讨了Java对象的创建过程,包括类加载、内存分配、初始化及对象分配策略。讲解了栈上分配、本地线程分配缓冲以及正常分配的细节,并介绍了对象的访问定位方式。此外,文章还详细阐述了Java垃圾回收机制,包括引用计数和根可达性分析算法,以及分代回收理论和各种垃圾回收算法的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.JVM中对象的创建过程

1.详细过程

①检查加载(检查有没有进行类加载)

符号引用是什么?在类加载的时候,A常量池中可能包含对B的引用,但是B还没有加载进来,所以就用一个符号来表示B,这就叫符号引用,有时也用字面量来称呼。
所以检查加载这一步还会进行将符号引用变成直接引用的步骤。如果B加载进来了,就变成直接引用。

②分配内存

划分内存的方式:指针碰撞、空闲列表

  • 指针碰撞:假设堆空间是规整的(无内存碎片),那就用一个指针一个一个的进行分配。
    在这里插入图片描述
  • 空闲列表:相反如果堆内存不规整,有内存碎片,那就用空闲列表来进行分配,这个列表记录了堆中空闲的位置。
    在这里插入图片描述

还有一个问题:如何解决并发安全问题?CAS或本地线程分配缓冲

  • CAS就不说了,在之前的文章中有介绍,下面介绍下后者
  • 本地线程分配缓冲(Thread Local Allocation Buffer)是说给每一个线程在堆中的Eden区分配一块区域,这块区域专门用于该线程分配对象。如果有新线程,那么就划分一块新的区域。CAS比较费时间,本地线程分配缓冲就是空间换时间
    在这里插入图片描述

③内存空间初始化
比如int你不赋值,那么默认赋0,boolean默认赋false等等。
④设置对象头等等
在这里插入图片描述
在这里插入图片描述
下面这个图好理解。类型指针就类似于记录下自己的祖宗,表明自己是属于哪一个类。我们平时接触最多的就是实例数据,就是对象里面那些变量啥的。
⑤对象初始化
也就是执行构造方法

2.对象分配策略

对象如何进行分配呢?
有句话:几乎所有的对象都在堆中进行分配,言外之意就是并不是所有的对象都在堆中,对象的分配其实是可以进行栈上分配的。

(1)如何进行栈上分配(虚拟机优化技术之一)

要满足两个条件①逃逸分析②触发JIT(热点数据)
怎么着才算是热点呢?循环达到一定程度才算。
比如我们进行一个循环,每次循环都调用一个方法,这个方法就是创建一个对象。
在这里插入图片描述
JVM遇到这样的代码,就不会每次都会解释执行,而是会触发一个技术,叫JIT动态编译技术,同时做“逃逸分析”,它分析我们创建的对象有没有可能逃出这个方法,逃出这个线程(通俗来讲就是这个对象能否被其他线程用到)。如果没有可能的话,那就将此对象分配到栈中。因为没必要为其他线程所共享,它逃不出去。这样就减少了在堆上的对象分配,减少垃圾回收次数,提高程序效率
也就是说如果触发了JIT技术,同时JVM进行了逃逸分析,那么这样的对象可能就分配在栈中

(2)本地线程分配缓冲(虚拟机优化技术之二)

上面有讲,这里就不介绍了。(2)和(3)都是分配到堆中

(3)正常的分配(对象的分配原则)

①对象首先在Eden区进行分配

②大对象直接进入老年代(用的比较少)

③长期存活的对象进入老年代

④动态对象年龄判定
关于第③条,我们可以设置一个标准,比如对象的年龄大于15就进入老年代,但是这个标准在很多情况下是很难设置的,有时快溢出了,但是很多对象还没到达规定年龄,无法进入老年代。所以我们就进行一种策略,比如将对象按年龄从小到大排序,将对象的大小依次相加,什么时候大于某个值了,就把后面所有的对象移到老年代。这种策略就是动态对象年龄判定

⑤空间分配担保
就是担保对象从新生代进入老年代的时候一定能放得下。

总体流程就是下面这张图

在这里插入图片描述

二.对象的访问定位

1.两种引用

在上一篇文章中讲过了,对象的引用可能保存在虚拟机栈的局部变量表中,如图
在这里插入图片描述

那么局部变量表中的引用如何指向真正的对象呢?其实有两种方式

(1)直接指针

第一,就是直接指针。顾名思义,就是指针直接指向堆中的对象,没有中间者。这也是主流的方式,hotspot也是用的这种方式在这里插入图片描述

(2)使用句柄

第二,就是 使用句柄。类似于有了一个中间者。
比如我们去按摩,我们可能是找13号技师,而不是直接指名道姓或者说出那个技师的身份证号来找到技师。13只是一个媒介,真正的人可能会更换,但是13可以一直保留。就类似于这个使用句柄。对象实例可以更换,但是引用不用变,都是指向13号技师。但是这样效率可能会更低,不如直接指名道姓来的更快。
在这里插入图片描述

2.四种引用

强引用:=

这个垃圾回收的时候是一定回收不了的

软引用:SoftReference

垃圾回收的时候有几率回收,比如马上要溢出的时候。

弱引用:WeakReference

只要发生了垃圾回收,就一定会被回收

虚引用:PhantomReference

比弱引用还要弱。有时用来监控垃圾回收是否正常。

三.Java自动化的垃圾回收(GC)

1.判断对象的存活算法

①引用计数算法

在这里插入图片描述

这个算法比较简单,就是说有几个引用指向它,它就记几
如上图,没有引用指向它,它就是“垃圾”,就可以被回收了。但是这个算法有缺点,看用紫色框圈出来的那两个,是互相引用的,但是两者都没法执行,都是垃圾,但是因为他们是1不是0,所以系统不会回收他们。
这种算法比较简单,但是无法解决循环引用问题,除非另启线程去专门处理这个循环引用。导致效率并不高

②根可达性分析算法(主流算法)

它定义了一些根,这些根都是集合,也就是RootSet,这些根也被称为GC roots,也就是跟垃圾回收相关的root set。常见的就是下面这4种。它的思想就是根据根来判断哪些对象可达,那些不可达的就是垃圾,就要被回收

这种算法天生就解决了循环引用问题

③class如何回收?

为什么我们一般都说对象回收而不是class回收呢?原因很简单,就是class回收的条件非常的苛刻,具体的我就不说了,可以查下相关材料。所以class回收的次数要少得多,所以我们一般说对象回收

④Finalize

如果通过可达性分析,判断一个对象是垃圾了,在回收之前可以进行一个“拯救”,也就是Finalize,就类似于“刀下留人”。这个用的非常少,知道作用就行了。平时不建议使用。

2.分代回收理论

堆分成了新生代和老年代,新生代又被分为Edenfromto
垃圾回收一般指两种回收,一是Minor GC或者Young GC,另一种是Full GC。前者是回收新生代,后者是回收新生代和老年代和方法区(class也可能会回收)
在这里插入图片描述

3.垃圾回收算法

①复制算法

预留一半空间,将非垃圾的对象进行复制,复制到预留的区域,再将原来的空间做一次整体的格式化
在这里插入图片描述

  • 优点
    实现简单、运行高效
    没有内存碎片
  • 缺点
    空间利用率只有一半

经过统计后,新生代百分之98以上的对象都是垃圾,所以就没必要预留一半的空间了,此时Eden区和from区,to区就来了。

②Appel式的复制回收算法

对象优先在Eden区进行分配,存活的对象会到from或者to中的一个。Eden:from:to=8:1:1
在这里插入图片描述

这样能使空间利用率达到百分之九十以上。( s0s1就是fromto
上面的两种算法是针对新生代的。而老年代中的对象大部分都不是垃圾,所以就要换一种思路

③标记-清除算法

这个很简单,标记完了清除就可以了。

  • 缺点
    位置不连续,容易产生内存碎片
  • 优点
    可以做到不暂停。我们程序在运行的时候是不会用到这些垃圾对象的,所以该算法在工作的时候不会影响业务线程,可以做到不暂停。上面那种复制算法就得暂停
    在这里插入图片描述
④标记-整理算法

先标记,再整理,最后清除。这样有利于整批清除,提高效率

  • 优点
    没有内存碎片

  • 缺点
    非垃圾对象进行了移动,所以要进行暂停
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值