Java मेमोरी का विश्लेषण करना

Java और Kotlin ऐप्लिकेशन, कचरा इकट्ठा करने वाले हीप के ज़रिए मेमोरी मैनेज करते हैं. जब ऑब्जेक्ट का इस्तेमाल नहीं किया जाता है, तो गार्बेज कलेक्टर (GC) आखिर में उनकी मेमोरी को वापस ले लेता है. मेमोरी लीक तब होती है, जब अब इस्तेमाल में नहीं आने वाले ऑब्जेक्ट को "GC रूट" से हटाया नहीं जाता. इस वजह से, उन्हें वापस नहीं पाया जा सकता.

सबसे ज़रूरी सिद्धांत

जीसी रूट

GC रूट एक खास तरह का ऑब्जेक्ट होता है. गार्बेज कलेक्टर इसे हमेशा ऐक्सेस किया जा सकने वाला ऑब्जेक्ट मानता है. उदाहरण के लिए:

  • ऐक्टिव थ्रेड और उनके मौजूदा समय में चल रहे Java स्टैक फ़्रेम से रेफ़र किए गए ऑब्जेक्ट.
  • ऐसी क्लास जिनमें तरीके चालू हैं.
  • JNI रेफ़रंस (नेटिव कोड के पास मौजूद ग्लोबल या लोकल रेफ़रंस).

जीसी रूट का पाथ

जब तक किसी GC रूट से किसी ऑब्जेक्ट तक रेफ़रंस की चेन होती है, तब तक वह ऑब्जेक्ट "रीचेबल" होता है और उसे गार्बेज इकट्ठा नहीं किया जा सकता. इस चेन को जीसी रूट का पाथ कहा जाता है. मेमोरी लीक की समस्या को ठीक करने के लिए, आपको इस चेन का पता लगाकर इसे तोड़ना होगा.

जीसी रूट का पाथ

डोमिनेटर ट्री

जीसी रूट का पाथ यह बताता है कि कोई ऑब्जेक्ट क्यों चालू है. हालांकि, इससे यह नहीं पता चलता कि अगर उस रेफ़रंस को हटा दिया जाए, तो कितनी मेमोरी वापस मिल जाएगी. इसके लिए, हम डोमिनेटर ट्री का इस्तेमाल करते हैं.

ऑब्जेक्ट A को ऑब्जेक्ट B का डोमिनेटर कहा जाता है. ऐसा तब होता है, जब किसी भी जीसी रूट से B तक पहुंचने वाले हर पाथ को A से गुज़रना पड़ता है. अगर A, B पर हावी है, तो A को वापस पाने से यह भी पक्का हो जाएगा कि B को वापस पाया जा सकता है, क्योंकि किसी भी रूट से B तक पहुंचने का कोई अन्य तरीका नहीं है.

इस डायग्राम में, ऑब्जेक्ट ग्राफ़ और उससे जुड़ा डॉमिनेटर ट्री दिखाया गया है. ध्यान दें कि ग्राफ़ में ऑब्जेक्ट D तक A और B, दोनों से पहुंचा जा सकता है. इसलिए, न तो A और न ही B, D पर हावी है. इसके बजाय, GC रूट, D पर हावी होने वाला सबसे नज़दीकी ऑब्जेक्ट है.

डोमिनेटर ट्री

Java के हीप डंप पाना

हीप डंप, किसी खास समय पर Java हीप में मौजूद सभी ऑब्जेक्ट का स्नैपशॉट होता है.

एडीबी का इस्तेमाल करना

किसी प्रोसेस का हीप डंप कैप्चर करने के लिए, पैकेज का नाम सीधे am dumpheap को पास किया जा सकता है. इस कमांड को चलाने के लिए, आपको अपने ऐप्लिकेशन को <profileable android:shell="true"/> या <debuggable> के साथ बनाना होगा.

यह स्नैपशॉट, AHAT के लिए बिटमैप का विश्लेषण करने के लिए ज़रूरी है.
# 1. Trigger the dump (the command takes a moment to complete):
adb shell am dumpheap -g -b png com.android.memorylab /data/local/tmp/heap.hprof

# 2. Pull the file to your development machine:
adb pull /data/local/tmp/heap.hprof .

Perfetto का इस्तेमाल करना

Perfetto, सिस्टम-वाइड ट्रेस के हिस्से के तौर पर Java हीप डंप भी कैप्चर कर सकता है. इसके लिए, आपको Perfetto कॉन्फ़िगरेशन में android.java_hprof डेटा सोर्स को चालू करना होगा. यह हीप की स्थिति को अन्य सिस्टम इवेंट से जोड़ने के लिए उपयोगी है.

