प्रतिनिधि इंटरव्यू विषय

JVM इंटरव्यू: आप लंबे गार्बेज-कलेक्शन पॉज़ का निदान और समाधान कैसे करते हैं?

सामान्यकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

एक Java सर्विस HotSpot JDK 25 पर G1 और -Xms12g -Xmx12g के साथ चलती है। एक कैश फ़ीचर रिलीज़ होने के बाद, रिक्वेस्ट p99 हर 3–5 मिनट में 120 मिलीसेकंड से बढ़कर 2–4 सेकंड हो जाता है। स्पाइक्स के दौरान CPU संतृप्त (saturated) नहीं होता है, कलेक्शन के बाद ओल्ड-जनरेशन ऑक्यूपेंसी दो घंटों में 6.1 GiB से बढ़कर 9.2 GiB हो जाती है, और एलोकेशन रेट लगभग 600 MiB/s से बढ़कर 1.4 GiB/s हो जाती है। आप यह कैसे साबित करेंगे कि स्टॉल्स GC के कारण हो रहे हैं या नहीं, उच्च एलोकेशन रेट, लाइव-सेट ग्रोथ, ह्यूमंगस ऑब्जेक्ट्स, स्पष्ट GC (explicit GC) और नॉन-GC सेफ़पॉइंट्स में अंतर कैसे करेंगे, और एक कम जोखिम वाली निवारण और सत्यापन योजना कैसे डिज़ाइन करेंगे?

समस्या और लागू परिदृश्य

एक लेटेंसी-संवेदनशील Java API HotSpot JDK 25 पर G1 और एक निश्चित 12 GiB हीप के साथ चलता है: -Xms12g -Xmx12g। एक कैश फ़ीचर जारी होने के बाद, रिक्वेस्ट p99 हर 3–5 मिनट में 120 मिलीसेकंड से बढ़कर 2–4 सेकंड हो जाता है। स्पाइक्स के दौरान होस्ट CPU संतृप्त नहीं होता है। मॉनिटरिंग यह भी दिखाती है कि कलेक्शन के बाद ओल्ड-जनरेशन ऑक्यूपेंसी दो घंटों में 6.1 GiB से बढ़कर 9.2 GiB हो जाती है, जबकि एप्लिकेशन की एलोकेशन रेट लगभग 600 MiB/s से बढ़कर 1.4 GiB/s हो जाती है। टीम इस घटना को "GC समस्या" करार देती है और पहले हीप को 24 GiB तक बढ़ाने का प्रस्ताव करती है।

सभी संख्याएँ इंटरव्यू की धारणाएँ (assumptions) हैं। उम्मीदवार को यह समझाना होगा कि यह कैसे साबित किया जाए कि लेटेंसी स्पाइक्स वास्तव में JVM पॉज़ के अनुरूप हैं या नहीं; उच्च एलोकेशन रेट, लाइव-सेट ग्रोथ, G1 ह्यूमंगस ऑब्जेक्ट्स, स्पष्ट System.gc() और नॉन-GC सेफ़पॉइंट्स में अंतर करने के लिए एकीकृत GC लॉगिंग, Java Flight Recorder (JFR) और बाउंडेड हीप डायग्नोस्टिक्स का उपयोग कैसे किया जाए; और एक प्रतिवर्ती (reversible) निवारण और सत्यापन योजना कैसे बनाई जाए। इसका उद्देश्य केवल कलेक्टर फ़्लैग्स को रटना नहीं है। इसका उद्देश्य लक्षण से साक्ष्य, परिकल्पना, प्रयोग और SLO तक एक कारणात्मक श्रृंखला (causal chain) का निर्माण करना है।

सार्वजनिक Java GC इंटरव्यू सामग्री स्टॉप-द-वर्ल्ड (stop-the-world) इवेंट्स और JVM पॉज़ टाइम्स को मुख्य विषयों के रूप में मानती है। लंबे पॉज़ की समस्या निवारण के लिए Oracle का HotSpot मार्गदर्शन सीधे तौर पर अपर्याप्त हीप, हीप विखंडन (fragmentation), ऑपरेटिंग-सिस्टम गतिविधि और स्पष्ट GC को कवर करता है। वे स्रोत इसे एक प्रतिनिधि JVM परफ़ॉर्मेंस-डायग्नोस्टिक्स प्रश्न बनाते हैं। कोई भी विश्वसनीय कंपनी एट्रिब्यूशन सार्वजनिक नहीं है, इसलिए companyName को खाली छोड़ दिया गया है।

इंटरव्यूअर क्या जांच रहा है

पहला संकेत यह है कि क्या उम्मीदवार ट्यूनिंग करने से पहले टाइमलाइन को संरेखित (align) करता है या नहीं। रिक्वेस्ट p99, JVM GC लॉग्स, JFR इवेंट्स, कंटेनर CPU थ्रॉटलिंग, डिस्क इवेंट्स और होस्ट मेट्रिक्स को एक ही समय आधार की आवश्यकता होती है। एक सॉटूथ (sawtooth) हीप ग्राफ़ केवल यह साबित करता है कि कलेक्शन हुआ था; यह यह साबित नहीं करता कि तीन सेकंड का एक विशेष रिक्वेस्ट स्पाइक GC के कारण हुआ था। इसके विपरीत, G1 पर्याप्त समवर्ती (concurrent) कार्य करता है, इसलिए लंबे GC चक्र का मतलब यह नहीं है कि एप्लिकेशन पूरे चक्र के लिए रुकी हुई थी।

