垃圾回收

垃圾回收

一、判断对象是否存活

1.引用计数法

对每个对象O,记录其被引用的数目。若多一个对象指向O,则O的引用值加一。若指向O的对象被回收或不再指向O,则O的引用值减一。若数目大于0,就说明该对象被其他对象使用中,不能被回收;若引用数目=0,则该对象可以被回收了。

引用计数法存在一个问题就是,循环引用。a指向b,b指向a,那么a和b都无法被回收。但是引用计数法在Python及其他地方是有被用到的,那么Python中是怎么解决循环引用的回收问题呢?

查了一下,看了一个资料中讲到其中一个方法,感觉不错。它的思路是,由引用计数法解决大部分对象的回收问题,再由另一个算法专门解决循环引用。在Python中,只有容器类包含其他对象的引用,那么只需要专门针对容器类就可以了。解决循环引用的思路如下:

用一个集合S1记录所有容器类对象(可以用双向链表实现,这样就可以快速插入和删除对象而不需要多余的内存分配)。

  1. 对每个容器类对象,设置gc_refs属性,值为该对象被引用的数目。
  2. 对容器类对象a,其指向容器类对象b,则将b的gc_refs属性值减一。
  3. 对所有容器类对象重复操作2.则最后gc_refs属性值>1的对象(集合S1包含对所有对象的引用),就是被集合以外的对象引用的,把它移到另外一个集合S2中。
  4. 集合S2中的对象都是不能被回收的,同理被S2中对象引用的对象也要被移到S2中。循环此操作。
  5. S1中剩余的对象,都只被S1中的对象引用,也就是循环引用。可以回收它们了。

2.可达性分析算法

主流的jvm中都使用此方法来判断对象是否可回收。

它的思路是,从一些根节点GC Roots追踪可达的对象,根节点可达到对象A,对象A又引用了B,则根节点就可达到对象B。从根节点可达的对象,就是可用的,否则就是不可用的即可回收的。

那么,哪些对象是根节点呢?

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中类静态属性引用的对象,static成员变量;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI引用的对象。

java代码经过编译器编译后,生成.class文件。jvm类加载器加载class文件后,将class的字节码、类名、方法信息、类静态属性、常量等都放入方法区。其中,常量包括各种字面量。jdk6中,字符串常量池在方法区中,jdk7及以后字符串常量池迁移到堆中。

二、垃圾收集算法

1.标记-清理算法mark-sweep

  1. 标记可以回收的对象
  2. 清理已标记的对象

这种方式会留下很多内存碎片,导致请求大片内存时可能无法得到满足大小的内存,而提前触发下一次gc。而且,标记和清理的效率都不高。

2.复制算法copying

将内存分为大小相等的两块,第一块内存用完之后,将其中还存活的对象复制到第二块,并清理第一块。同样地,第二块用完之后,再将其中存活的对象复制到第一块,清理第二块。

这样,内存只有50%的利用率,比较低,而且复制对象的代价也比较高。

但是,在新生代中,对象的生命期都很短,很多都是一次性的,所以根本不会存活到下一次垃圾回收。所以,很多商用虚拟机都用复制算法回收新生代。

Hotspot默认将新生代分为一个eden区和两个survivor区,比例为8:1。每次GC时,将eden区和一个survivor区中存活的对象复制到另一个survivor中。如果eden区和survivor中存活的对象超过了10%,那么另一个survivor中就放不下这么多对象,hotspot会将剩余的这些对象都放入到老年代中。老年代作为新生代的分配担保。

3.标记-整理算法mark-compact

  1. 标记可以回收的对象
  2. 将存活的对象都复制到内存的一边。然后清理剩余的空间

因为涉及到对象复制,所以也需要考虑复制的成本。

4.分代收集算法

将内存分为新生代、老生代,存活超过一定周期的对象放入到老生代。新生代、老生代采用不同的GC算法。新生代对象存活时间短,就可以选用复制算法,只有少量对象需要复制。而老生代对象一般存活时间长,如果进行复制代价高,且没有额外空间进行分配担保,所以可以用标记-清理算法。

三、Hotspot的GC算法实现

1.枚举根节点

可达性算法进行GC的第一步就是枚举根节点,这一步在所有算法中都需要stop the world,停止所有其他线程,否则枚举的结果可能是不准确的。

Hotspot中停止其他线程后,不需要遍历所有内存区域找到对象引用。在类加载完成的时候,虚拟机用OopMap来记录对象内什么偏移量上是什么类型的对象。这样,GC扫描时就可以直接知道哪些地方存放着对象引用。

这里写图片描述

