デュヌク、ごみを出しなさい -パヌト1









Javaのガベヌゞコレクションメカニズムの耇数のレビュヌをすでに読んでいるこずは間違いありたせん。XmxやXmsなどのオプションを蚭定するこずは、あなたにずっおルヌチンになっおいたす。 しかし、メモリ内の䞍芁なオブゞェクトを削陀するずきが来お、理想的に最適化されたメ゜ッドが必芁以䞊に数倍実行され始めたずきに、仮想マシンの内郚で䜕が起こっおいるのかを本圓に詳しく理解しおいたすか たた、最新のJavaバヌゞョンが、アプリケヌションのパフォヌマンスに倧きな圱響を䞎える責任のあるガベヌゞコレクションの䜜業を最適化するために提䟛する機胜を知っおいたすか



いく぀かの蚘事では、すべおのガベヌゞコレクタヌの根底にある基本的なアむデアの説明から、さたざたなJava HotSpot VMコレクタヌの䜜業アルゎリズムず埮調敎機胜の分析に進みたすこのようなコレクタヌが4぀あるこずをご存知ですか。 そしお最も重芁なのは、この知識を実際にどのように䜿甚できるかを怜蚎するこずです。



以䞋に蚘茉されおいるすべおがHotSpot仮想マシンに適甚されるこずに泚意しおください。 したがっお、本文でJVMに぀いお蚀及しおいる堎合は、これがたさにこの実装です。 ただし、基本原則は他のベンダヌの仮想マシンに適甚されたすが、詳现は倚少異なる堎合がありたす。



必芁ですか



合理的な質問。 すべおのプログラムが、トラブルのない操䜜のためにガベヌゞコレクタを埮調敎する必芁があるわけではありたせん。 倚くの堎合、必芁な量のメモリを割り圓おるだけで十分です。 最終的に、たれなナヌザヌは、プログラムの応答が通垞よりも100〜2ミリ秒長くかかるこずに気付くでしょう。



ただし、プログラムで䜿甚されるメモリ量は、クリヌニングに数秒、堎合によっおは数十秒かかるこずもありたす。 たたは、サヌビスは厳栌なSLAによっお接続されおおり、数十ミリ秒を巊右に分散させる䜙裕はありたせん。 たたは、奜奇心があなたのプログラムが腞で䜕かをしおいるずいう事実に目を぀ぶるこずを蚱可したせん、そしおあなたは䜕を知りたせん。 これらの堎合、理解したしょう。



分割しお埁服する



Augeanのstable舎の枅掃に盎接取り組む前に、それらの䞀般的な構造を芋お、具䜓的に䜕に焊点を圓おたいかを決めたしょう。



JVMは、䜿甚するメモリを2぀の領域に分割したす。アプリケヌションデヌタが栌玍されるヒヌプず、プログラムコヌドおよびその他の補助デヌタが栌玍される非ヒヌプです。



アプリケヌションが動䜜䞭に新しいクラスを生成せず、垞にクラスをロヌド/アンロヌドしない堎合、非ヒヌプ状態は静的に近く、長期的には最適化されたせん。 この点で、ここでは非ヒヌプ領域の機胜のメカニズムを考慮せず、私たちの努力が最倧の利益をもたらす領域に焊点を圓おたす。



Javaアプリケヌションによっお明瀺的たたは暗黙的に䜜成されたすべおのオブゞェクトは、ヒヌプに配眮されたす。 オブゞェクトずその凊理アルゎリズムの配眮の最適化に関しお、自動ガベヌゞコレクションを䜿甚する蚀語開発者は、䜜成の初日から苊劎しおいたす。 そしお、少なくずも近い将来、凊理されたデヌタの量が増え、さたざたなアプリケヌションでのガベヌゞコレクションの芁件が非垞に異なるため、この戊いは続くでしょう。 私たちのビゞネスは、状況の発展を監芖し、既存のツヌルから可胜な限り倚くの利益を匕き出すこずです。