दूसरा संकेत यह है कि क्या उम्मीदवार अत्यधिक लंबे व्यक्तिगत पॉज़ को अत्यधिक कुल पॉज़ अनुपात (total pause ratio) से अलग करता है। पूर्व के लिए पॉज़ प्रकार और फ़ेज़ टाइमिंग, पहले/बाद का लाइव डेटा, कॉपी किए गए बाइट्स और OS टाइमिंग की आवश्यकता होती है। उत्तरार्द्ध अक्सर एलोकेशन रेट और कलेक्शन आवृत्ति को ट्रैक करता है। केवल औसत GC अवधि दुर्लभ टेल पॉज़ और बड़ी संख्या में छोटे पॉज़ दोनों को छुपाती है।

तीसरा संकेत यह है कि क्या साक्ष्य ही निवारण (remediation) का चयन करता है। एलोकेशन प्रेशर के लिए एलोकेशन हॉटस्पॉट्स खोजने की आवश्यकता होती है। GC के बाद बढ़ते ओल्ड बेसलाइन के लिए यह साबित करने की आवश्यकता होती है कि अवांछित ऑब्जेक्ट्स रीचेबल बने हुए हैं। Humongous regions में वृद्धि आधे G1 रीजन को पार करने वाले ऑब्जेक्ट्स को ट्रेस करने की मांग करती है। System.gc() का लॉग कारण कॉलर को खोजने की मांग करता है। "हीप बढ़ाएँ, पॉज़ लक्ष्य कम करें, या ZGC पर स्विच करें" ऐसा निदान नहीं है जो हर लक्षण पर फिट बैठता हो।

अंत में, इंटरव्यूअर प्रोडक्शन जोखिम जागरूकता का परीक्षण कर रहा है। JFR हीप आँकड़े अतिरिक्त ओल्ड कलेक्शन्स को ट्रिगर करते हैं। Oracle jcmd GC.class_histogram और हीप डंप को उच्च-प्रभाव वाले ऑपरेशन्स के रूप में चिह्नित करता है, और एक हीप डंप डिफ़ॉल्ट रूप से एक Full GC का अनुरोध करता है। एक मजबूत उत्तर पहले कम-ओवरहेड साक्ष्य एकत्र करता है, एक प्रतिकृति (replica) पर, ऑफ़-पीक, या एक नियंत्रित वातावरण में भारी साक्ष्य प्राप्त करता है, और यह साबित करने के लिए समान-लोड कैनरी का उपयोग करता है कि कम पॉज़ अस्वीकार्य थ्रूपुट, CPU या मेमोरी की कीमत पर नहीं खरीदे गए थे।

उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न

  • सटीक रूप से "स्टॉल्ड" क्या है? क्या यह सर्वर हैंडलर लेटेंसी है, क्लाइंट एंड-टू-एंड लेटेंसी है, सभी Java थ्रेड्स कोई प्रगति नहीं कर रहे हैं, या केवल कुछ रिक्वेस्ट कतारबद्ध (queueing) हैं? क्या स्पाइक्स एक इंस्टेंस तक सीमित हैं, और क्या लोड बैलेंसर, डिपेंडेंसीज़ या नेटवर्क एक ही घटना दिखाते हैं?
  • क्या लॉग और मेट्रिक्स को सटीक रूप से सहसंबंधित (correlate) किया जा सकता है? हमें समान इंस्टेंस ID, UTC टाइमस्टैम्प, JVM अपटाइम और रिलीज़ वर्ज़न की आवश्यकता है। यदि लॉग में केवल मिनट-स्तरीय टाइमस्टैम्प हैं, तो केवल समान दिखने वाले दो कर्व्स से निष्कर्ष निकालने से पहले ऑब्जर्वेबिलिटी में सुधार करें।
  • G1 लॉग में क्या शामिल है? कम से कम, GC ID, कारण, पॉज़ प्रकार, पहले और बाद का हीप, और अवधि बनाए रखें। आवश्यकता पड़ने पर gc+heap, gc+phases और gc+cpu को सक्षम करें। केवल प्रोसेस RSS या हीप प्रतिशत अपर्याप्त है।
  • क्या तुलनीय लोड के तहत पोस्ट-कलेक्शन ओल्ड-जनरेशन बेसलाइन बढ़ती रहती है? कैश वार्म-अप के बाद एक स्थिर स्तर (plateau) अपेक्षित लाइव सेट हो सकता है। कम रिक्लेम किए गए बाइट्स के साथ निरंतर वृद्धि रिटेंशन या मेमोरी लीक के अधिक सुसंगत है।
  • कंटेनर और होस्ट पर कौन सी सीमाएं लागू होती हैं? CPU कोटा और थ्रॉटलिंग, स्वैप, पेज फ़ॉल्ट, मेमोरी प्रेशर, ब्लॉकिंग लॉग I/O और नॉइज़ी नेबर्स की जाँच करें। कम CPU समय के साथ लंबा GC वॉल टाइम (wall time) का मतलब यह हो सकता है कि JVM को डीशेड्यूल किया गया था या उसके पेज स्वैप आउट हो गए थे।
  • रिलीज़ में कौन से एलोकेशन और संदर्भ संबंध बदल गए? कैश क्षमता, TTL, कुंजी और मान आकार, सीरियलाइज़ेशन बफ़र्स, बैच आकार, कंकरेंसी, और ThreadLocals, श्रोताओं (listeners), कतारों या स्थिर संग्रहों के माध्यम से रिटेंशन का निरीक्षण करें।
  • लेटेंसी और थ्रूपुट लक्ष्य क्या हैं? पॉज़ p99 और अधिकतम, रिक्वेस्ट p99, थ्रूपुट, त्रुटि दर, CPU और मेमोरी सीमाएँ परिभाषित करें। -XX:MaxGCPauseMillis G1 के लिए एक लक्ष्य संकेत है, प्रत्येक पॉज़ पर एक कठिन सीमा नहीं।
  • क्या भारी साक्ष्य सुरक्षित रूप से एकत्र किए जा सकते हैं? यदि केवल एक प्रोडक्शन इंस्टेंस मौजूद है, तो पहले क्षमता जोड़ें या ट्रैफ़िक को ड्रेन करें। घटना के दौरान 12 GiB हीप को डंप न करें और फिर डायग्नोस्टिक Full GC को मूल विफलता समझने की गलती न करें।