Perfetto का इस्तेमाल करके, MemoryLab ऐप्लिकेशन के लिए हीप डंप कैप्चर करने के लिए, इस निर्देश का इस्तेमाल किया जा सकता है:

# Create a temp file for the configuration
cat > /tmp/java_heap.pbtx <<EOF
data_sources: {
    config {
        name: "android.java_hprof"
        java_hprof_config {
            process_cmdline: "com.android.memorylab"
        }
    }
}
EOF

# Run trace command referencing the file
external/perfetto/tools/record_android_trace -o java_heap.perfetto-trace \
  -t 10s -c /tmp/java_heap.pbtx

देखें: Perfetto के दस्तावेज़ों में Java के हीप डंप.

AHAT की मदद से विश्लेषण करना

वेब ब्राउज़र में .hprof फ़ाइलें देखने के लिए, AHAT (Android Heap Analysis Tool) का इस्तेमाल करने का सुझाव दिया जाता है.

एएचएटी शुरू होने का समय

अगर आपने ahat को अपने पाथ पर इंस्टॉल किया है, तो इसे इस कमांड से लॉन्च करें:

ahat heap.hprof

इसके अलावा, अलग से उपलब्ध JAR फ़ाइल को चलाएं:

java -jar ahat.jar heap.hprof

इसके बाद, अपने ब्राउज़र में http://localhost:7100 खोलें.

AHAT पाने या बनाने के बारे में ज़्यादा जानने के लिए, AHAT सोर्स रिपॉज़िटरी देखें.

विश्लेषण से जुड़े मुख्य वर्कफ़्लो

लीक का पता लगाना

बंटवारा व्यू में, अपनी ऐक्टिविटी क्लास (MainActivity) खोजें.

इंस्टेंस दिखाने वाला एएचएटी व्यू

सभी उदाहरण ढूंढने के लिए, क्लास पर क्लिक करें.

AHAT व्यू में MainActivity के इंस्टेंस दिखाए गए हैं जांच करने के लिए, MainActivity इंस्टेंस पर क्लिक करें.

इस इमेज में, AHAT में किसी इंस्टेंस की जानकारी दिखाई गई है

इंस्टेंस व्यू में, आपको GC रूट से सैंपल पाथ दिखेगा. इससे रेफ़रंस की वह चेन दिखती है जिसकी वजह से ऑब्जेक्ट को गार्बेज इकट्ठा होने से रोका जाता है. साथ ही, आपको ऑब्जेक्ट का साइज़ दिखेगा. इससे पता चलता है कि इस खास इंस्टेंस के लिए कितनी मेमोरी सेव की जा रही है.

जीसी रूट और ऑब्जेक्ट साइज़ से AHAT सैंपल पाथ

बिटमैप का विश्लेषण किया जा रहा है

AHAT में, android.graphics.Bitmap ऑब्जेक्ट देखने की सुविधा उपलब्ध है. ये ऑब्जेक्ट, अक्सर ज़्यादा मेमोरी इस्तेमाल करते हैं. किसी बिटमैप इंस्टेंस पर क्लिक करके, उसके कॉन्टेंट की रेंडर की गई झलक देखें.

AHAT Bitmap Preview

गतिविधि लीक होने से जुड़ा पेज

AHAT में, लीक हुई गतिविधियों की पहचान करने के लिए एक खास व्यू होता है. ये Android में मेमोरी लीक होने की सबसे आम और असरदार वजहों में से एक हैं.

  1. कार्रवाई: MemoryLab में, Leak an Activity पर टैप करें. इससे LeakedActivity लॉन्च होता है, जो जान-बूझकर खुद को लीक करता है.
  2. डंप करें: हीप डंप लें.
  3. विश्लेषण करें: AHAT के साइडबार में मौजूद, गतिविधि से जुड़ी जानकारी लीक होने की घटनाएं पर क्लिक करें.
  4. पुष्टि करें: AHAT, com.android.memorylab.LeakedActivity को लीक हुई मेमोरी के तौर पर दिखाएगा, क्योंकि इसका mDestroyed फ़ील्ड सही है. इसका मतलब है कि गतिविधि पूरी होने की प्रोसेस खत्म हो गई है. हालांकि, यह अब भी किसी जीसी रूट से ऐक्सेस की जा सकती है.

