【读书会第十二期】第三篇Java虚拟机怎么进行垃圾回收

举报
龙哥手记 发表于 2022/05/06 17:27:53 2022/05/06
1.8k+ 0 0
【摘要】 读书笔记

本文的主要有

  1. 对象是否真死
  2. 垃圾收集算法
  3. 垃圾收集器有哪些
  4. 内存分配与回收的策略

一 对象是否真死

先来看下引用计数法,目的判断对象是否存活的一种算法,具体是给对象增加一个引用计数器,对象被引用时引用计数器+1,若对象被引用失效时计数器就减一;任何时候计数器为0,那这个对象不再使用,代表这个对象可以回收了。这种算法简单,效率也高,但是主流Java版本没有选用引用计数来管理内存,因为这种算法很难解决对象之间相互引用的问题;举个简单例子理解,下面代码有二个objA,objB两个对象相互引用,除此两者没有任何引用,然后给它们赋值为空,因为刚才说了两对象相互引用,导致各自引用都不为0,于是无法通知GC收集器回收它们。
image.png

配置JVM参数
image.png

运行结果

image.png

二 垃圾回收算法

可以看到结果已经回收了的,之前的9257k回收到1002k,所以结论是JAVA虚拟机是没有用引用计数法

我们来看重要的可达性算法,比如Java,c#等都是通过可达性来判断对象是否存活的;算法思路是GCRoot对象作为起始点,向下搜索,搜索走过的路径被称为引用链,当一个对象到GCRoot Set没有任何一个引用链与之相连的时候,这个对象判定为可回收对象。
image.png

在Java语言,GC Root对象有下面几种:

  • 虚拟机栈中引用的对象-栈帧中局部变量表变量引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

image.png

  • 强引用:也就是经常使用的创建对象的引用,只要引用还存在垃圾收集器永远不会收集的对象,除非爆出内存异常

  • 软引用:是来描述有用但非必须的对象,对软引用关联的对象,在系统将要发生内存溢出异常之前会进行一次回收,如果还没有足够的内存,将会抛出内存溢出异常

  • 弱引用:也是描述非必要的对象,它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次收集器发生垃圾回收之前,当垃圾收集器工作时,当前内存是否足够,都会回收弱引用关联的对象

  • 虚引用:是最弱的引用关系,完全不会对对象生存时间构成引用,也无法关联其他实例,关联对象的目的是回收该对象会得到一个系统通知。

强调的是:不可达对象也不是非死不可,一个对象要经历死亡,至少要经过两次标记

如果对象在可达性分析后,发现没有与GCRoot相连的引用链,那么该对象要进行第一次标记,并进行筛选,筛选条件是有必要执行finalize(),如果说对象没有覆盖finalize()方法,或者finalize()已经被JVM调用过,JVM把这两种情况都视为没有必要执行finalize(),反之如果有必要执行finalize(),那么这个对象那个被放在fquenue队列之中,然后先用finalize()调用队列中优先级低的对象,也是对象利用finalize()逃脱死亡的最后一次机会,怎么拯救自己,只要对象在队列能与GCRoot引用链建议关联就可逃过死劫,比如把对象复制给类变量,或者局部变量,那么该对象被移除即将回收的集合。
image.png
结果是
image.png
我们在第一次执行finalize()的时候,把对象做了一次赋值,对象第一次会逃脱finalize()这个回收方法,但是第二次由于finalize()已经执行过了,第二次调用方法这个对象已经被回收掉;

三 垃圾收集器有哪些

image.png
上图图左右两部分,JDK8前后,上下是年轻代与老年代的不同回收器。可以看到Serial收集器特点是采用单线程-复制回收算法进行收集,使用它必须暂停所有工作线程直到完成,Serial-Old是老年代版本,采用单线程-标记整理(压缩)回收算法,Parallel是多线程的Serial,它是对回收时间进行切分,降低垃圾回收对业务线程的影响,那么JDK8之后就看到我们熟悉的G1,使用G1堆的内存布局不同,它是把堆划分成大小相等的独立区域,虽然还保留着新生代老年代的概念,但其实是连在一起的内存;现在还不存在最好的收集器,而是针对合适场景选择最合适收集器。

四 内存分配与回收策略

  • MinGC:发生在新生代的回收动作,MinGC非常频繁,回收速度比较快
  • FullGC:发生在老年代的回收动作,至少伴随一次MinGC,FullGC回收速度是慢MinGC十倍以上,大多数情况下对象在新生代的Edend区域进行分配,当空间没有足够空间进行分配时,JVM进行一次MinGC;

image.png

第二是大对象直接进入老年代。 大对象是指需要大量连续空间的JAVA对象,典型就是很长字符串,或数组对象。对于还有空间来了大对象提前触发垃圾回收器获取足够的内存来存放大对象;如果设置-XX:PretenureSizeThreshold,大于这个值的分配在老年代,这样做的目的避免在Edend区,两个S区有大量内存复制;

第三个是长期存活的对象进入老年代。 采用分代收集思想来管理内存,那么内存回收时能识别哪些对象放在新生代,哪些对象放在老年代;JVM给每个对象定义了对象年龄,也就是对象头里面有个计数器,对象在Edend出生,并经历一次MinGC,仍然存活,并且这个对象能被S区容纳的话,将被移动到S区,把对象年龄设置为1,对象在S区熬过一次MinGC,对象年龄就会加1,当对象年龄增加到一定程度,JDK默认的是15,就把该对象搬到老年代中,有多少对象能进入老年代可以用-XX:MaxTenuringThreshold进行设置;

第4个是动态对象年龄判定。 为了更好适应不同程序的内存状况,JVM并不是总是要求对象年龄必须达到足够的回收年龄,才能进入老年代;如果在S空间,相同年龄所有对象的大小总和超过S区空间的一半,年龄大于或等于回收年龄直接进入老年代

第5个是空间分配担保。 MinG之前,虚拟机会先检查老年代最大可用的连续空间,是否大于新生代所有对象总空间,如果条件成立,那么MinGC可以确保我们的对象是安全的;反之,不成立虚拟机会查看是否允许担保失败,如果允许,就检查老年代最大可用的连续空间是否大于以前进入老年代对象的平均大小值,如果大于进行一次MinGC,如果小于进行一次FullGC

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。