30-सेकंड उत्तर ढाँचा

"मैं पहले हीप में बदलाव नहीं करूँगा। मैं प्रत्येक रिक्वेस्ट स्पाइक को, इंस्टेंस और टाइमस्टैम्प द्वारा, -Xlog:gc*, JFR jdk.GCPhasePause इवेंट्स, CPU थ्रॉटलिंग, पेज फ़ॉल्ट और डिपेंडेंसी लेटेंसी के साथ सहसंबंधित करूँगा ताकि यह मापा जा सके कि एप्लिकेशन वास्तव में कितनी देर रुकी थी। फिर मैं साक्ष्य को तीन समूहों में विभाजित करूँगा: प्रत्येक पॉज़ का प्रकार और फ़ेज़; प्रति समय विंडो कुल पॉज़ अनुपात; और एलोकेशन रेट, प्रमोशन रेट, GC के बाद ओल्ड-ऑक्यूपेंसी और ह्यूमंगस रीजन्स में रुझान।

यदि कैश एलोकेशन बढ़ाता है लेकिन पोस्ट-कलेक्शन बेसलाइन स्थिर है, तो मैं एलोकेशन हॉटस्पॉट खोजने और अस्थायी ऑब्जेक्ट्स को कम करने के लिए JFR का उपयोग करूँगा। यदि ओल्ड-आफ़्टर-GC बढ़ता रहता है, तो मैं क्लास हिस्टोग्राम की तुलना करूँगा और डोमिनेटर और रिटेंशन पथों का निरीक्षण करने के लिए एक नियंत्रित प्रतिकृति पर एक हीप डंप प्राप्त करूँगा। यदि Full GCs से पहले इवैक्यूएशन विफलताएं या कई ह्यूमंगस रीजन्स आते हैं, तो मैं बड़े एरेज़, बफ़र्स और बैचों का निरीक्षण और विभाजन करूँगा। यदि कारण System.gc() है, तो मैं कॉलर की पहचान करूँगा। रोलआउट से पहले प्रत्येक परिवर्तन का परीक्षण समान-लोड कैनरी में रिक्वेस्ट p99, पॉज़ p99 और अधिकतम, कुल पॉज़ अनुपात, एलोकेशन रेट, पोस्ट-कलेक्शन लाइव सेट, CPU, थ्रूपुट और त्रुटियों के विरुद्ध किया जाएगा।"

चरण-दर-चरण गहन विश्लेषण

चरण 1: एक गलत साबित करने योग्य (falsifiable) टाइमलाइन बनाएं और साबित करें कि क्या GC शामिल है।

प्रत्येक लेटेंसी स्पाइक के लिए, इंस्टेंस, रिक्वेस्ट विंडो, रिलीज़ वर्ज़न और UTC समय रिकॉर्ड करें। GC ID द्वारा एकीकृत लॉगिंग और JFR पॉज़ इवेंट्स के साथ इसे सहसंबंधित करें। एक बेसलाइन स्टार्टअप कॉन्फ़िगरेशन रोटेटिंग फ़ाइलों में टाइमस्टैम्प, टैग किए गए GC और सेफ़पॉइंट डेटा को सुरक्षित रख सकता है:

text
-Xlog:gc*,safepoint:file=/var/log/app/gc-%p.log:time,uptime,level,tags:filecount=10,filesize=100m

यह एक डायग्नोस्टिक उदाहरण है; पर्यावरण के अनुसार पथ, रिटेंशन और डिस्क बजट को अनुकूलित करें। JFR में, CPU लोड, थ्रेड्स, सॉकेट/फ़ाइल I/O और एलोकेशन इवेंट्स की जांच करते हुए jdk.GCPhasePause अवधि पर ध्यान केंद्रित करें। Oracle का मार्गदर्शन बताता है कि, एक समवर्ती कलेक्टर के लिए, कुल चक्र की लंबाई उस समय से कम सार्थक है जिसके लिए एप्लिकेशन वास्तव में रुकी हुई थी।