2.安全点safepoint

虚拟机用OopMap记录哪些位置是引用,但是如果要把所有的引用位置都记录下来,以及引用关系的变化记录下来,代价会比较大。所以就有了安全点,Hotspot只在安全点生成OopMap。

安全点,指特定的class指令位置,Hotspot只在安全点处进行gc。安全点太多,生成OopMap的代价就大;安全点太少,则需要等待较长时间才能开始gc。Hotspot选定安全点的位置是,以“是否具有让程序长时间执行”的标准选择。一般如方法调用、循环跳转等。

简单来说,Hotspot判断需要进行gc时,设置一个参数通知所有线程。线程在安全点处轮询这个参数,发现需要gc,则自己中断挂起。这是主动式中断。

还有一种是抢先式中断,是虚拟机进行gc前,直接将所有线程回滚到最近的安全点,再进行gc。几乎没有虚拟机用这种方式。

所有线程怎么轮询的?Hotspot怎么设置轮询参数的?线程怎么中断挂起的?

3.安全区域safe region

安全点只对执行中的线程有效,若线程整sleep或被block中,那么它没法运行到安全点处中断自己。所以就有了安全区域,指一块代码区域,其中引用关系不会发生变化。

线程进入安全区时,标记自己在安全区中。那么jvm在进行gc时,不用管安全区中的线程。线程重新开始运行,离开安全区时,若发现jvm正在进行根节点枚举,就必须要等待jvm完成根节点枚举,才能继续运行。

四、Hotspot的垃圾收集器

就是具体的垃圾回收实现。

1.serial

单线程进行垃圾收集,且进行垃圾收集时暂停其他所有线程,直到收集结束。在新生代中用标记-复制方式,在老生代中用标记-清除方式。优点是,简单而高效,不需要考虑并行模式下的线程交互等问题。它是虚拟机在client模式下默认的新生代收集器。

serial

2.parNew

是serial的多线程版本parallel,其他都与serial一致。

parNew

3.parallel scavenge

也是采用复制算法的新生代多线程收集器,但是以吞吐量为目标的,所以适合以后台计算为主的应用。

而cms是以停顿少为目标,所以cms作为老生代收集器时,若以parallel scavenge为新生代收集器,二者配合的效果将非常差。无法一起配合使用。
parallel scavenge

4.serial old

serial的老年代版本,单线程,采用标记-清理方式。

5.parallel old

parallel scavenge的老年代版本。

6.cms

concurrent mark sweep是以最短停顿为目标的老年代收集器,采用标记-清除算法。

主要步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发删除

cms

cms作为老年代收集器时,因为cms执行过程中,用户线程还可以执行,还会继续产生垃圾,即“浮动垃圾”。cms无法在本次执行中清理浮动垃圾,只能等到下次。因此cms不能等到老年代已经满了再开始收集垃圾,因为在执行的过程中要给用户线程预留一些内存空间。jdk1.6中,设置阈值为92%。老年代使用空间达到92%,就开始cms回收。但如果回收中用户线程要申请的内存太多,预留的空间无法满足需求,就会出现concurrent mode failure。此时,jvm将启动预案serial old来进行老年代收集,这时停顿的时间就会比较长了。

7.G1

G1收集器不是以新生代老生代来划分内存区域,它将内存划分为多个块,每次根据用户允许的停顿时间等,选择其中的一块进行回收,获得最高的收集效果。

在线程分配空间缓存TLAB中,线程申请的内存都在自己专属的空间中,也比较适合G1方式回收。

显然,会存在对象引用另一块中对象的情况。那么在判断对象是否可回收时,根据可达性算法,还要以其他所有块的对象作为gc_roots来做初始标记。这样工作量将非常大,且没有达到分块回收的目的。在其他收集器进行新生代、老生代分别回收时,也会新生代引用老生代、老生代引用新生代的情况。这个问题的解决方法是,每一块用一个Remembered Set记录其中的对象被其他块引用的情况。每当有引用类型对象的写操作时,如果引用的是另一块中的内存,就记录到被引用内存所属块的Remembered Set中。进行回收时,就只需要根据Remembered Set判断有没有被外部引用,只在回收块内进行初始标记就可以了。
G1

8.收集器的配合使用

收集器搭配

五、GC日志

六、内存分配策略

1.对象优先在eden区分配

2.大对象直接进入老年代

3.长期存活的对象将进入老年代

4.动态对象年龄判断

如果survivor区中相同年龄所有对象大小总和大于survivor区的一般,则大于或等于该年龄的所有对象直接进入老年代。

5.空间分配担保

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值