浅谈GC

JVM GC(垃圾回收机制)

在学习Java GC 之前,我们需要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成。GC优化很多时候就是减少stop-the-world 的发生。

JVM GC回收哪个区域内的垃圾?

需要注意的是,JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内。

JVM GC怎么判断对象可以被回收了?

· 对象没有引用

· 作用域发生未捕获异常

· 程序在作用域正常执行完毕

· 程序执行了System.exit()

· 程序发生意外终止(被杀线程等)

在Java程序中不能显式的分配和注销缓存,因为这些事情JVM都帮我们做了,那就是GC。

有些时候我们可以将相关的对象设置成null 来试图显示的清除缓存,但是并不是设置为null 就会一定被标记为可回收,有可能会发生逃逸。

将对象设置成null 至少没有什么坏处,但是使用System.gc() 便不可取了,使用System.gc() 时候并不是马上执行GC操作,而是会等待一段时间,甚至不执行,而且System.gc() 如果被执行,会触发Full GC ,这非常影响性能。

JVM GC什么时候执行?

eden区空间不够存放新对象的时候,执行Minro GC。升到老年代的对象大于老年代剩余空间的时候执行Full GC,或者小于的时候被HandlePromotionFailure 参数强制Full GC 。调优主要是减少 Full GC 的触发次数,可以通过 NewRatio 控制新生代转老年代的比例,通过MaxTenuringThreshold 设置对象进入老年代的年龄阀值(后面会介绍到)。

按代的垃圾回收机制

新生代(Young generation):绝大多数最新被创建的对象都会被分配到这里,由于大部分在创建后很快变得不可达,很多对象被创建在新生代,然后“消失”。对象从这个区域“消失”的过程我们称之为:Minor GC 。

老年代(Old generation):对象没有变得不可达,并且从新生代周期中存活了下来,会被拷贝到这里。其区域分配的空间要比新生代多。也正由于其相对大的空间,发生在老年代的GC次数要比新生代少得多。对象从老年代中消失的过程,称之为:Major GC 或者 Full GC。

持久代(Permanent generation)也称之为 方法区(Method area):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生GC。发生在这个区域的GC事件也被算为 Major GC 。只不过在这个区域发生GC的条件非常严苛,必须符合以下三种条件才会被回收:

1、所有实例被回收

2、加载该类的ClassLoader 被回收

3、Class 对象无法通过任何途径访问(包括反射)

可能我们会有疑问:

如果老年代的对象需要引用新生代的对象,会发生什么呢?

为了解决这个问题,老年代中存在一个 card table ,它是一个512byte大小的块。所有老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只需要查询 card table 来决定是否可以被回收,而不用查询整个老年代。这个 card table 由一个write barrier 来管理。write barrier给GC带来了很大的性能提升,虽然由此可能带来一些开销,但完全是值得的。

默认的新生代(Young generation)、老年代(Old generation)所占空间比例为 1 : 2 。

新生代空间的构成与逻辑

为了更好的理解GC,我们来学习新生代的构成,它用来保存那些第一次被创建的对象,它被分成三个空间:

· 一个伊甸园空间(Eden)

· 两个幸存者空间(Fron Survivor、To Survivor)

默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1

每个空间的执行顺序如下:

1、绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)。

2、在伊甸园空间执行第一次GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)。

3、此后,每次伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间。

4、当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。然后会清空已经饱和的哪个幸存者空间。

5、在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代。

从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。

我们需要重点记住的是,对象在刚刚被创建之后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。

也有例外出现,对于一些比较大的对象(需要分配一块比较大的连续内存空间)则直接进入到老年代。一般在Survivor 空间不足的情况下发生。

老年代空间的构成与逻辑

老年代空间的构成其实很简单,它不像新生代空间那样划分为几个区域,它只有一个区域,里面存储的对象并不像新生代空间绝大部分都是朝闻道,夕死矣。这里的对象几乎都是从Survivor 空间中熬过来的,它们绝不会轻易的狗带。因此,Full GC(Major GC)发生的次数不会有Minor GC 那么频繁,并且做一次Major GC 的时间比Minor GC 要更长(约10倍)。

JVM GC 算法讲解

1、根搜索算法

根搜索算法是从离散数学中的图论引入的,程序把所有引用关系看作一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点。当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

上图红色为无用的节点,可以被回收。

目前Java中可以作为GC ROOT的对象有:

1、虚拟机栈中引用的对象(本地变量表)

2、方法区中静态属性引用的对象

3、方法区中常亮引用的对象

4、本地方法栈中引用的对象(Native对象)

基本所有GC算法都引用根搜索算法这种概念。

 

2、标记 - 清除算法

 

标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象进行直接回收,如上图。

标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活的对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,并没有对还存活的对象进行整理,因此会导致内存碎片。

3、复制算法

 

 

复制算法将内存分为两个区间,使用此算法时,所有动态分配的对象都只能分配在其中一个区间(活动区间),而另外一个区间(空间区间)则是空闲的。

复制算法采用从根集合扫描,将存活的对象复制到空闲区间,当扫描完毕活动区间后,会的将活动区间一次性全部回收。此时原本的空闲区间变成了活动区间。下次GC时候又会重复刚才的操作,以此循环。

复制算法在存活对象比较少的时候,极为高效,但是带来的成本是牺牲一半的内存空间用于进行对象的移动。所以复制算法的使用场景,必须是对象的存活率非常低才行,而且最重要的是,我们需要克服50%内存的浪费。

4、标记 - 整理算法

 

 

标记-整理算法采用 标记-清除 算法一样的方式进行对象的标记、清除,但在回收不存活的对象占用的空间后,会将所有存活的对象往左端空闲空间移动,并更新对应的指针。标记-整理 算法是在标记-清除 算法之上,又进行了对象的移动排序整理,因此成本更高,但却解决了内存碎片的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值