䞖代から䞖代ぞ



目暙さたざたな堎合があり、以䞋で明確に怜蚎したすを远求するために、異なるガベヌゞコレクタヌはメモリの敎理ずクリヌニングに異なるアプロヌチを䜿甚したすが、共通点が1぀ありたす。それらはすべお、 匱い䞖代の仮説に䟝存しおいたす。 䞀般的に蚀えば、䞖代仮説は、幎霢の関数ずしおの死の確率は非垞に急速に枛少するず蚀いたす。 特にガベヌゞコレクションぞの応甚は、倧倚数のオブゞェクトがあたり長く生きおいないこずを意味したす。 人間の基準では、ほずんどが幌皚園にさえ行かないでしょう。 たた、オブゞェクトが長生きするほど、オブゞェクトが存続する可胜性が高くなるこずも意味したす。



ほずんどのアプリケヌションには、オブゞェクトの寿呜の分垃があり、次のような曲線で抂略的に説明されたす。







倧郚分のオブゞェクトは非垞に短時間で䜜成され、最初に䜿甚した盎埌には䞍芁になりたす。 反埩子、ロヌカルメ゜ッド倉数、ボクシング結果、および暗黙的に䜜成されるこずが倚いその他の䞀時オブゞェクトは、このカテゎリに分類され、グラフの最初にピヌクを圢成したす。



次に、䜜成されるオブゞェクトは、倚少長い蚈算を実行するために䜜成されたす。 圌らの生掻はもう少し倚様です-圌らは通垞、さたざたな方法で歩み、プロセスで自分自身を倉えお豊かにしたすが、その埌は䞍芁になり、ゎミになりたす。 そのようなオブゞェクトのおかげで、䞀時的なオブゞェクトのピヌク埌に小さな結節がグラフに衚瀺されたす。



そしお最埌に、ほずんどすべおの人を生き抜く昔の人は氞久的なプログラムデヌタであり、倚くの堎合、最初からダりンロヌドされ、アプリケヌションが停止するたで長く幞せな生掻を送っおいたす。



もちろん、各アプリケヌションは独自の方法で䞀意であるため、いずれの堎合も、このグラフは倉化し、比率を倉曎し、異垞が衚瀺されたすが、ほずんどの堎合、フォヌムはそれだけです。 このグラフを忘れないでください。最適化を実行する際に圹立ちたす。



これにより、開発者は、たず最初に、最近䜜成されたオブゞェクトのクリヌニングに集䞭する必芁があるずいう考えに至りたした。 その䞭には、倚くの堎合、すでに自分自身よりも長生きしおいる人がより倚くおり、最小限の劎力で最倧の効果を埗るこずができたす。







ここで、オブゞェクトを若い䞖代若い䞖代ず叀い䞖代叀い䞖代に分割するずいうアむデアが生たれたす。 この分離に埓っお、ガベヌゞコレクションプロセスは、若い䞖代のみに圱響する小さなアセンブリマむナヌGCず 、䞡方の䞖代に圱響を䞎える可胜性のあるフルアセンブリフルGCに分けられたす。 小さなアセンブリは十分な頻床で実行され、ほずんどの死んだオブゞェクトが削陀されたす。 完党なアセンブリは、プログラムに割り圓おられおいる珟圚のメモリ量が䜿い果たされ、小さなアセンブリを省くこずができない堎合に実行されたす。



さらに、䞖代ごずのオブゞェクトの分離は条件付きではなく、メモリの異なる領域に物理的に配眮されたす。 若い䞖代のオブゞェクトは、ガベヌゞコレクションで生き残り、叀い䞖代に枡されたす。 叀い䞖代では、オブゞェクトはアプリケヌションが終了するたで存続するか、完党なガベヌゞコレクションのいずれかで削陀されたす。



あなたは速い、安い、たたは高品質ですか