तीन परिणाम शाखाएँ बनाएँ। यदि स्पाइक्स JVM पॉज़ के साथ एक-से-एक संरेखित होते हैं, तो GC मूल-कारण विश्लेषण में आगे बढ़ें। यदि रिपोर्ट किया गया GC पॉज़ छोटा है लेकिन एक सेफ़पॉइंट लंबा है, तो सेफ़पॉइंट के कारण और उस तक पहुँचने के समय का निरीक्षण करें। यदि कोई भी संरेखित नहीं होता है, तो CPU थ्रॉटलिंग, लॉक, I/O, नेटवर्किंग और डाउनस्ट्रीम सेवाओं की जांच करें। यह GC परिकल्पना को सभी लेटेंसी के लिए डिफ़ॉल्ट स्पष्टीकरण के बजाय गलत साबित करने योग्य बनाता है।

चरण 2: केवल एक ग्राफ़ से नहीं, बल्कि एक मेट्रिक सेट के साथ GC लक्षण का वर्णन करें।

कम से कम निम्नलिखित समय श्रृंखला बनाए रखें और रिलीज़ से पहले और बाद में समान लोड विंडो की तुलना करें:

text
Requests: p50 / p95 / p99 / max, throughput, timeouts, error rate
Pauses: pause p50 / p95 / p99 / max, paused time per minute, pause cause
Heap: young / old usage, old-after-GC, reclaimed bytes, promotion rate
Allocation: bytes/s, top allocation sites by class and thread, inside/outside TLAB
G1: young / mixed / Full counts, evacuation failures, humongous regions
System: process CPU, GC CPU, CPU throttling, RSS, swap, major page faults, disk latency

used-before → used-after (heap-capacity) प्रारूप दिखाता है कि एक कलेक्शन ने क्या रिक्लेम किया, लेकिन एक बिंदु कोई प्रवृत्ति (trend) नहीं है। तुलनीय लोड पर एक मोनोटोनिक रूप से बढ़ती ओल्ड-आफ़्टर-GC बेसलाइन लाइव सेट में वृद्धि या प्रमोशन प्रेशर का सुझाव देती है। बार-बार कलेक्शन्स के साथ एक स्थिर बेसलाइन अक्सर उच्च एलोकेशन की ओर इशारा करती है। जब gc+cpu=info उपयोगकर्ता और सिस्टम समय से कहीं अधिक वास्तविक समय दिखाता है, तो GC थ्रेड जोड़ने से पहले शेड्यूलिंग, कोटा या पेजिंग की जांच करें।

चरण 3: पांच मूल-कारण पथों को अलग करने के लिए साक्ष्य का उपयोग करें।

  1. उच्च एलोकेशन रेट। ओल्ड-आफ़्टर-GC स्थिर है, जबकि यंग-पॉज़ की गिनती और प्रति मिनट पॉज़ किया गया समय बढ़ता है। JFR एलोकेशन इवेंट्स कैश-की निर्माण, सीरियलाइज़ेशन, या अस्थायी संग्रहों की ओर इशारा करते हैं। कॉपी करने को कम करें, स्पष्ट जीवनचक्र वाले बफ़र्स का पुन: उपयोग करें, और बैच आकार को नियंत्रित करें। साक्ष्य के बिना व्यापक ऑब्जेक्ट पूलिंग और शेयर्ड स्टेट को पेश न करें।
  2. लाइव-सेट वृद्धि या मेमोरी लीक। ओल्ड-आफ़्टर-GC बढ़ता रहता है और प्रत्येक कलेक्शन कम रिक्लेम करता है। कई बार क्लास हिस्टोग्राम की तुलना करें। हीप डंप केवल ऑफ़-पीक प्रतिकृति पर या ट्रैफ़िक-रीप्ले वातावरण में प्राप्त करें, फिर एक वैध कैश को असीमित रिटेंशन से अलग करने के लिए रिटेन्ड साइज़, डोमिनेटर ट्री और GC रूट्स का उपयोग करें। एक कैश जो क्षमता पर स्थिर हो जाता है, वह साइज़िंग का मुद्दा है; समाप्त (expired) ऑब्जेक्ट्स जो संदर्भित रहते हैं, वे एक लीक का संकेत देते हैं।
  3. G1 ह्यूमंगस ऑब्जेक्ट्स। G1 कम से कम आधे रीजन के आकार वाले ऑब्जेक्ट को ह्यूमंगस मानता है और इसे सीधे सन्निहित (contiguous) ओल्ड रीजन्स में रखता है। यदि Humongous regions: X → Y उच्च रहता है, तो बड़े byte[], char[], कंप्रेस्ड ब्लॉक्स, या बैचों को ट्रेस करें और पहले व्यक्तिगत ऑब्जेक्ट या बैच आकार को कम करें। माप के बाद ही रीजन-आकार परिवर्तन को एक प्रयोग के रूप में मानें, क्योंकि यह रीजन गणना और कलेक्शन ग्रैन्युलैरिटी को भी बदलता है।
  4. समवर्ती मार्किंग पीछे रह जाती है या इवैक्यूएशन विफल हो जाता है। यदि लॉग अपर्याप्त गंतव्य स्थान या उन रीजन्स को दिखाता है जिन्हें इवैक्यूएट नहीं किया जा सकता है, तो सबसे खराब स्थिति पूरे हीप का Full GC है। ओल्ड-रीजन एलोकेशन या प्रमोशन को कम करें, समवर्ती मार्किंग को पर्याप्त हेडरूम दें, और हीप क्षमता और लाइव सेट के बीच सुरक्षा मार्जिन की जांच करें। केवल MaxGCPauseMillis को कम करने से प्रत्येक कलेक्शन कम काम कर सकता है और सिस्टम को समाप्त होने के करीब छोड़ सकता है।
  5. स्पष्ट GC या एक नॉन-GC सेफ़पॉइंट। यदि GC का कारण System.gc() है, तो कॉल को हटाने, लाइब्रेरी को कॉन्फ़िगर करने या कार्य को अलग करने से पहले स्रोत को खोजने के लिए कॉल स्टैक, डिपेंडेंसी कॉन्फ़िगरेशन और ऑपरेशनल कमांड्स का उपयोग करें। सिमेंटिक प्रभाव की जांच के बाद ही DisableExplicitGC का उपयोग करें। यदि सेफ़पॉइंट लंबा है लेकिन GC पॉज़ छोटा है, तो कलेक्टर्स को बदलने के बजाय वास्तविक कारण का निरीक्षण करें—जैसे डीऑप्टिमाइज़ेशन, क्लास पुनर्परिभाषा, या कोई अन्य VM ऑपरेशन।

