एक कचरा संग्रह की कहानी

यह सावधानी कहानी यह बताती है कि Google कौशल को विकसित करना कितना महत्वपूर्ण है, और मैंने प्रति घंटा पूर्ण कचरा संग्रह के साथ कैसे संघर्ष किया।

समस्या का संक्षिप्त विवरण


टमाटर के नए संस्करण में सिस्टम घटकों में से एक (टॉमकैट पर चलने वाला एकमात्र) के उत्पादन में माइग्रेट होने के बाद, जब जीसी को एक आधे सेकंड के लिए लॉग में देखा गया तो समर्थन अचानक घबरा गया।

सामान्य तौर पर, हमारी परियोजना में (एक निवेश बैंक में एफएक्स ट्रेडिंग प्लेटफॉर्म), 50 मिलीसेकंड (एमएस) के लिए जीसी के किसी भी लॉन्च का समर्थन निगरानी के लिए चिंता का कारण बनता है, अन्य चीजों के अलावा, जीसी लॉग और जीसी 100 एमएस से अधिक हिस्टीरिया का कारण बनता है। इसलिए, जब उन्होंने लॉग में 1.45 सेकंड देखे, तो वे बस घबरा गए। मैं उस दिन "रक्षा की दूसरी पंक्ति" पर भाग्यशाली था, और मैंने यह पता लगाना शुरू कर दिया कि मामला क्या था।

जांच की प्रगति