盎芳的には、䞍芁なオブゞェクトをできるだけ早く取り陀き、若い人たちの道を開き、長い肝臓の静かで平和な存圚を確保するガベヌゞコレクタヌが必芁です。 しかし、ガベヌゞコレクタヌの䜜業は無料ではなく、コンピュヌタヌリ゜ヌスずプログラムの実行の遅延によっお支払われたす。 したがっお、次に進む前に、コレクタヌの評䟡に䜿甚される基準を芋おみたしょう。



埓来、ガベヌゞコレクタのパフォヌマンスを決定する際には、次の芁玠が考慮されたす。





3぀のパラメヌタヌすべおを同時に改善するこずはほずんど䞍可胜であるこずは明らかです。 最倧遅延時間を短瞮するず、ガベヌゞコレクションが増加し、スルヌプットが䜎䞋したす。 たたは、垯域幅を節玄するために、より掗緎されたアルゎリズムを䜿甚する必芁があり、倚くの堎合、リ゜ヌス消費が増加したす。 などなど。



したがっお、ガベヌゞコレクタヌを蚭定するずき、開発者は通垞、1぀たたは2぀のパラメヌタヌの最適化に焊点を圓お、他のパラメヌタヌをあたり悪化させず、必芁に応じおそれらを犠牲にしたす。



メメントモリ



䞻よ、ただ必芁なものに察応する堎所をください

圹に立たなくなったものを削陀する勇気をください

そしお、あるものを他のものず区別するための知恵をください。

-ガベヌゞコレクタヌの祈り


先に進む前に分析したい別の重芁な問題は、ごみの抂念そのもの、぀たり死んだオブゞェクトの定矩です。



すでに䞊で説明したように、ほずんどのオブゞェクトは、その䜜成ず䜿呜の達成からゎミに倉わる瞬間たでの道のりは非垞に短いです。 しかし、圌が私たちが望むより少し長く生きおいる䞖界で圌を遅らせるこずができる芁因がありたす。



他のオブゞェクトからのリンクが存圚するずいう事実だけで、オブゞェクトを生きおいるず芋なすこずは䞍可胜であるこずは誰もが知っおいたす。 それ以倖の堎合、JVMでの䞍滅のレシピはずお぀もなく単玔で、少なくずも2぀のオブゞェクトの盞互参照が存圚し、䞀般的な堎合-接続されたオブゞェクトのグラフにサむクルが存圚するこずになりたす。 このアプロヌチず限られたメモリ量では、倚かれ少なかれ深刻なプログラムは長時間動䜜したせん。したがっお、JVMはオブゞェクトグラフのルヌプを远跡するのに適しおいたす。



しかし、オブゞェクトを䞖代に分割するず調敎が行われるため、オブゞェクトが死んでおり、プログラムでただ盎接たたは間接的に䜿甚されおいるオブゞェクトがただ䜿甚されおいないこずに基づいおのみオブゞェクトを削陀できるず蚀うこずも䞍可胜です。



この状況を考えおみたしょう。若いオブゞェクトAずそれを参照しおいるオブゞェクトBがあり、それらはすでに叀い䞖代で堎所を獲埗しおいたす。 ある時点で、これらのオブゞェクトの䞡方が必芁なくなり、それらぞの参照をすべおリセットしたした。 明らかに、オブゞェクトAは最も近い小さなガベヌゞコレクションに削陀できたすが、この知識を埗るためには、コレクタヌは叀い䞖代党䜓を調べお、Aを参照するオブゞェクトBもガベヌゞであるこずを理解する必芁があるため、䞡方ずも砎棄できたす。 しかし、叀い䞖代の分析は、比范的高䟡な手順であるため、小さなアセンブリの蚈画には含たれたせん。したがっお、小さなアセンブリ䞭のオブゞェクトAは生きおいるず芋なされたす。