चरण 4: डायग्नोस्टिक टूल्स को लेयर करें और अवलोकन जोखिम को नियंत्रित करें।

लगातार उपलब्ध, कम लागत वाली परत से शुरुआत करें: एप्लिकेशन SLIs, एकीकृत GC/सेफ़पॉइंट लॉग्स, और मानक JFR। लाइव पुष्टिकरण के लिए, उसी प्रभावी उपयोगकर्ता के रूप में उसी होस्ट पर jcmd PID JFR.start, JFR.check, और JFR.dump चलाएं। jcmd PID help COMMAND के साथ पहले उस JVM द्वारा समर्थित विकल्पों की जांच करें।

फिर उच्च लागत वाली परत पर जाएं। Oracle GC.class_histogram को उच्च प्रभाव के रूप में चिह्नित करता है। GC.heap_dump भी उच्च प्रभाव वाला है और डिफ़ॉल्ट रूप से Full GC का अनुरोध करता है। JFR हीप आँकड़े शुरुआत और अंत में अतिरिक्त ओल्ड कलेक्शन्स को ट्रिगर करते हैं, इसलिए लेटेंसी जांच के दौरान डिफ़ॉल्ट रूप से हीप आँकड़े सक्षम न करें। ट्रैफ़िक को ड्रेन करें या किसी प्रतिकृति पर समस्या को पुन: उत्पन्न करें, डिस्क क्षमता और संवेदनशील डेटा हैंडलिंग की पुष्टि करें, और उसके बाद ही हिस्टोग्राम या डंप प्राप्त करें। एक स्थिर लोड अंतराल द्वारा अलग किए गए दो कैप्चर की तुलना करना सबसे बड़े ऑब्जेक्ट्स की एक सूची की तुलना में वृद्धि को बेहतर ढंग से समझाता है।

चरण 5: कोड, क्षमता या कलेक्टर सेटिंग्स बदलते समय कार्य-कारण को एकवचन (singular) रखें।

नए कैश से शुरुआत करें। क्या यह असीमित है? क्या इसका TTL वास्तव में प्रविष्टियों को समाप्त करता है? क्या अधिकतम भार प्रविष्टि गणना के बजाय बाइट्स में मापा जाता है? क्या प्रत्येक मान एक बड़े एरे को कॉपी करता है? क्या समवर्ती मिसेस स्वतंत्र रूप से समान मान का निर्माण करते हैं? संभावित कोड सुधारों में अधिकतम भार को सीमित करना, समान-कुंजी लोड को संयोजित करना, स्ट्रीमिंग सीरियलाइज़ेशन, बैच आकार को कम करना, या अस्थायी ऑब्जेक्ट्स के संदर्भों को जल्दी हटाना शामिल है। प्रत्येक प्रयोग में एक मुख्य चर बदलें।

एक बड़ा हीप कलेक्शन्स के बीच के अंतराल को बढ़ा सकता है, लेकिन यह असीमित रिटेंशन को भी छुपा सकता है, कंटेनर-मेमोरी जोखिम को बढ़ा सकता है, और लाइव डेटा की मात्रा को बढ़ा सकता है जिसे अंततः प्रोसेस किया जाना चाहिए। IHOP, यंग-जनरेशन साइज़िंग, रीजन साइज़, या पॉज़ टारगेट को बदलने से पहले, लॉग किए गए फ़ेज़ या संसाधन की कमी की पहचान करें जिसे फ़्लैग द्वारा बदलने की उम्मीद है। ZGC जैसे कलेक्टर पर स्विच करना एक आर्किटेक्चरल प्रयोग है जिसके लिए नए थ्रूपुट, CPU, हीप-हेडरूम, कंटेनर और ऑपरेशनल सत्यापन की आवश्यकता होती है। यह पहली घटना कमांड नहीं है।