AHAT की गतिविधि की जानकारी देने वाला पेज

हीप डंप की तुलना करना

दो हीप डंप की तुलना करना, मेमोरी से जुड़ी समस्याओं का पता लगाने का सबसे असरदार तरीका है. "क्लीन" बेसलाइन डंप की तुलना, कुछ कार्रवाइयां करने के बाद लिए गए डंप से करके, यह तुरंत देखा जा सकता है कि कौनसे ऑब्जेक्ट इकट्ठा हुए हैं.

एक्सरसाइज़: अंतर की तुलना करके लीक की पहचान करना

  1. बेसलाइन: MemoryLab लॉन्च करें और बेसलाइन हीप डंप लें:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/base.hprof
    adb pull /data/local/tmp/base.hprof .
    
  2. कार्रवाई: ऐप्लिकेशन में, Allocate Java Memory(10MB) पर कई बार टैप करें.

  3. फ़ाइनल: दूसरा हीप डंप लें:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/leaked.hprof
    adb pull /data/local/tmp/leaked.hprof .
    
  4. तुलना करें: दूसरे डंप को प्राइमरी और पहले डंप को बेसलाइन के तौर पर इस्तेमाल करके, एएचएटी शुरू करें:

    java -jar out/host/linux-x86/framework/ahat.jar leaked.hprof --baseline base.hprof
    
  5. खास जानकारी का विश्लेषण करें: खास जानकारी पेज में अब Δ (डेल्टा) कॉलम शामिल है. आपको app हीप के लिए, पॉज़िटिव डेल्टा दिखेगा. इससे पता चलता है कि मेमोरी में काफ़ी बढ़ोतरी हुई है.

डेल्टा के साथ AHAT की खास जानकारी

  1. ड्रिल-डाउन: मेन्यू में रूट किया गया पर क्लिक करें. इस पेज पर, GC रूट से ऐक्सेस किए जा सकने वाले ऑब्जेक्ट दिखते हैं. इन्हें इनके रिटेन किए गए साइज़ के हिसाब से क्रम में लगाया जाता है. आपको सबसे ऊपर MainActivity दिखेगा. इसमें पॉज़िटिव डेल्टा ज़्यादा होगा.

AHAT Rooted View with Delta

बंटवारे से जुड़े स्टैक ट्रेस रिकॉर्ड किए जा रहे हैं

GC रूट से सैंपल पाथ से आपको यह पता चलता है कि कोई ऑब्जेक्ट अब भी मौजूद क्यों है. हालांकि, इससे आपको यह पता नहीं चलता कि इसे कैसे बनाया गया था. ऐलोकेशन स्टैक ट्रेस से, उस कोड की सटीक लाइन के बारे में पता चलता है जिसने किसी ऑब्जेक्ट को ऐलोकेट किया है.

कॉन्सेप्ट और ट्रेड-ऑफ़: हर एक ऐलोकेशन के स्टैक ट्रेस को रिकॉर्ड करना, कंप्यूटेशनल तौर पर महंगा होता है. साथ ही, इससे काफ़ी मेमोरी खर्च होती है. बड़े प्रोडक्शन ऐप्लिकेशन में, इससे ऐप्लिकेशन का इस्तेमाल करना मुश्किल हो सकता है. हालांकि, MemoryLab एक छोटा ऐप्लिकेशन है. इसलिए, हम इस ट्रैकिंग को सुरक्षित तरीके से चालू कर सकते हैं, ताकि यह पता लगाया जा सके कि मेमोरी कहां से आ रही है.

एक्सरसाइज़: बाइट ऐरे के सोर्स की पहचान करना

  1. ट्रैकिंग शुरू करें: MemoryLab को बंद करें और --track-allocation फ़्लैग के साथ इसे फिर से शुरू करें. ज़्यादा कॉन्टेक्स्ट कैप्चर करने के लिए, डिफ़ॉल्ट स्टैक डेप्थ बढ़ाएं.

    # Increase the allocation tracker's stack depth (requires a process restart)
    adb shell setprop dalvik.vm.allocTrackerMaxStack 16
    
    adb shell am force-stop com.android.memorylab
    adb shell am start --track-allocation -n com.android.memorylab/.MainActivity
    
  2. कार्रवाई: Allocate Java Memory(10MB) पर कुछ बार टैप करें.

  3. डंप: हीप डंप लें और उसे पुल करें.

  4. विश्लेषण करें: डंप को AHAT में खोलें. किसी बड़े byte[] इंस्टेंस पर जाएं. उदाहरण के लिए, जांच करें MainActivity → mJavaAllocations (ArrayList) → elementData (Object[]) → ऐरे एलिमेंट [0]).

  5. पुष्टि करें: इंस्टेंस व्यू में, एलॉकेशन साइट सेक्शन देखें. इससे MainActivity.allocateJava की वजह से हुई पूरी स्टैक ट्रेस दिखेगी.

