グラフィックメモリJVMのガベージコレクションアルゴリズム
この記事では、ガベージコレクションのアルゴリズムはそれについて学ぶために一緒に回収効率、以下のブロガーと皆に重点になるように、若い世代のガベージコレクションのヒープ領域は非常に頻繁にするように、グラフィックメモリJVMのガベージコレクションのアルゴリズムを紹介します
序文
まず、私は、この人は二つのことを知っているデフォルトの読み取りを準備し、JVMのガベージコレクション機構について話します:
- JVMがやっています
- Javaヒープとは何ですか
我々はJVMのJavaヒープガベージコレクションに何が起こったのかについて話をする程度であるため、コアを強調するために、いくつかの他のものではない関連Benpian私は合計をスキップします
我々はすべて知っているように、Javaヒープは、オブジェクトのインスタンスを保存しない、とJavaヒープサイズが制限され、我々はいくつかのアップに使用されている置くことができますので、ゴミオブジェクトは、もはや手動で私たちを助けるためにJVMのように、メモリから解放されたを使用することができます行動のC ++コードに似無料Aステートメントを追加します。
しかし、これらのオブジェクトがガベージコレクトされているか、それが問題で、今、私たちはすぐの話を知らないしません。
ゴミオブジェクトどのようにオブジェクトを決定することです
特定のGC(ガベージコレクション)アルゴリズムを見る前に、我々はJVMを初めて目には、オブジェクトを決定するために、どのようにゴミオブジェクトされている
、それはアクセスされない、オブジェクトはより厳しい刑で、無価値であり、名前は、ゴミオブジェクトを提案します参照カウント:私達の第1判定プログラムを引き出している他のオブジェクトによって参照されていないオブジェクト
参照カウント
このアルゴリズムの原理は、それぞれが他のオブジェクトへの参照がオブジェクトA、参照カウント+ 1上の物体A、及びその逆の、物体Aの障害への参照があるたびに、オブジェクトのオブジェクトカウントAへの参照が生成されていることです値は-1、ガーベッジオブジェクトとして指定された0であるオブジェクトの場合、参照カウント、
このアルゴリズムC ++の理解、より良いルックスが知っておくべき、C ++スマートポインタは、同様の参照カウントを持っていますが、これは「シンプル」なアプローチとないようですが、オブジェクトを決定するために使用することは、私たちが見て、ゴミオブジェクトであります次のシーン:
このシナリオでは、オブジェクトAがオブジェクトBへの参照を持つ、オブジェクトBは、オブジェクトAへの参照を持つ、二つのオブジェクトの参照カウントがゼロでない、但し、A、B二つのオブジェクトだろう明らかにせずに外部オブジェクトであります彼らはお互いに依存している場合でも、海の島の隣に2、などの基準が、それでも島があり、他の人々は生活が困難になる、と参照カウントが0ではないので、多くのJVMがある場合は、オブジェクトは、スパムと判断することはできませんこうしたゴミオブジェクト、最終的には頻繁に頻繁にシステムがクラッシュにつながる、異常OOMがスローされます
すべてのすべてで、誰かがあなたを尋ねられた場合、なぜJVMはこれらの言葉を覚えておく必要がある、ゴミオブジェクトを決定するために参照カウント方式を使用していません:参照カウントは、循環依存関係オブジェクトの問題を解決することはできません
到達可能性解析
参照カウントが非常に近い結果にですが、問題は、すべてのオブジェクトが基準値+ 1への参照を持って、なぜ重要なのは、誰かがオープンへの扉をノックしたかのように、我々は唯一の私たちが知っているものに与える必要があり、カウントされます男は、言うことだけ参照カウントを参照するための唯一の重要な課題であるドアを開け+1
しかし、これは、それは長いものが十分であるとしてとしてオブジェクトを参照することが重要であるので、我々は参照カウント方式に次の形式を最適化することができるように、各1への参照を持っている必要はありませんが、十分ではありません。
参照する際に何の「重要なオブジェクト」が存在しない場合にターゲットマークを設定するには、「重要なターゲット」引用するがあるたびに、このフラグをtrueに設定されているだろう、それは、オブジェクトが偽としてマークされ、falseに設定迎えますゴミオブジェクト
これは、到達可能性分析のプロトタイプは、我々は正しいに続けることができている、我々はオブジェクトをマークするためのイニシアチブをとる必要があるが、これらのみ「重要なオブジェクト」とそれからを見つけるために、ガベージコレクタを待つ必要はありません、我々はターゲットを見つけることができますこれらは、非ゴミオブジェクトとしてマークされ、そして自然の残りの部分はゴミオブジェクトですされています
そこで我々は、到達可能性解析アルゴリズムの最終的概念を取得という名前のGCのルーツの上にいう「重要なオブジェクト」、次のようになります。
目標に到達していない、ガベージコレクションは、GCのルーツから始まる、GCのルーツと呼ばれるルート・ノードを作成し、スパム対象としてマークされています
ここで、領域としてGCのルーツは、次のとおりです。
- ローカル変数テーブル内のVMのスタックのスタックフレーム
- クラス属性とメソッド参照されたオブジェクトの定常領域
- ネイティブメソッドスタック参照オブジェクト
換言すれば、GCルーツ方法は、ローカル変数、クラス属性、及び一定であります
ガベージコレクションのアルゴリズム
最後に、本論文の焦点は、我々は、我々はこれらのオブジェクトはゴミをリサイクルする方法の分析に焦点を当てる、ゴミオブジェクトに属するオブジェクトを判断するだけの方法を分析します
マーク - スイープアルゴリズム
マーク - スイープアルゴリズムは二つの手順、マーキングプロセスおよび洗浄プロセスを持っていることを理解することは簡単ですが、ラベリング処理は、すべての非ゴミオブジェクト上記の到達可能性解析によってマークされ、その後、洗浄工程で洗浄されます
たとえば、私たちは今、次のJavaヒープなどのいずれかを持っている、(オレンジ色は、その青が通常のオブジェクトであることを示します)到達可能性解析により、すべてのゴミオブジェクトをマークしました。
その後、我々はステージをクリアすることにより、クリーン、結果が次の図です。
发现什么问题了吗,没错,清理完后的空间是不连续的,也就是说,整个算法最大的缺点就是:
- 会出现大量的空间碎片,当需要分配大对象时,会触发FGC,非常消耗性能
这里引出一个FGC的概念,为了避免主题跑偏,本文中暂时不进行深入,只需要知道垃圾回收分为YGC(年轻代垃圾回收)和FGC(完全垃圾回收),可以把YGC理解为扫扫地,倒倒垃圾,把FGC理解为给家里来个大扫除
复制算法
复制算法将Java堆划分为两块区域,每次只使用其中的一块区域,当垃圾回收发生时,将所有被标记的对象(GC Roots可达,为非垃圾对象)复制到另一块区域,然后进行清理,清理完成后交换两块区域的可用性
这种方式因为每次只需要一整块一起删除即可,就不用一个个地删除了,同时还能保证另一块区域是连续的,也解决了空间碎片的问题
整个流程我们再来看一遍
1.首先我们有两块区域S1和S2,标记为灰色的区域为当前激活可用的区域:
2.对Java堆上的对象进行标记,其中蓝色的为GC Roots可达的对象,其余的均为垃圾对象:
3.接下来将所有可用的对象复制到另一块区域中:
4.将原区域中所有内容删除,并将另一块区域激活
这种方法的优缺点也很明显:
- 优点:解决了空间不连续的问题
- 缺点:空间利用率低(每次只使用一半)
为了解决这一缺点,就引出了下面这个算法
优化的复制算法
至于为什么不另起一个名字,其实是因为这个算法也叫做复制算法,更确切的说,刚才介绍的只是优化算法的雏形,没有虚拟机会使用上面的那种复制算法,所以接下来要讲的,就是真正的复制算法
这个算法的思路和刚才讲的一样,不过这个算法将内存分为3块区域:1块Eden区,和2块Survivor区,其中,Eden区要占到80%
这两块Survivor区就可以理解为我们刚才提到的S1和S2两块区域,我们每次只使用整个Eden区和其中一块Survivor区,整个算法的流程可以简要概括为:
1.当发生垃圾回收时,将Eden区+Survivor区中仍然存活的对象一次性复制到另一块Survivor区上
2.清理掉Eden区和使用的Survivor区中的所有对象
3.交换两块Survivor的激活状态
光看文字描述比较抽象,我们来看图像的形式:
1.我们有以下这样的一块Java堆,其中灰色的Survivor区为激活状态
2.标记所有的GC Roots可达对象(蓝色标记)
3.将标记对象全部复制到另一块Survivor区域中
4.清理掉Eden区和激活的Survivor区中的所有对象,然后交换两块区域的激活状态
以上就是整个复制算法的全过程了,有人可能会问了,为什么Survivor区这么小,就不怕放不下吗?其实平均来说,每次垃圾回收的时候基本都会回收98%左右的对象,也就是说,我们完全可以保证大部分情况下剩余的对象都小于10%,放在一块Survivor区中是没问题的。当然,也可能会发生Survivor区不够用的问题,这时候就需要依赖其他内存给我们提供后备了
这种算法较好地解决了内存利用率低的问题,但是复制算法的两个问题依然没有解决:
- 对象复制采用深度优先的递归方式来实现,会消耗栈资源(Cheney改进的GC复制算法解决了该问题)
- 复制算法无法处理长寿数据,只会频繁地将其复制来白白消耗资源(重点)
标记-整理算法
这种算法可以说是专门针对对象存活率高的程序,具体的流程如下:
1.GC发生时,将所有被标记的存活对象移动到内存的一端
2.移动完成后,清理掉所有移动后的边界以外的对象
我相信大家在理解了前面几个算法之后,这个算法也能很方便地理解,我就不画图了,用一个例子来解释:
问题:对于一个长度为n的数组,我们想要保留其中所有小于10的数字,其余的数字删掉
方案:可以遍历一遍数据,将所有小于10的数字全部放到数组的最左侧,最终,数组的0~m(0<=m<=n)位置全部都是小于10的数字,然后我们只需要删除m+1~n的所有数字即可
种方法的优点也显而易见:
- 实现简单,执行速度快
- 针对复制算法处理不佳的长寿数据,标记-整理算法可以选择不去整理这些对象
- 没有空间碎片的问题
但是依然还是有缺点的:
- 如果堆内存较小,则该算法的速度会下降
- 遍历时需要多次访问类型信息和对象的指针域,开销很大
- 记录新的转发地址需要占用额外的空间,导致吞吐量下降
- 不适合并发回收器
分代收集算法
别急,我们还没说完,还有最后一个分代收集算法,这个算法将Java堆划分为两块区域:
- 年轻代:存放朝生夕灭的对象,即存活率低的对象,大部分对象在一次GC后都会被回收
- 老年代:存放存活率高的对象
可以看出,分代收集算法按照对象在GC后的存活率将Java堆分为这样两块区域,针对不同区域采用不同的算法,就能尽可能地做到“扬长补短”,来提高垃圾回收的效率
- 针对年轻代朝生夕灭的性质,我们采用复制算法
- 针对老年代存活率高的性质,我们采用标记-整理算法
总结
最后,垃圾回收的几种常见算法已经为大家介绍完毕,接下来如果有机会我会再介绍一下几种常见的垃圾回收器