चरण 6: समान-लोड कैनरी और विपरीत-तथ्य (counterfactual) साक्ष्य के साथ घटना को समाप्त करें।

बेसलाइन और फिक्स्ड वर्ज़न के विरुद्ध प्रोडक्शन-आकार के रिक्वेस्ट, ऑब्जेक्ट साइज़, कैश हिट रेट और कंकरेंसी को फिर से चलाएं। कैनरी को कई मूल 3–5 मिनट के स्पाइक चक्रों तक चलना चाहिए और इसमें कैश कोल्ड स्टार्ट और स्थिर स्थिति शामिल होनी चाहिए। परीक्षण से पहले स्वीकृति सीमाएं परिभाषित करें, उदाहरण के लिए: रिक्वेस्ट p99 200 ms से नीचे; GC पॉज़ p99 100 ms से नीचे और अधिकतम 500 ms से नीचे; प्रति मिनट 1% से कम पॉज़ समय; वार्म-अप के बाद ओल्ड-आफ़्टर-GC का बढ़ना बंद होना; और थ्रूपुट, CPU बजट, OOMs, या त्रुटियों में कोई रिग्रेशन नहीं होना।

फिर विपरीत-तथ्यों का परीक्षण करें। क्या कैश को रोल बैक करने से एलोकेशन रेट और स्पाइक्स दोनों पुनर्स्थापित होते हैं? क्या केवल कैश भार को सीमित करने से ओल्ड-आफ़्टर-GC स्थिर हो जाता है? क्या केवल बैच आकार को कम करने से ह्यूमंगस रीजन्स कम होते हैं? जब अनुमानित मेट्रिक्स एक साथ चलते हैं, तो निवारण का मूल कारण से एक कारणात्मक संबंध होता है। चरणों में रोल आउट करें और स्वचालित रोलबैक मानदंड बनाए रखें।

उच्च गुणवत्ता वाला नमूना उत्तर

"वर्तमान डेटा GC की जांच को सार्थक बनाता है, लेकिन यह साबित नहीं करता है कि GC के कारण 2–4 सेकंड का रिक्वेस्ट स्पाइक हुआ। मैं पहले रिक्वेस्ट SLIs, -Xlog:gc*,safepoint, JFR jdk.GCPhasePause इवेंट्स, CPU थ्रॉटलिंग, पेज फ़ॉल्ट और डिपेंडेंसी लेटेंसी को इंस्टेंस और UTC समय द्वारा संरेखित करूँगा। यदि स्पाइक्स पॉज़ के साथ संरेखित होते हैं, तो मैं एक लंबे पॉज़ को कई संचित छोटे पॉज़ से अलग करूँगा। यदि केवल सेफ़पॉइंट लंबा है, तो मैं सेफ़पॉइंट कारण का पालन करूँगा। यदि कोई भी संरेखित नहीं होता है, तो मैं GC शाखा छोड़ दूँगा।

रिलीज़ ने एलोकेशन को 600 MiB/s से बढ़ाकर 1.4 GiB/s कर दिया, जबकि ओल्ड-आफ़्टर-GC 6.1 GiB से बढ़कर 9.2 GiB हो गया। इससे मुझे कम से कम दो परिकल्पनाएँ मिलती हैं: कैश ने पर्याप्त अस्थायी एलोकेशन बनाया, और कैश या संबंधित ऑब्जेक्ट्स ने लाइव सेट का विस्तार किया। मैं यंग, मिक्स्ड और Full कारणों का निरीक्षण करूँगा; पहले और बाद का हीप; प्रमोशन; इवैक्यूएशन विफलताएं; और ह्यूमंगस रीजन्स। JFR एलोकेशन इवेंट्स क्लासेस, थ्रेड्स और कॉल साइट्स की पहचान करते हैं। कई समयों के हिस्टोग्राम दिखाते हैं कि कौन सी क्लासेस लगातार बढ़ रही हैं। मैं केवल ड्रेन की गई प्रतिकृति पर या रीप्ले में हीप डंप प्राप्त करूँगा क्योंकि यह उच्च प्रभाव वाला है और डिफ़ॉल्ट रूप से Full GC का अनुरोध कर सकता है।

यदि पोस्ट-कलेक्शन बेसलाइन स्थिर है लेकिन यंग पॉज़ बार-बार होते हैं, तो मैं कैश-की निर्माण, सीरियलाइज़ेशन और अस्थायी संग्रहों में एलोकेशन कम करूँगा। यदि बेसलाइन बढ़ती रहती है, तो मैं एक बाउंडेड कैश को लीक से अलग करने के लिए रिटेन्ड साइज़ और GC-रूट पाथ्स का उपयोग करूँगा। यदि बड़े एरेज़ आधे G1 रीजन को पार करते हैं और ह्यूमंगस-रीजन का उपयोग बढ़ता है, तो मैं बफ़र्स या बैचों को विभाजित करूँगा। यदि कारण System.gc() है, तो मैं कॉलर को ढूंढूंगा। यदि वास्तविक समय GC CPU समय से बहुत अधिक है, तो मैं कोटा, स्वैप और होस्ट कंटेंशन की जांच करूँगा।