AHAT Allocation Site

डुप्लीकेट स्ट्रिंग और हाइड्रेशन ब्लोट का पता लगाना

भले ही, किसी ऐप्लिकेशन में क्लासिक GC-रूट लीक न हो, लेकिन उसके लाइव Java हीप में हज़ारों डुप्लीकेट java.lang.String इंस्टेंस हो सकते हैं. ये इंस्टेंस, JSON, Protobuf, Cursor या Room डेटाबेस के डिसिरियलाइज़ेशन के दौरान बनाए जाते हैं. बार-बार इस्तेमाल किए गए कुंजियों, स्टेटस स्ट्रिंग, कैटगरी के लेबल या यूआरएल को अक्सर हर नेटवर्क रिस्पॉन्स या डेटाबेस क्वेरी पर नया असाइन किया जाता है. बड़े फ़ीड, मैसेजिंग, और कॉन्टेंट ऐप्लिकेशन में, डुप्लीकेट स्ट्रिंग की वजह से 30% से 60% तक लाइव String मेमोरी का इस्तेमाल होता है.

AHAT में डुप्लीकेट स्ट्रिंग की जांच करने के लिए:

  1. बंटवारा पेज खोलें और java.lang.String के हिसाब से फ़िल्टर करें.
  2. --baseline की मदद से दो हीप डंप की तुलना करते समय, देखें कि फ़ीड को हाइड्रेट करने या लोकल कैश मेमोरी लोड करने के बाद, java.lang.String इंस्टेंस की संख्या और कुल बाइट में काफ़ी बढ़ोतरी हुई है या नहीं.
  3. java.lang.String इंस्टेंस टेबल (साइज़ या वैल्यू के हिसाब से क्रम में लगाई गई) ब्राउज़ करें. इससे आपको एक जैसी स्ट्रिंग वैल्यू का पता चलेगा. ये वैल्यू, मेमोरी में मौजूद कई मॉडल ऑब्जेक्ट में सेव रहती हैं.
  • समस्या हल करने का तरीका: आर्बिट्रेरी उपयोगकर्ता या नेटवर्क इनपुट पर String.intern() को बिना सोचे-समझे कॉल करने से बचें, क्योंकि रनटाइम इंटर्न टेबल ग्लोबल होती है. इससे लॉक कंटेंशन हो सकता है या स्ट्रिंग को ज़रूरत से ज़्यादा समय तक सेव रखा जा सकता है. इसके बजाय, डीसीरियलाइज़ेशन के दौरान, ज़्यादा बार इस्तेमाल होने वाली डोमेन स्ट्रिंग को डुप्लीकेट होने से रोकें. इसके लिए, डुप्लीकेट होने से रोकने वाली सीमित दायरे वाली कैश मेमोरी का इस्तेमाल करें. जैसे, LruCache<String, String> आपके पार्सर या अडैप्टर में मौजूद कैश मेमोरी. इसके अलावा, वैल्यू के तय किए गए सेट को enum या पूर्णांक स्थिरांक के तौर पर दिखाएं.

Java की मेमोरी के डाइनैमिक का विश्लेषण करना (कंबाइंड प्रोफ़ाइल)

किसी ऐप्लिकेशन के मेमोरी इस्तेमाल करने के तरीके की पूरी जानकारी पाने के लिए, मेमोरी काउंटर, थ्रेड गतिविधि, और कॉलस्टैक पर आधारित मेमोरी एलोकेशन की प्रोफ़ाइलिंग को एक ही Perfetto ट्रेस में शामिल किया जा सकता है. इससे आपको सिस्टम-वाइड मेमोरी मेट्रिक (जैसे, आरएसएस और हीप साइज़) को कोड के खास एक्ज़ीक्यूशन और ऐलोकेशन साइटों से जोड़ने में मदद मिलती है.

