खरोंच से कचरा संग्रह स्थापित करने के लिए बुनियादी सिद्धांत

इस लेख में, मैं कचरा कलेक्टर के सिद्धांत पर ध्यान केंद्रित नहीं करना चाहता हूं - यह यहां खूबसूरती से और स्पष्ट रूप से वर्णित है: habrahabr.ru/post/112676 । मैं जेवीएम में कचरा संग्रह की स्थापना के लिए व्यावहारिक बुनियादी बातों और मात्रात्मक विशेषताओं पर अधिक जाना चाहता हूं - और यह समझने की कोशिश करता हूं कि यह कैसे प्रभावी हो सकता है।



जीसी प्रदर्शन मूल्यांकन के मात्रात्मक लक्षण



निम्नलिखित संकेतकों पर विचार करें:







एक नियम के रूप में, सूचीबद्ध विशेषताएं समझौता हैं और उनमें से एक के सुधार से बाकी के लिए लागत आती है। अधिकांश अनुप्रयोगों के लिए, सभी तीन विशेषताएं महत्वपूर्ण हैं, लेकिन अक्सर एक या दो आवेदन के लिए अधिक महत्वपूर्ण होते हैं - यह कॉन्फ़िगरेशन में शुरुआती बिंदु होगा।



जीसी ट्यूनिंग मूल बातें





जीसी सेटिंग्स को समझने के लिए तीन बुनियादी मूलभूत नियमों पर विचार करें:





एक साधारण एप्लिकेशन के उदाहरण पर विचार करें (जो, उदाहरण के लिए, एक वेब एप्लिकेशन के संचालन का अनुकरण कर सकता है, जिसके दौरान डेटाबेस एक्सेस किया जाता है और लौटा हुआ परिणाम जमा होता है), जिसमें मेकओब्जेक्ट () विधि कई थ्रेड्स में एक्सेस की जाती है, जिसके दौरान लूप लगातार उत्पन्न होता है। एक ऑब्जेक्ट जो ढेर पर एक निश्चित मात्रा में रहता है, फिर उसके साथ कोई भी गणना की जाती है - एक देरी की जाती है, ऑब्जेक्ट का लिंक विधि से लीक नहीं होता है और पूरा होने पर, जीसी समझ सकता है कि इस ऑब्जेक्ट को साफ करने की आवश्यकता है।

package ru.skuptsov; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MemoryConsumer implements Runnable { private static final int OBJECT_SIZE = 1024 * 1024; private static final int OBJECTS_NUMBER = 8; private static final int ADD_PROCESS_TIME = 1000; private static final int NUMBER_OF_REQUEST_THREADS = 50; private static final long EXPERIMENT_TIME = 30000; private static volatile boolean stop = false; public static void main(String[] args) throws InterruptedException { start(); Thread.sleep(EXPERIMENT_TIME); stop(); } private static void start() { ExecutorService execService = Executors.newCachedThreadPool(); for (int i = 0; i < NUMBER_OF_REQUEST_THREADS; i++) execService.execute(new MemoryConsumer()); } private static void stop() { stop = true; } @Override public void run() { while (true && !stop) { makeObjects(); } } private void makeObjects() { List<byte[]> objectList = new ArrayList<byte[]>(); for (int i = 0; i < OBJECTS_NUMBER; i++) { objectList.add(new byte[OBJECT_SIZE]); } try { Thread.sleep(ADD_PROCESS_TIME); } catch (InterruptedException e) { e.printStackTrace(); } } }
      
      







प्रयोग कुछ समय के लिए रहता है, फिर हम प्रभावशीलता का मूल्यांकन करने के लिए कचरा कलेक्टर द्वारा उत्पन्न कुल देरी समय का उपयोग करेंगे। विलंब आवश्यक है ताकि हटाने के लिए वस्तुओं के अंतिम अंकन के बाद, साफ की जा रही वस्तु का लिंक दिखाई न दे। तथ्य यह है कि वहाँ jvm है जो "स्टॉप-द-वर्ल्ड" ठहराव और विभिन्न प्रकार के GC फ़ंक्शन का कारण बनाए बिना वस्तुओं को चिह्नित और साफ़ कर सकता है - यहाँ विस्तार से वर्णित है habrahabr.ru/post/148322 - हम इस तरह के एक विकल्प पर विचार नहीं करते हैं।



हम इस पर प्रयोग करेंगे:

 C:\>java -XX:+PrintCommandLineFlags -version -XX:MaxHeapSize=4290607104 -XX:ParallelGCThreads=8 -XX:+PrintCommandLineFlags -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC java version "1.6.0_16" Java(TM) SE Runtime Environment (build 1.6.0_16-b01) Java HotSpot(TM) 64-Bit Server VM (build 14.2-b01, mixed mode)
      
      





जिसके लिए सर्वर और UseParallelGC मोड डिफ़ॉल्ट रूप से सक्षम होते हैं (छोटे कचरा संग्रह चरण के बहु-थ्रेडेड ऑपरेशन)



कुल ठहराव समय का मूल्यांकन करने के लिए, कचरा कलेक्टर को मोड में चलाया जा सकता है:

 java -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -verbose:gc -Xloggc:gc.log ru.skuptsov.MemoryConsumer
      
      