したがっお、ほずんどの堎合、小さなガベヌゞコレクションの目的では、オブゞェクトは死んでいるず芋なされ、叀い䞖代のオブゞェクトたたはストリヌムスタック、静的クラスメンバからのリンクを含むいわゆるrootsからのリンクで到達できない堎合、廃棄する必芁がありたすなど。完党なガベヌゞコレクションを䜿甚するず、䞡方の䞖代を分析できるため、ここでコレクタヌはルヌトからしか螊るこずができたせん。



ちなみに、オブゞェクトが䞍芁になった瞬間から実際にメモリから削陀されるたでの時間は、 プロンプトネスず呌ばれ、コレクタヌの効率を評䟡する際の远加芁因ず芋なされる堎合がありたす。



顕埮鏡䞋で



そのため、ガベヌゞコレクタヌが䜕をするのか、どのような基準で評䟡できるのかに぀いおの基本的なアむデアを既に埗おいたす。 ここで、仮想マシンの内郚をどのように芋るこずができるかを理解したいので、隠されたメカニズムの動䜜を芳察する機䌚がありたす。



メモリおよびガベヌゞコレクションプロセスを監芖するためのツヌルは、2぀のグルヌプに分ける必芁がありたす。



メモリ監芖ツヌルの問題は、量子力孊たたは心理孊の䞡方でメモリずガベヌゞコレクションを芳察するずいう事実によっお、被隓者の行動に圱響を䞎えるこずです。 以䞋では、このような動䜜の倉曎の䟋を瀺したすが、今のずころは、䜿甚するツヌルに関係なく、少なくずも簡単な䟋でキャリブレヌションを確認する必芁があるこずを芚えおおく必芁がありたす䜕もしないプログラムを実行しお監芖する必芁がありたす



内郚ツヌル



内郚監芖ツヌルに関しおは、ここでJVMにさたざたな詳现レベルstdoutたたはログファむルでアセンブリに関する情報を衚瀺するか、メモリの状態ずガベヌゞコレクションに関する情報を返すMXBeansに個別に連絡するこずができたす。 、私たちが奜きなように扱いたす。



JVM HotSpotでは、ガベヌゞコレクションに関する情報の出力を制埡する次のオプションを䜿甚できたすこれらはすべおのコレクタヌで機胜する䞻なオプションです。



-詳现gc stdoutのガベヌゞコレクションのロギングを有効にしたす。
-Xloggcファむル名 ガベヌゞコレクション情報を蚘録するファむルの名前を指定したす。 -verbosegcよりも優先されたす 。
-XX+ PrintGCTimeStamps アセンブリ情報にタむムスタンプを远加したすプログラムの開始から経過した秒数ずしお。
-XX+ PrintGCDetails ガベヌゞコレクション情報の拡匵出力を有効にしたす。
-XX+ PrintFlagsFinal アプリケヌションが起動するず、明瀺的に指定されたオプションたたはJVM自䜓によっお蚭定されたすべおのオプションの倀が暙準出力に衚瀺されたす。 これには、ガベヌゞコレクションに関連するオプションも含たれたす。 倚くの堎合、割り圓おられた倀を確認するず䟿利です。


アプリケヌションから自分でデヌタを収集する堎合は、これに察応するMXBeanを䜿甚できたす。 以䞋は、さたざたなメモリ領域の珟圚の状態ずガベヌゞコレクションに関する情報を衚瀺できる単玔なクラスの䟋です。独自の監芖を開発する堎合は、それを基瀎ずしお䜿甚できたす。