मैं 24 GiB हीप को समाधान नहीं मानूँगा। मैं कैश कोल्ड स्टार्ट और कई मूल स्पाइक चक्रों में समान-लोड कैनरी में प्रत्येक उम्मीदवार परिवर्तन का स्वतंत्र रूप से परीक्षण करूँगा। अंतिम गेट रिक्वेस्ट p99, पॉज़ p99 और अधिकतम, कुल पॉज़ अनुपात, एलोकेशन रेट, ओल्ड-आफ़्टर-GC, ह्यूमंगस रीजन्स, थ्रूपुट, CPU, RSS, त्रुटियों और OOMs को कवर करता है। केवल तभी मैं रोलबैक को संरक्षित रखते हुए ट्रैफ़िक बढ़ाऊँगा।"

सामान्य गलतियाँ

  • गलती: सॉटूथ हीप ग्राफ़ देखने के बाद GC को कारण घोषित करना → यह क्यों विफल होता है: ग्राफ़ साबित करता है कि कलेक्शन हुआ था, यह नहीं कि एक रिक्वेस्ट स्पाइक और स्टॉप-द-वर्ल्ड पॉज़ एक इंस्टेंस पर ओवरलैप हुए थे → सुधार: इंस्टेंस, GC ID और एक टाइमलाइन के साथ रिक्वेस्ट, लॉग और JFR को सहसंबंधित करें।
  • गलती: केवल औसत GC अवधि को देखना → यह क्यों विफल होता है: औसत एकल तीन-सेकंड टेल इवेंट को छुपाता है और कई छोटे पॉज़ की कुल लागत की उपेक्षा करता है → सुधार: पॉज़ परसेंटाइल, अधिकतम, प्रति मिनट पॉज़ किया गया समय और कारण मापें।
  • गलती: GC-चक्र की अवधि को एप्लिकेशन-पॉज़ अवधि के रूप में मानना → यह क्यों विफल होता है: G1 का अधिकांश मार्किंग कार्य एप्लिकेशन के साथ समवर्ती रूप से चल सकता है → सुधार: वास्तविक पॉज़ इवेंट्स और रिक्वेस्ट SLIs का उपयोग करें।
  • गलती: घटना के दौरान हीप को 12 GiB से बढ़ाकर 24 GiB करना → यह क्यों विफल होता है: यह असीमित रिटेंशन को छुपा सकता है और यह साबित किए बिना मेमोरी जोखिम बढ़ा सकता है कि लाइव सेट की एक वैध सीमा है → सुधार: पोस्ट-कलेक्शन लाइव-सेट ग्रोथ से एलोकेशन रेट को अलग करें, फिर एक प्रतिवर्ती क्षमता प्रयोग चलाएं।
  • गलती: केवल एकमात्र प्रोडक्शन इंस्टेंस पर तुरंत हीप डंप लेना → यह क्यों विफल होता है: कमांड उच्च प्रभाव वाली है, डिफ़ॉल्ट रूप से Full GC का अनुरोध कर सकती है, एक बड़ी फ़ाइल लिखती है, और संवेदनशील डेटा उजागर करती है → सुधार: ट्रैफ़िक को ड्रेन करें और इसे प्रतिकृति या नियंत्रित रीप्ले पर एकत्र करें।
  • गलती: बड़े ऑब्जेक्ट्स को देखने के बाद G1 रीजन का आकार बदलना → यह क्यों विफल होता है: हो सकता है कि कोई बड़ा ऑब्जेक्ट हाफ़-रीजन सीमा को पार न करे, और यह फ़्लैग पूरे हीप में रीजन ग्रैन्युलैरिटी को बदल देता है → सुधार: gc+heap और एलोकेशन साक्ष्य के साथ ह्यूमंगस ऑब्जेक्ट्स की पुष्टि करें, फिर प्रयोगों की तुलना करें।
  • गलती: -XX:MaxGCPauseMillis=50 को SLA के रूप में मानना → यह क्यों विफल होता है: यह एक लक्ष्य संकेत है और G1 एक रीयल-टाइम कलेक्टर नहीं है → सुधार: वास्तविक पॉज़ वितरण को मान्य करें और लक्ष्य, थ्रूपुट और हीप हेडरूम के बीच ट्रेडऑफ़ का बजट बनाएं।
  • गलती: System.gc() दिखाई देते ही स्पष्ट GC को विश्व स्तर पर अक्षम करना → यह क्यों विफल होता है: कोई डिपेंडेंसी या परिचालन वर्कफ़्लो सिमेंटिक्स पर निर्भर हो सकता है → सुधार: इसे हटाने, कॉन्फ़िगर करने या अलग करने से पहले कॉलर और उसके इरादे की पहचान करें।
  • गलती: कलेक्टर्स बदलने के बाद केवल यह जांचना कि p99 गिरता है → यह क्यों विफल होता है: कम पॉज़ उच्च CPU, कम थ्रूपुट, या अधिक मेमोरी के साथ आ सकते हैं → सुधार: समान लोड के तहत लेटेंसी, थ्रूपुट, CPU, RSS, त्रुटियों और रिकवरी का परीक्षण करें।

अनुवर्ती प्रश्न और उत्तर

अनुवर्ती 1: आप मेमोरी लीक को सामान्य कैश वार्म-अप से कैसे अलग करते हैं?