हम एक ऐसे मिले-जुले कॉन्फ़िगरेशन का इस्तेमाल करेंगे जो इन सुविधाओं को चालू करता है:

  • मेमोरी काउंटर (linux.process_stats): आरएसएस और मेमोरी से जुड़ी अन्य मेट्रिक पोल करता है.
  • ATrace (dalvik, memory, sched कैटगरी): थ्रेड की स्थितियां और GC इवेंट कैप्चर करता है.
  • Heapprofd (android.heapprofd): यह com.android.art (Java) और libc.malloc (नेटिव), दोनों ही हीप को टारगेट करता है. साथ ही, हर पांच सेकंड में लगातार डंप करता है.

एक्सरसाइज़: मेमोरी के इस्तेमाल का विश्लेषण

इस अभ्यास में, हम MemoryLab ऐप्लिकेशन चलाएंगे और मेमोरी से जुड़ी कई कार्रवाइयां करेंगे, ताकि ट्रेस में अलग-अलग पैटर्न देखे जा सकें:

  1. बेसलाइन: कोई काम नहीं हो रहा है.
  2. Java Churn: कुछ समय के लिए किए गए ऐसे ऐलोकेशन जिन्हें तुरंत गार्बेज कलेक्शन के लिए भेज दिया जाता है.
  3. परसिस्टेंट Java ऐलोकेशन: ऐसे Java ऑब्जेक्ट ऐलोकेट करना जो मेमोरी में बने रहते हैं.
  4. बिट मैप का बंटवारा: बड़ी ग्राफ़िक ऐसेट का बंटवारा करना. ये ऐसेट, नेटिव हीप/ग्राफ़िक्स मेमोरी में मौजूद होती हैं.
  5. वापस पाना: सभी असाइन किए गए संसाधनों को वापस पाना.

1. लॉन्च करना और तैयारी करना

  1. ऐप्लिकेशन को ज़बरदस्ती बंद करके फिर से चालू करें, ताकि यह पक्का किया जा सके कि ऐप्लिकेशन सही तरीके से काम कर रहा है:

    adb shell am force-stop com.android.memorylab
    adb shell am start -W -n com.android.memorylab/.MainActivity
    

2. ट्रेसिंग शुरू करना और ट्रिगर सीक्वेंस

हम 40 सेकंड का ट्रेस शुरू करेंगे और am broadcast कमांड का इस्तेमाल करके मेमोरी इवेंट ट्रिगर करेंगे.

  1. ट्रेस शुरू करें:

    adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/java_memory.perfetto-trace <<EOF
    buffers: {
        size_kb: 131072
        fill_policy: RING_BUFFER
    }
    data_sources: {
        config {
            name: "linux.process_stats"
            target_buffer: 0
            process_stats_config {
                scan_all_processes_on_start: true
                proc_stats_poll_ms: 100
            }
        }
    }
    data_sources: {
        config {
            name: "linux.ftrace"
            target_buffer: 0
            ftrace_config {
                ftrace_events: "sched/sched_switch"
                ftrace_events: "task/task_newtask"
                ftrace_events: "task/task_rename"
                ftrace_events: "ftrace/print"
                atrace_categories: "dalvik"
                atrace_categories: "am"
                atrace_categories: "res"
                atrace_categories: "memory"
                atrace_categories: "sched"
                atrace_apps: "com.android.memorylab"
            }
        }
    }
    data_sources: {
        config {
            name: "android.heapprofd"
            target_buffer: 0
            heapprofd_config {
                sampling_interval_bytes: 4096
                process_cmdline: "com.android.memorylab"
                heaps: "libc.malloc"
                heaps: "com.android.art"
                shmem_size_bytes: 8388608
                block_client: true
                continuous_dump_config {
                    dump_phase_ms: 1000
                    dump_interval_ms: 5000
                }
            }
        }
    }
    duration_ms: 40000
    EOF
    
  2. सीक्वेंस ट्रिगर करें (ट्रेसिंग चालू रहने के दौरान, अपने होस्ट टर्मिनल में ये कमांड चलाएं. साथ ही, सुझाई गई समयावधि का ध्यान रखें):

    # Wait ~5s for trace initialization, then start Java churn:
    adb shell am broadcast -a com.android.memorylab.CHURN_JAVA
    
    # Wait ~10s (at 15s mark), allocate 10MB of persistent Java memory:
    adb shell am broadcast -a com.android.memorylab.ALLOC_JAVA
    
    # Wait ~5s (at 20s mark), allocate 20MB of Bitmaps (native/graphics):
    adb shell am broadcast -a com.android.memorylab.LEAK_BITMAP
    
    # Wait ~10s (at 30s mark), free everything:
    adb shell am broadcast -a com.android.memorylab.FREE_ALL
    
  3. दूसरा तरीका (सीएलआई टूल): heap_profile स्क्रिप्ट का इस्तेमाल करके भी प्रोफ़ाइलिंग शुरू की जा सकती है. इससे Java और नेटिव, दोनों ही हीप को टारगेट किया जा सकता है. साथ ही, लगातार डंप किए जा सकते हैं:

    external/perfetto/tools/heap_profile -n com.android.memorylab \
      --heaps com.android.art,libc.malloc \
      -c 5000 \
      -d 40000 \
      -o java_memory_profile
    