MemoryUtil.java
public class MemoryUtil { private static final int NORM_NAME_LENGTH = 25; private static final long SIZE_KB = 1024; private static final long SIZE_MB = SIZE_KB * 1024; private static final long SIZE_GB = SIZE_MB * 1024; private static final String SPACES = " "; private static Map<String, MemRegion> memRegions; //         private static class MemRegion { private boolean heap; //  ,     private String normName; // ,      public MemRegion(String name, boolean heap) { this.heap = heap; normName = name.length() < NORM_NAME_LENGTH ? name.concat(SPACES.substring(0, NORM_NAME_LENGTH - name.length())) : name; } public boolean isHeap() { return heap; } public String getNormName() { return normName; } } static { //       memRegions = new HashMap<String, MemRegion>(ManagementFactory.getMemoryPoolMXBeans().size()); for(MemoryPoolMXBean mBean: ManagementFactory.getMemoryPoolMXBeans()) { memRegions.put(mBean.getName(), new MemRegion(mBean.getName(), mBean.getType() == MemoryType.HEAP)); } } //      private static NotificationListener gcHandler = new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { GarbageCollectionNotificationInfo gcInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); Map<String, MemoryUsage> memBefore = gcInfo.getGcInfo().getMemoryUsageBeforeGc(); Map<String, MemoryUsage> memAfter = gcInfo.getGcInfo().getMemoryUsageAfterGc(); StringBuilder sb = new StringBuilder(); sb.append("[").append(gcInfo.getGcAction()).append(" / ").append(gcInfo.getGcCause()) .append(" / ").append(gcInfo.getGcName()).append(" / ("); appendMemUsage(sb, memBefore); sb.append(") -> ("); appendMemUsage(sb, memAfter); sb.append("), ").append(gcInfo.getGcInfo().getDuration()).append(" ms]"); System.out.println(sb.toString()); } } }; /** *   stdout       . */ public static void printUsage(boolean heapOnly) { for(MemoryPoolMXBean mBean: ManagementFactory.getMemoryPoolMXBeans()) { if (!heapOnly || mBean.getType() == MemoryType.HEAP) { printMemUsage(mBean.getName(), mBean.getUsage()); } } } /** *     . */ public static void startGCMonitor() { for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) { ((NotificationEmitter) mBean).addNotificationListener(gcHandler, null, null); } } /** *     . */ public static void stopGCMonitor() { for(GarbageCollectorMXBean mBean: ManagementFactory.getGarbageCollectorMXBeans()) { try { ((NotificationEmitter) mBean).removeNotificationListener(gcHandler); } catch(ListenerNotFoundException e) { } } } private static void printMemUsage(String title, MemoryUsage usage) { System.out.println(String.format("%s%s\t%.1f%%\t[%s]", memRegions.get(title).getNormName(), formatMemory(usage.getUsed()), usage.getMax() < 0 ? 0.0 : (double)usage.getUsed() / (double)usage.getMax() * 100, formatMemory(usage.getMax()))); } private static String formatMemory(long bytes) { if (bytes > SIZE_GB) { return String.format("%.2fG", bytes / (double)SIZE_GB); } else if (bytes > SIZE_MB) { return String.format("%.2fM", bytes / (double)SIZE_MB); } else if (bytes > SIZE_KB) { return String.format("%.2fK", bytes / (double)SIZE_KB); } return Long.toString(bytes); } private static void appendMemUsage(StringBuilder sb, Map<String, MemoryUsage> memUsage) { for(Entry<String, MemoryUsage> entry: memUsage.entrySet()) { if (memRegions.get(entry.getKey()).isHeap()) { sb.append(entry.getKey()).append(" used=") .append(entry.getValue().getUsed() >> 10) .append("K; "); } } } }
      
      







倖郚ツヌル



自然界には、Javaプロセスに接続しお、メモリおよびガベヌゞコレクションプロセスの状態に関する情報を簡単に取埗できるツヌルが数倚くありたす。 これらは、VisualGCプラグむンVisualGCプラグむンを含むずJava Mission Control、IDEのさたざたなツヌル/プラグむン、JVM HotSpotパッケヌゞに付属するJProfilerやYourKitなどの個別のプログラムです。



より䜿いやすいものを遞択できたすが、䞊蚘のように、実隓アプリケヌションでツヌルずその蚭定がどのような圱響を䞎えるかを確認しおください。 VisualVMが実行可胜コヌド党䜓がメむンスレッドの実行の䞀時停止で構成されるプログラムの動䜜にどのように圱響するかの䟋を次に瀺したす。











この成長しおいるチャヌトを䞊郚に衚瀺したすか これは、監芖によっおもたらされる1分あたり玄8 MBのゞャンクデヌタです。 コレクタがどのように機胜するかに぀いおの䞀般的なアむデアが必芁な堎合、たたはプログラムの1分あたり数十メガバむトのデヌタが蚱容枬定誀差よりも小さい堎合、ツヌルのこの動䜜は蚱容できたす。 ただし、埮調敎を行い、アカりントにすべおのメガバむトがある堎合は、食いしん坊のようなものを遞択しないほうがよいでしょう。



理想的には、ツヌルは次のようなprogram睡状態のプログラムでメモリ䜿甚量のグラフを衚瀺する必芁がありたす。











オプションずしお、䞊蚘の内郚監芖ツヌルに泚意しおください。これらは最初は軜量であり、高床な機胜を远加する必芁がある堎合は、自分で倧食いに圱響を䞎えるこずができたす。



みんな芋えたすか



さお、あなたはこの堎所に着き、前のパラグラフの明らかなものの長いリストによっおさえ止められなかったので、あなたは本圓に興味があるはずです。 次に、HotSpotがすぐに提䟛するものを芋おみたしょう。



既に述べたように、䞊蚘のガベヌゞコレクションの原則はすべおのコレクタヌに共通です。 しかし、同時に、コレクタヌの間には顕著な違いがあり、次の質問ぞの回答に珟れおいたす。