शुरुआत के लिए, लॉग में चढ़ गए। और मैंने देखा:
[Full GC (System) 25.575: [CMS: 20700K->22393K(655360K) <...>

यहां कुंजी एक संकेत है कि यह एक प्रणाली है - अर्थात Full कचरा संग्रह को System.gc() कहकर सक्रिय किया जाता है। मुझे लगता है कि यह कहना अतिश्योक्तिपूर्ण होगा कि इसका उपयोग हमारे कोड में नहीं किया गया है।

गीतात्मक विषयांतर
पूरी तरह से ईमानदार होने के लिए, यह सच नहीं है - हम बहुत अच्छे उद्देश्य के लिए System.gc() कॉल का उपयोग कर रहे हैं। तथ्य यह है कि स्टार्ट-अप प्रक्रिया के दौरान, सिस्टम "स्थिर डेटा" की एक बड़ी मात्रा को प्री-कैश करता है - उपयोगकर्ताओं, मुद्राओं आदि के बारे में जानकारी, और यह प्रक्रिया काफी कचरा उत्पन्न करती है। इसलिए, जब हमने सभी तैयारी पूरी कर ली है, और इससे पहले कि हम रिपोर्ट करें कि सिस्टम शुरू हो गया है, हम एक ही समय में अनावश्यक स्मृति विखंडन से छुटकारा पाने के लिए System.gc() कहते हैं। इसके अलावा, हम हर रात 5:00 बजे न्यूयॉर्क के समय में एक ही काम करते हैं, जब व्यापारिक दिन समाप्त होता है, और हमारी पूरी प्रणाली 5 मिनट (रखरखाव मोड) के लिए ऑफ़लाइन हो जाती है, ताकि दिन के दौरान जमा हुए कचरे को हटाने और स्मृति को डीफ़्रैग्मेन्ट किया जा सके। । काम के घंटों के दौरान, हम पुराने-जीन संग्रह से बचने की कोशिश करते हैं, क्योंकि 20-40 एमएस लेने वाले ParNew असेंबली कभी-कभी अस्वीकार्य देरी भी करते हैं।

पुरानी मेमोरी के अनुसार पहली चीज़, डीजीसी (एंटी-मंगोलियन में लिंक, संक्षेप में - आरएमआई अनुप्रयोग में उपयोग किए जाने पर सक्रिय कचरा कलेक्टर) द्वारा जाँच की गई थी। एक बार, यह वह था, जिसने हमारे कई सिस्टमों पर हर घंटे पूर्ण असेंबली का कारण बना, साधारण कारण से कि हम जेएमएक्स के लिए आरएमआई का उपयोग करते हैं। इसलिए, ठीक एक दिन पहले, कई साल पहले, सभी प्रणालियों पर, हमने JVM स्टार्टअप मापदंडों में निम्नलिखित को जोड़ा:

 -Dsun.rmi.dgc.server.gcInterval=0x7FFFFFFFFFFFFFFE -Dsun.rmi.dgc.client.gcInterval=0x7FFFFFFFFFFFFFFE 

इन सेटिंग्स के स्रोत: [१] , [२] । उन्हें केवल डीजीसी को बताना है कि यह हर 0x7FFFFFFFFFFFFFFE मिलीसेकंड (लगभग हर 292471208 साल में एक बार) चल सकता है।

टमाटर की शुरुआत की स्क्रिप्ट की जांच करने के बाद, मैंने देखा कि एक छोटी सी त्रुटि है, और 0x7FFFFFFFFFFFFFFE बजाय, 0x7FFFFFFFFFFFFFFF उपयोग किया जाता है। इस मान को बाध्य करने से एक IllegalArgumentException ( sun.misc.GC वर्ग से कोड) फेंकता है:
 if (this.latency == 9223372036854775807L) { throw new IllegalStateException("Request already cancelled"); } 

हमें और गहरे जाने की जरूरत है!


हालांकि, इस गलतफहमी को ठीक करते हुए, मुझे बहुत आश्चर्य हुआ जब टमाटर को फिर से शुरू करने के बाद हमने फिर से वही प्रति घंटा Full GC (System) ! मुझे मन को तनाव में डालना था। System.gc() को कॉल करने के लिए किसे दोषी ठहराया जाए, यह जानने के लिए, मैंने जल्दी से java.lang.Runtime क्लास के अपने कार्यान्वयन को लिखा, मानक वर्ग की प्रतिलिपि बनाई और इसकी gc() विधि (जिसे System.gc() कहा जाता है) कहा जाता है) को बदलना।
मूल विधि:
 public native void gc(); 

संशोधित विधि:
 public void gc() { Thread.dumpStack(); } 

जैसा कि आप देख सकते हैं, हम यहां जो कुछ भी करते हैं वह यह पता लगाने के लिए है कि स्टडआउट के लिए एक स्टैट्रेस का उत्पादन करके हम सभी को एक ही कहा जाता है, क्योंकि सभी जो Thread.dumpStack() विधि करता है Thread.dumpStack() एक अपवाद को फेंक देता है और इसके स्टैक ट्रेस को फेंक देता है:
  /** * Prints a stack trace of the current thread to the standard error stream. * This method is used only for debugging. * * @see Throwable#printStackTrace() */ public static void dumpStack() { new Exception("Stack trace").printStackTrace(); } 

हम अपने जावा मशीन में java.lang.Runtime क्लास की जगह -Xbootclasspath/p:/tmp/runtime इसे चलाते हैं ... और हम क्या देखते हैं?

 java.lang.Exception: Stack trace at java.lang.Thread.dumpStack(Thread.java:1249) at java.lang.Runtime.gc(Runtime.java:689) at java.lang.System.gc(System.java:926) at sun.misc.GC$Daemon.run(GC.java:92) 

वही DGC! लेकिन कैसे? हमने DGC लॉन्च के बीच लगभग अनंत अंतराल निर्धारित किया है? यहाँ मुझे अपने सभी Google कौशल को तनावपूर्ण करना पड़ा। और इसलिए मैंने खोज की, और इस तरह से - कुछ भी नहीं मिला। मैंने जाँच की कि क्या कोई भी डीजीसी के लिए मेरी सेटिंग्स को ओवरराइट कर रहा है - यह पता चला कि जब तक जीसी शुरू हुआ, तब तक सभी सिस्टम गुणधर्मों में एक जैसा मान था। आश्चर्य ...
छवि

... हाहाकार!


इस बिंदु पर, मेरा संदेह टॉमकैट पर गिर गया। और इसलिए, Google अनुरोध में शब्दों के खुश संयोजन ने मुझे केवल एक ही लिंक दिया जिसने मुझे कहा कि मुझे यहां प्रिंट करने में बहुत शर्म आ रही है, और मेरे अंग्रेजी सहयोगियों (जिन्होंने मेरी मदद की, जहां तक ​​संभव हो, जांच में, विचारोत्तेजक पूछकर "क्या आपने Google के लिए प्रयास किया?") जैसे प्रश्न उन्होंने मुझे बहुत निराशाजनक रूप से देखे ... फिर उन्होंने लिंक पर लिखे गए और मेरे शापों को दोहराया।
तो, यह यहाँ है, लिंक , जो, बदले में, टमटम बगज़िला की ओर जाता है: टिंट्स। संक्षेप में, ये सरल अपाचे लोग अंतराल के मूल्य को अधिलेखित करते हैं जिसके साथ DGC को sun.rmi.GC वर्ग में सही कहा जाना चाहिए, और इस तथ्य के बावजूद कि हम अपने -Dsun.rmi.dgc.* सेट करते हैं -Dsun.rmi.dgc.* गुण: वे सभी समान हैं। कोई प्रभाव नहीं है! क्योंकि हम tomcat 6.0.35 का उपयोग करते हैं, और यह बग अगले संस्करण, 6.0.36 में तय किया गया था।

यहाँ JreMemoryLeakPreventionListener क्लास JreMemoryLeakPreventionListener का कोड है, जो वास्तव में इस व्यवहार के लिए जिम्मेदार है:
 /* * Several components end up calling: * sun.misc.GC.requestLatency(long) * * Those libraries / components known to trigger memory leaks * due to eventual calls to requestLatency(long) are: * - javax.management.remote.rmi.RMIConnectorServer.start() */ if (gcDaemonProtection) { try { Class<?> clazz = Class.forName("sun.misc.GC"); Method method = clazz.getDeclaredMethod("requestLatency", new Class[] {long.class}); method.invoke(null, Long.valueOf(3600000)); } catch (...) { ... } } 

वहां, SO पर, मुझे वर्कअराउंड मिला:
सेट />


ठीक यही मैंने किया। जैसा कि आप ऊपर दिए गए कोड से देख सकते हैं, gcDaemonProtection ध्वज केवल अजीब व्यवहार के लिए जिम्मेदार कोड के इस ब्लॉक को अक्षम करता है। और - ओह, एक चमत्कार! - यह मदद की! प्रति घंटा कचरा संग्रह गुमनामी में गायब हो गया, समर्थन खुश है, मैं चाय पीने गया।

निष्कर्ष


और निष्कर्ष, सामान्य तौर पर, कुछ हैं:
  1. सबसे महत्वपूर्ण बात - Google को अनुरोधों को सही ढंग से तैयार करना सीखें! मैंने एक दर्जन अलग-अलग संयोजनों की कोशिश की, जब तक कि मैं अंत में एक पूरी तरह से केले के tomcat hourly full GC अनुरोध पर नहीं आया, जिसने मुझे वह लिंक दिया जिसकी मुझे तलाश थी।
  2. उस थर्ड-पार्टी, यहां तक ​​कि बहुत प्रसिद्ध सॉफ्टवेयर का भी मतलब नहीं है, कीड़े नहीं है - यह करता है! और यह पहली बार नहीं है जब हम बग में भागे हैं। पिछली बार यह हॉटस्पॉट सीएमएस में एक बग था, जिसने कचरा संग्रह के लिए वास्तव में हमारा समय खराब कर दिया। मैंने एक नए JVM में अपग्रेड करने का फैसला किया।
  3. और, ज़ाहिर है, प्रतिबिंब बुराई है, खासकर यदि आप एक कंटेनर या लाइब्रेरी के डेवलपर हैं, और आप अपने गंदे हाथों को प्रतिबिंब के साथ सिस्टम कक्षाओं में जकड़ते हैं।


    आपका ध्यान के लिए धन्यवाद, मुझे आशा है कि कोई मेरे अनुभव को उपयोगी पाएगा।

Source: https://habr.com/ru/post/In179309/


All Articles