3. कंबाइंड ट्रेस का विश्लेषण करना

Perfetto UI में, इकट्ठा किया गया java_memory.perfetto-trace खोलें.

Perfetto में मुख्य ट्रैक

टाइमलाइन का विश्लेषण करने से पहले, com.android.memorylab प्रोसेस के लिए इन ज़रूरी ट्रैक का पता लगाएं:

  1. mem.rss.anon (बिना सोर्स फ़ाइल वाली आरएसएस मेमोरी): यह प्रोसेस के मेमोरी सेक्शन में मौजूद होती है. यह ट्रैक, ओएस की ओर से प्रोसेस को असाइन की गई फ़िज़िकल मेमोरी (रैम) को मेज़र करता है. इससे मेमोरी फ़ुटप्रिंट का पता चलता है.
  2. Heap size (KB): यह मेमोरी सेक्शन में भी दिखती है. यह Dalvik/ART के लिए खास काउंटर है. यह Java हीप के लिए रिज़र्व किए गए वर्चुअल पते की जगह को दिखाता है. इससे वीएम की इंटरनल हीप लिमिट के बारे में पता चलता है. यह ऑब्जेक्ट के हिसाब से बदलती रहती है. साथ ही, GC के चलने पर भी इसमें बदलाव होता है.
  3. HeapTaskDaemon: यह प्रोसेस के तहत थ्रेड की सूची में मौजूद होता है. यह बैकग्राउंड थ्रेड है, जहां ART Garbage Collector ज़्यादातर काम करता है. यहां की गतिविधि से पता चलता है कि GC पास चालू हैं.
  4. लगातार मेमोरी असाइन करने की जानकारी (heapprofd): इसे सबसे ऊपर मौजूद टाइमलाइन के साथ-साथ रंगीन स्लाइस के तौर पर दिखाया जाता है. हर स्लाइस, समय की अवधि को दिखाता है. किसी एक स्लाइस पर क्लिक करने या समयसीमा चुनने से, आपको फ़्लेमग्राफ़ (सबसे नीचे वाले पैन में) की जांच करने की सुविधा मिलती है. इससे यह पता चलता है कि उस अवधि के दौरान क्या असाइन किया गया था. इसके लिए, com.android.art (Java के लिए मेमोरी असाइनमेंट) या libc.malloc (नेटिव के लिए मेमोरी असाइनमेंट) में से किसी एक को चुना जा सकता है.

समय के हिसाब से फ़ेज़ का विश्लेषण

आइए, समय के हिसाब से ट्रेस की समीक्षा करें. इससे पता चलेगा कि कसरत के हर चरण के दौरान, ये ट्रैक एक-दूसरे से कैसे इंटरैक्ट करते हैं.

पहला चरण: बेसलाइन (0 से 5 सेकंड)
  • क्या हो रहा है: ऐप्लिकेशन कुछ समय से इस्तेमाल में नहीं है, निर्देशों का इंतज़ार कर रहा है.
  • ट्रैक का स्टेटस:
    • mem.rss.anon: बेसलाइन पर फ़्लैट लाइन (आम तौर पर, डिवाइस के हिसाब से 60 से 80 एमबी के आस-पास).
    • Heap size (KB): फ़्लैट लाइन, जो Java के हीप के शुरुआती असाइनमेंट से मेल खाती है.
    • HeapTaskDaemon: कोई गतिविधि नहीं हो रही है (एक्ज़ीक्यूशन दिखाने वाले कोई स्लाइस नहीं दिख रहे हैं).
    • एलॉकेशन डंप: इसमें कम से कम बेसलाइन एलॉकेशन दिखाए जाते हैं.

Perfetto के यूज़र इंटरफ़ेस (यूआई) में, पहले चरण की बेसलाइन दिखाई गई है