प्रक्रिया शुरू होने के बाद केवल प्रारंभिक ढलान ही नहीं, बल्कि तुलनीय लोड पर कई पुराने संग्रहों के बाद लाइव सेट की जांच करें। एक सामान्य सीमित कैश अपने अधिकतम भार और स्थिर हिट दर तक पहुँचने के बाद स्थिर (plateau) हो जाता है, और निष्कासन (eviction) और TTL समाप्ति देखने योग्य होनी चाहिए। एक लीक ऐसे ऑब्जेक्ट्स को छोड़ देता है जिनका कोई व्यावसायिक मूल्य नहीं होता है और जो GC रूट से रीचेबल होते हैं, इसलिए बेसलाइन बढ़ती रहती है। बढ़ती हुई क्लासेस को खोजने के लिए कई बार क्लास हिस्टोग्राम का उपयोग करें, फिर एक नियंत्रित हीप डंप में डोमिनेटर, रिटेन्ड साइज़ और संदर्भ पथों का निरीक्षण करें। भले ही कैश अंततः स्थिर हो जाए, 12 GiB हीप में 9.2 GiB का प्लेटफ़ॉर्म समवर्ती मार्किंग और बर्स्ट के लिए अपर्याप्त हेडरूम छोड़ सकता है, जिससे यह क्षमता-डिज़ाइन की समस्या बन जाती है।

अनुवर्ती 2: पहले MaxGCPauseMillis को कम क्यों नहीं करते?

यह मार्गदर्शन करता है कि G1 प्रति कलेक्शन कितना काम करने का प्रयास करता है; यह कोई लागू की गई ऊपरी सीमा नहीं है। यदि लाइव सेट बहुत बड़ा है, समवर्ती मार्किंग पीछे रह जाती है, या कंटेनर CPU की आपूर्ति नहीं कर सकता है, तो एक निचला लक्ष्य प्रत्येक मिश्रित संग्रह को कम रिक्लेम करने, आवृत्ति बढ़ाने और प्रक्रिया को इवैक्यूएशन विफलता के करीब ले जाने का कारण बन सकता है। पहले फ़ेज़ टाइमिंग, हीप हेडरूम और एलोकेशन या प्रमोशन रेट से एक परिकल्पना बनाएं, फिर उसी लोड के तहत एक फ़्लैग परिवर्तन को मान्य करें।

अनुवर्ती 3: GC लॉग में लंबा वास्तविक समय लेकिन छोटा उपयोगकर्ता और सिस्टम समय क्या दर्शाता है?

यह सुझाव देता है कि GC थ्रेड्स ने संपूर्ण वॉल-क्लॉक अंतराल के लिए CPU का उपभोग नहीं किया। संभावित कारणों में कंटेनर CPU थ्रॉटलिंग, होस्ट कंटेंशन, स्वैप या प्रमुख पेज फ़ॉल्ट, वर्चुअलाइज़ेशन पॉज़ और लॉग I/O शामिल हैं। cgroup कोटा और थ्रॉटल्ड समय, रन कतार, पेज फ़ॉल्ट, स्वैप, डिस्क लेटेंसी और कोलोकेटेड-होस्ट इवेंट्स का निरीक्षण करें। समानांतर GC थ्रेड जोड़ने से कंटेंशन तीव्र हो सकता है; पहले बाहरी संसाधन आपूर्ति के साक्ष्य का समाधान करें।

अनुवर्ती 4: आप G1 से ZGC पर जाने पर कब विचार करेंगे?

इस पर विचार तब करें जब सेवा का एक स्पष्ट कम-लेटेंसी लक्ष्य हो, एलोकेशन और लाइव-सेट वृद्धि नियंत्रण में हो, G1 अभी भी लक्षित हीप और लोड पर पॉज़ SLO को मिस करता हो, और टीम अतिरिक्त CPU, हीप हेडरूम, JDK संगतता और संचालन को मान्य कर सकती हो। कोल्ड स्टार्ट, स्थिर स्थिति, बर्स्ट, विफलता पुनर्प्राप्ति और कंटेनर सीमाओं सहित प्रोडक्शन-आकार के ट्रैफ़िक के साथ एक A/B परीक्षण चलाएं। कलेक्टर माइग्रेशन एक क्षमता और रनटाइम-मॉडल विकल्प है; यह किसी ऑब्जेक्ट लीक या असीमित कैश को ठीक करने का विकल्प नहीं है।

अनुवर्ती 5: आप यह कैसे साबित करते हैं कि समाधान केवल स्पाइक में देरी करने के बजाय स्थायी रूप से काम करता है?

कैनरी को कई मूल स्पाइक चक्रों, कैश स्थिर स्थिति और अपेक्षित पीक को कवर करने के लिए पर्याप्त समय तक चलाएं। ओल्ड-आफ़्टर-GC के ढलान, प्रति घंटा Full GCs और इवैक्यूएशन विफलताओं, एलोकेशन रेट, ह्यूमंगस रीजन्स, कुल पॉज़ अनुपात और रिक्वेस्ट SLIs की तुलना करें, और हेडरूम के लिए पूर्वानुमानित पीक से परे परीक्षण करें। यदि एक बड़ा हीप केवल पहले स्पाइक को पांच मिनट से दस मिनट तक ले जाता है जबकि बेसलाइन ढलान अपरिवर्तित रहता है, तो निवारण विफल हो गया है।

सार्वजनिक स्रोत

संबंधित प्रश्न