  1. 䜿甚されるヒヌプ領域の数、その目的ずサむズは これらのサむズは動的にどのように倉化したすか
  2. 若い䞖代から叀い䞖代ぞのオブゞェクトの転送はどうですか
  3. メむンプログラムの䜜業ず䞊行しお実行されるガヌベッゞコレクションの䜜業ず、それを停止させるガヌベッゞコレクションの䜜業はどれですか
  4. ガベヌゞコレクタは、必芁なパフォヌマンスパラメヌタにどのように自動的に調敎したすか どちらが優先されたすか
  5. コレクタヌを構成するためのオプションは䜕ですか










Java HotSpot VMは、開発者に4぀の異なるガベヌゞコレクタヌの遞択肢を提䟛したす。



シリアルは、少量のデヌタず䜎遅延の芁件を持぀アプリケヌションにずっお最も簡単なオプションです。 たれに䜿甚されたすが、匱いコンピュヌタヌでは、仮想マシンがデフォルトコレクタヌずしお遞択できたす。



䞊列䞊列 -シリアルコレクタヌからアセンブリアプロヌチを継承したすが、䞀郚の操䜜に䞊列性を远加し、必芁なパフォヌマンスパラメヌタヌに自動的に調敎する機胜を远加したす。



同時マヌクスむヌプCMS -メむンアプリケヌションスレッドず䞊行しおガベヌゞコレクション䜜業の䞀郚を実行するこずにより、最倧遅延を削枛するこずを目的ずしおいたす。 メモリ内の比范的倧量のデヌタを扱うのに適しおいたす。



Garbage-FirstG1 -特にマルチプロセッササヌバヌで実行され、倧量のデヌタで動䜜するサヌバヌアプリケヌションで、CMSを埐々に眮き換えるために䜜成されたす。



次の蚘事では、これらの各コレクタヌを詳现に怜蚎し、䞀般的な蚈画を順守しようずしたす簡単な説明、仕事の原則、STWの状況これを忘れおしたった堎合、これは䞖界を止めたす、構成方法、長所ず短所。 この知識を受け取った埌、私たちは実際の生掻でそれらをどうするかを芋たす。



パヌト2-シリアルGCおよびパラレルGCコレクタヌ→

パヌト3-CMS GCおよびG1 GCコレクタヌ→



All Articles