दूसरा चरण: Java के लिए मेमोरी का बार-बार इस्तेमाल होना (5 से 15 सेकंड)
  • क्या हो रहा है: AllocationChurnThread शुरू हो गया है. इसमें बार-बार 1 एमबी के ऐरे असाइन किए जा रहे हैं और उन्हें खारिज किया जा रहा है.
  • ट्रैक का स्टेटस:
    • Heap size (KB): इसमें सॉटूथ पैटर्न दिखता है. मेमोरी में ऑब्जेक्ट के लिए जगह (हीप साइज़) तब बढ़ती है, जब ऑब्जेक्ट के लिए जगह बढ़ती है. हालांकि, जब कचरा इकट्ठा करने की प्रोसेस शुरू होती है, तब यह तेज़ी से कम हो जाती है.
    • HeapTaskDaemon: इसमें लगातार होने वाली गतिविधि दिखती है. साथ ही, एक्ज़ीक्यूशन स्लाइस, Heap size सॉटूथ में होने वाली गिरावट के साथ पूरी तरह से अलाइन होते हैं.
    • mem.rss.anon: यह कुकी, Java हीप की गतिविधि को ट्रैक करती है.
    • Java Heap Allocation Dumps: इस ट्रैक में स्लाइस चुनने पर, com.android.art के हीप असाइनमेंट दिखते हैं.

Perfetto के यूज़र इंटरफ़ेस (यूआई) में, दूसरे चरण में ऐप्लिकेशन अनइंस्टॉल करने वाले लोगों की संख्या दिखाई गई है सैंपल के हिसाब से, AllocationChurnThread को प्राइमरी ऐलोकेटर के तौर पर दिखाया गया है. सभी ऐलोकेशन, एक ही कॉलस्टैक शेयर करते हैं. यह कॉलस्टैक, MainActivity.java के अंदर मौजूद लैम्डा की ओर इशारा करता है.

Perfetto के यूज़र इंटरफ़ेस (यूआई) की इमेज, जिसमें दूसरे फ़ेज़ के Java ऐलोकेशन दिख रहे हैं

तीसरा फ़ेज़: लगातार Java मेमोरी का इस्तेमाल (15 से 20 सेकंड)
  • क्या हो रहा है: हम 10 एमबी के Java ऑब्जेक्ट असाइन करते हैं और mJavaAllocations में उनका रेफ़रंस सेव करते हैं.
  • ट्रैक का स्टेटस:
    • Heap size (KB): सॉटूथ का बेसलाइन करीब 10 एमबी तक बढ़ जाता है.
    • mem.rss.anon: यह करीब 10 एमबी तक बढ़ जाता है, क्योंकि ओएस को इस परसिस्टेंट ऐलोकेशन को नए फ़िज़िकल पेजों के साथ बैकअप लेना होता है.
    • Java Heap Allocation Dumps: इस ट्रैक में स्लाइस चुनने पर, com.android.art के हीप असाइनमेंट दिखते हैं.
    • ऐलोकेशन डंप (फ़्लेमग्राफ़): इस विंडो में लिए गए डंप के लिए, com.android.art हीप की जांच करने पर, MainActivity.allocateJava से एक नया ऐलोकेशन पाथ दिखता है. इससे बनाए गए साइज़ में मदद मिलती है.

Perfetto के यूज़र इंटरफ़ेस (यूआई) की इमेज, जिसमें तीसरे चरण में लगातार Java मेमोरी का इस्तेमाल दिखाया गया है ऐसा सैंपल चुनें जिसमें परसिस्टेंट ऐलोकेशन के लिए, 10 एमबी की बढ़ोतरी वाली अवधि शामिल हो. आपको दिखेगा कि कॉलस्टैक, दो अलग-अलग साइटों पर डाइवर्ज हो रहे हैं. इनमें से एक साइट, कम समय के लिए किए गए उसी एलॉकेशन के लिए ज़िम्मेदार है जिसे हमने पहले देखा था. वहीं, दूसरी साइट, लंबे समय के लिए किए गए नए एलॉकेशन के लिए ज़िम्मेदार है.

Perfetto के यूज़र इंटरफ़ेस (यूआई) की इमेज, जिसमें तीसरे फ़ेज़ के Java ऐलोकेशन दिखाए गए हैं

चौथा चरण: बिटमैप असाइन करना (20 से 30 सेकंड)
  • क्या हो रहा है: हम 20 एमबी के बिटमैप भी असाइन करते हैं.
  • ट्रैक का स्टेटस:
    • Heap size (KB): पहले जैसा ही.
    • mem.rss.anon: इसमें करीब 20 एमबी की बढ़ोतरी दिखती है. यह Bitmap पिक्सल डेटा के लिए नेटिव ऐप्लिकेशन को दिए गए साइज़ के बराबर है.
    • ऐलोकेशन डंप (फ़्लेमग्राफ़): इस बार, libc.malloc (नेटिव) हीप के स्लाइस पर फ़ोकस करें.