और देरी को संक्षेप में glog के द्वारा:

 0.167: [Full GC [PSYoungGen: 21792K->13324K(152896K)] [PSOldGen: 341095K->349363K(349568K)] 362888K->362687K(502464K) [PSPermGen: 2581K->2581K(21248K)], 0.0079385 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
      
      





जहाँ वास्तविक = 0.01 सेकंड असेंबली पर बिताया गया वास्तविक समय है।



और आप VisualVm उपयोगिता का उपयोग कर सकते हैं, विजुअलजीसी प्लगइन स्थापित करने के साथ, जिसमें आप जीसी (ईडन, सर्वाइवर 1, सर्वाइवर 2, ओल्ड) के विभिन्न क्षेत्रों में स्मृति वितरण को देख सकते हैं और कचरा संग्रह की शुरुआत और अवधि के आंकड़े देख सकते हैं।



आवश्यक मेमोरी का आकार निर्धारित करना



सबसे पहले, हमें एप्लिकेशन को वास्तव में आवश्यक एप्लिकेशन की तुलना में सबसे बड़े संभव मेमोरी आकार के साथ चलाना चाहिए। यदि हम शुरू में नहीं जानते हैं कि हमारा एप्लिकेशन मेमोरी में कितना व्याप्त होगा, तो आप निर्दिष्ट किए बिना एप्लिकेशन को शुरू कर सकते हैं -Xmx और -Xms, और हॉटस्पॉट वीएम मेमोरी साइज़ को ही चुनेगा। यदि आवेदन शुरू होने पर हमें आउटऑफमेरी (जावा हीप स्पेस या पर्मगेन स्पेस) मिलता है, तो हम त्रुटियों को दूर करने तक उपलब्ध मेमोरी (-Xmx या -XX: PermSize) के आकार को बढ़ा सकते हैं।

अगला कदम लंबे समय तक जीवित डेटा के आकार की गणना करना है - यह पूर्ण कचरा संग्रह चरण के बाद ढेर के पुराने और स्थायी क्षेत्रों का आकार है। यह आकार अनुप्रयोग के लिए कार्य करने के लिए आवश्यक स्मृति की अनुमानित राशि है; इसे प्राप्त करने के लिए, आप पूर्ण असेंबली की एक श्रृंखला के बाद क्षेत्रों के आकार को देख सकते हैं। एक नियम के रूप में, -Xms और -Xmx अनुप्रयोगों के लिए आवश्यक मेमोरी का आकार लाइव डेटा की मात्रा से 3-4 गुना बड़ा है। तो, ऊपर दिए गए लॉग के लिए - पूर्ण कचरा संग्रह के चरण के बाद पुराने क्षेत्र का मूल्य 349363K है। फिर प्रस्तावित मूल्य -Xmx और -Xms ~ 1400 एमबी है। -XX: PermSize और -XX: MaxPermSize - पूर्ण कचरा संग्रह चरण के बाद PermGenSize से 1.5 गुना बड़ा - 13324K ~ 20 एमबी। मैं 1-1.5 लाइव डेटा की मात्रा के बराबर युवा पीढ़ी के आकार को स्वीकार करता हूं ~ 525 एमबी। फिर हमें निम्नलिखित मापदंडों के साथ jvm लॉन्च लाइन मिलती है:



 java -Xms1400m -Xmx1400m -Xmn525m -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      







VisualVm में हमें निम्न चित्र मिलते हैं:







प्रयोग के केवल 30 सेकंड में, 54 विधानसभाएं बनाई गईं - 31 छोटी और 23 पूर्ण - 3.227 सेकेंड के कुल रोक समय के साथ। यह देरी मूल्य आवश्यक आवश्यकताओं को पूरा नहीं कर सकता है - चलो देखते हैं कि क्या हम आवेदन कोड को बदलने के बिना स्थिति में सुधार कर सकते हैं।



स्वीकार्य प्रतिक्रिया समय निर्धारित करना



प्रतिक्रिया समय निर्धारित करते समय निम्नलिखित मापदंडों को मापा जाना चाहिए और ध्यान में रखा जाना चाहिए:





युवा और पुरानी पीढ़ी के आकार को समायोजित करना


छोटे कचरा संग्रह के चरण के कार्यान्वयन के लिए आवश्यक समय सीधे युवा पीढ़ी में वस्तुओं की संख्या पर निर्भर करता है, इसका आकार जितना छोटा होता है - अवधि कम, लेकिन आवृत्ति बढ़ जाती है, क्योंकि क्षेत्र अधिक बार भरने लगता है। आइए पुरानी पीढ़ी के आकार को बनाए रखते हुए, युवा पीढ़ी के आकार को कम करके प्रत्येक छोटी विधानसभा के समय को कम करने का प्रयास करें। यह अनुमान लगाना लगभग संभव है कि हर सेकेंड हमें युवा पीढ़ी में ५० धाराएँ * 1 वस्तुएं * १ एमबी ~ ४०० एमबी खाली करनी होती हैं। मापदंडों के साथ चलाएँ:



 java -Xms1275m -Xmx1275m -Xmn400m -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      







VisualVm में हमें निम्न चित्र मिलते हैं:







हम एक छोटे कचरा संग्रह के कुल परिचालन समय को प्रभावित नहीं कर सके - 1.533s - छोटी विधानसभाओं की आवृत्ति में वृद्धि हुई, लेकिन समग्र समय खराब हो गया - पुरानी पीढ़ी की बढ़ती भरण गति और पूर्ण कचरा संग्रह को कॉल करने की आवृत्ति के कारण 3.661। इसे दूर करने के लिए - आइए पुरानी पीढ़ी के आकार को बढ़ाने की कोशिश करें - मापदंडों के साथ jvm चलाएं:



 java -Xms1400m -Xmx1400m -Xmn400m -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      











कुल ठहराव में अब सुधार हुआ है और 2.637 s तक की मात्रा है, जबकि अनुप्रयोग के लिए आवश्यक मेमोरी का कुल मूल्य कम हो गया है - इस प्रकार एक विशेष अनुप्रयोग में वस्तुओं के जीवनकाल को वितरित करने के लिए पुरानी और युवा पीढ़ी के बीच सही संतुलन खोजना संभव है।



यदि देरी का समय अभी भी हमें सूट नहीं करता है, तो आप -XX विकल्प चालू करके समवर्ती कचरा संग्रहकर्ता के पास जा सकते हैं: + UseConcMarkSweepGC - एक एल्गोरिथ्म जो एप्लिकेशन थ्रेड्स के समानांतर एक अलग थ्रेड में विलोपन के लिए वस्तुओं को चिह्नित करने का मुख्य काम करने की कोशिश करेगा।



समवर्ती कचरा संग्राहक को कॉन्फ़िगर करना


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



 java -Xms1680m -Xmx1680m -Xmn400m -XX:+UseConcMarkSweepGC -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      











कुल ठहराव 1.923 सेकेंड हो गया।



उत्तरजीवी का आकार समायोजित करें


नीचे, ग्राफ़ के तहत, आप पुरानी पीढ़ी में आने से पहले ईडन, सर्वाइवर 1 और सर्वाइवर 2 के चरणों के बीच संक्रमण की संख्या से एप्लिकेशन की मेमोरी का वितरण देखते हैं। तथ्य यह है कि ConcMarkSweep GC में पुरानी पीढ़ी की ओवरफ्लो की संख्या को कम करने के तरीकों में से एक है, युवा पीढ़ी से वस्तुओं के सीधे प्रवाह को रोकना - बचे हुए क्षेत्र को दरकिनार करना।



चरणों में वस्तुओं के वितरण की निगरानी करने के लिए, आप -XX के साथ jvm चला सकते हैं: + PrintTenuringDistribution पैरामीटर।

Glog में हम देख सकते हैं:

 Desired survivor size 20971520 bytes, new threshold 1 (max 4) - age 1: 40900584 bytes, 40900584 total
      
      





उत्तरजीवी वस्तुओं का कुल आकार 40900584 है, सीएमएस डिफ़ॉल्ट रूप से उत्तरजीवी क्षेत्र को भरने के लिए 50% अवरोध का उपयोग करता है। इस प्रकार, हमें क्षेत्र का आकार ~ 80 एमबी मिलता है। जब jvm शुरू होता है, तो इसे -XX द्वारा निर्दिष्ट किया जाता है: उत्तरजीवी अनुपात, जो सूत्र से निर्धारित होता है:

 survivor space size = -Xmn<value>/(-XX:SurvivorRatio=<ratio> + 2)
      
      









हमें मिलता है

 java -Xms1680m -Xmx1680m -Xmn400m -XX:SurvivorRatio=3 -XX:+UseConcMarkSweepGC -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      





ईडन स्पेस के आकार को एक समान छोड़ने की कामना, हमें मिलता है:

 java -Xms1760m -Xmx1760m -Xmn480m -XX:SurvivorRatio=5 -XX:+UseConcMarkSweepGC -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      











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

 java -Xms1760m -Xmx1760m -Xmn480m -XX:SurvivorRatio=100 -XX:+UseConcMarkSweepGC -XX:PermSize=20m ru.skuptsov.MemoryConsumer
      
      











परिणाम



नतीजतन, हम प्रयोग के 30 सेकंड के लिए कुल ठहराव के आकार को 3.227 एस से 1.481 एस तक कम करने में सक्षम थे, जबकि कुल मेमोरी खपत में थोड़ी वृद्धि हुई। चाहे वह बहुत कम हो या थोड़ा - विशेष रूप से, विशिष्ट बारीकियों पर निर्भर करता है, विशेष रूप से भौतिक स्मृति की लागत को कम करने की प्रवृत्ति और उपयोग की गई स्मृति को अधिकतम करने के सिद्धांत को देखते हुए - यह अभी भी जीसी के विभिन्न क्षेत्रों के बीच एक संतुलन खोजना महत्वपूर्ण है और यह प्रक्रिया वैज्ञानिक की तुलना में अधिक रचनात्मक है।



All Articles