Perfetto यूज़र इंटरफ़ेस (यूआई) में, चौथे फ़ेज़ के बिट मैप के लिए मेमोरी असाइनमेंट दिखाने वाली इमेज नेटिव ऐलोकेशन के कॉलस्टैक से पता चलता है कि नेटिव ग्राफ़िक्स लाइब्रेरी से Bitmap ऐलोकेट किया गया है. यह नेटिव ऐलोकेशन ट्रैकिंग का एक अच्छा उदाहरण है, क्योंकि आपको Java हीप में ये बिटमैप ऐलोकेशन नहीं दिखेंगे.

Perfetto के यूज़र इंटरफ़ेस (यूआई) की इमेज, जिसमें चौथे फ़ेज़ के नेटिव ऐलोकेशन दिखाए गए हैं

पांचवां चरण: वापस पाना (30 से 40 सेकंड)
  • क्या हो रहा है: हम FREE_ALL को ट्रिगर करते हैं. इससे सभी परसिस्टेंट Java ऐलोकेशन और बिटमैप के रेफ़रंस मिट जाते हैं. इसके बाद, हम साफ़ तौर पर System.gc() को ट्रिगर करते हैं.
  • ट्रैक का स्टेटस:
    • Heap size (KB): यह वापस बेसलाइन लेवल पर आ जाता है.
    • mem.rss.anon: यह वापस कम हो जाता है. इससे पता चलता है कि ओएस, फ़िज़िकल पेजों को वापस ले रहा है.
    • HeapTaskDaemon: यह कचरा इकट्ठा करने की प्रोसेस के दौरान, गतिविधि में अचानक बढ़ोतरी दिखाता है.

Perfetto के यूज़र इंटरफ़ेस (यूआई) की इमेज, जिसमें पांचवें फ़ेज़ का रीक्लेमेशन दिखाया गया है

ऐतिहासिक ओओएम (ApplicationExitInfo) की निगरानी करना

एलएमके की गड़बड़ी का पता तुरंत लगाने से, ऐक्टिव डीबगिंग में मदद मिलती है. हालांकि, फ़ील्ड टेलीमेट्री के लिए, ApplicationExitInfo एपीआई का इस्तेमाल किया जा सकता है. इससे आपका ऐप्लिकेशन यह पता लगा पाता है कि पिछले सेशन में उसे क्यों बंद किया गया था.

ActivityManager am = getSystemService(ActivityManager.class);
List<ApplicationExitInfo> exitReasons = am.getHistoricalProcessExitReasons(null, 0, 1);
if (!exitReasons.isEmpty()) {
    ApplicationExitInfo info = exitReasons.get(0);
    if (info.getReason() == ApplicationExitInfo.REASON_LOW_MEMORY) {
        // App was killed by the system Low Memory Killer
    }
}

सबसे सही तरीके

  1. बेसलाइन फ़र्स्ट: ऐप्लिकेशन के शुरू होने के बाद हमेशा "बेसलाइन" हीप डंप लें. हालांकि, ऐसा उस कार्रवाई को करने से पहले करें जिसकी आपको जांच करनी है.
  2. AHAT के 'ऐक्टिविटी लीक' पेज का इस्तेमाल करें: AHAT में ऐक्टिविटी लीक पेज शामिल होता है. यह पेज, ऐक्टिविटी के उन इंस्टेंस की अपने-आप पहचान करता है जिन्हें बंद कर दिया गया है, लेकिन वे अब भी मेमोरी में सेव हैं. आम तौर पर, इस तरीके से सामान्य लीक का पता सबसे तेज़ी से चलता है.
  3. जीसी रूट का पाथ देखें: लीक हुए किसी भी ऑब्जेक्ट के लिए, AHAT में रूट से पाथ व्यू का इस्तेमाल करें.इससे आपको यह समझने में मदद मिलेगी कि कौनसे रेफ़रंस की वजह से ऑब्जेक्ट मेमोरी में बना हुआ है. उदाहरण के लिए, कोई स्टैटिक फ़ील्ड, लंबे समय तक चलने वाला थ्रेड या रजिस्टर किया गया लिसनर.

← टूल | ↑ ऊपर जाएं | बिटमैप →