জাভা মেমরি বিশ্লেষণ

জাভা এবং কোটলিন অ্যাপ্লিকেশনগুলো গার্বেজ-কালেক্টেড হিপের মাধ্যমে মেমরি পরিচালনা করে। যখন অবজেক্টগুলো আর অ্যাক্সেসযোগ্য থাকে না, তখন গার্বেজ কালেক্টর (GC) অবশেষে সেগুলোর জায়গা পুনরুদ্ধার করে। মেমরি লিক তখন ঘটে যখন অপ্রয়োজনীয় অবজেক্টগুলো "GC রুট"-এর দখলে থেকে যায়, যা সেগুলোকে পুনরুদ্ধার হতে বাধা দেয়।

মূল ধারণা

জিসি রুটস

GC Root হলো এক বিশেষ ধরনের অবজেক্ট, যাকে গার্বেজ কালেক্টর সর্বদা নাগালের মধ্যে বলে মনে করে। উদাহরণস্বরূপ:

  • সক্রিয় থ্রেডসমূহ (এবং তাদের বর্তমানে চলমান জাভা স্ট্যাক ফ্রেম থেকে রেফারেন্সকৃত অবজেক্টসমূহ)।
  • যেসব ক্লাসে সক্রিয়ভাবে চলমান মেথড রয়েছে।
  • JNI রেফারেন্স (নেটিভ কোড দ্বারা ধারণকৃত গ্লোবাল বা লোকাল রেফারেন্স)।

GC রুটের পথ

যতক্ষণ পর্যন্ত একটি GC Root থেকে কোনো অবজেক্টের দিকে রেফারেন্সের একটি শৃঙ্খল থাকে, ততক্ষণ সেই অবজেক্টটি "প্রাপ্য" থাকে এবং গার্বেজ কালেক্টেড হতে পারে না। এই শৃঙ্খলটিকে Path to GC Root বলা হয়। একটি মেমোরি লিক ঠিক করতে হলে, আপনাকে অবশ্যই এই শৃঙ্খলটি শনাক্ত করে ভাঙতে হবে।

GC রুটের পথ

প্রভাবশালী গাছ

যদিও GC রুটের পাথ আপনাকে বলে দেয় কেন একটি অবজেক্ট সচল আছে, কিন্তু সেই রেফারেন্সটি ভেঙে গেলে কী পরিমাণ মেমরি পুনরুদ্ধার করা হবে, তা এটি বলে না। এর জন্য আমরা ডমিনেটর ট্রি ব্যবহার করি।

অবজেক্ট A- কে অবজেক্ট B- এর উপর ডমিনেট করে বলা হয়, যদি যেকোনো GC রুট থেকে B পর্যন্ত প্রতিটি পথকে অবশ্যই A-এর মধ্য দিয়ে যেতে হয়। যদি A , B-কে ডমিনেট করে, তাহলে A- কে রিক্লেইম করলে এটাও নিশ্চিত হয় যে B-কেও রিক্লেইম করা যাবে, কারণ যেকোনো রুট থেকে B পর্যন্ত অন্য কোনো পথ থাকে না।

নিম্নোক্ত ডায়াগ্রামটিতে একটি অবজেক্ট গ্রাফ এবং এর সংশ্লিষ্ট ডমিনেটর ট্রি দেখানো হয়েছে। লক্ষ্য করুন, গ্রাফটিতে A এবং B উভয়েই অবজেক্ট D- তে পৌঁছাতে পারে, ফলে A বা B কেউই D-কে ডমিনেট করে না; বরং, GC রুটটিই হলো এর নিকটতম ডমিনেটর।

আধিপত্যকারী গাছ

জাভা হিপ ডাম্প সংগ্রহ করা

হিপ ডাম্প হলো একটি নির্দিষ্ট সময়ে জাভা হিপে থাকা সমস্ত অবজেক্টের একটি স্ন্যাপশট।

এডিবি ব্যবহার করে

চলমান কোনো প্রসেস থেকে হিপ ডাম্প ক্যাপচার করতে, আপনি সরাসরি am dumpheap কমান্ডে প্যাকেজের নামটি পাস করতে পারেন। এই কমান্ডটি চালানোর জন্য, আপনাকে অবশ্যই <profileable android:shell="true"/> অথবা <debuggable> দিয়ে আপনার অ্যাপটি বিল্ড করতে হবে।

# 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 কনফিগে android.java_hprof ডেটা সোর্সটি সক্রিয় করার মাধ্যমে, Perfetto একটি সিস্টেম-ব্যাপী ট্রেসের অংশ হিসেবে জাভা হিপ ডাম্পও ক্যাপচার করতে পারে। হিপ স্টেটকে অন্যান্য সিস্টেম ইভেন্টের সাথে সম্পর্কযুক্ত করার জন্য এটি উপযোগী।

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 heap dumps

AHAT দিয়ে বিশ্লেষণ

ওয়েব ব্রাউজারে .hprof ফাইল দেখার জন্য AHAT (Android Heap Analysis Tool) হলো প্রস্তাবিত টুল।

AHAT শুরু হচ্ছে

আপনার পাথে যদি ahat ইনস্টল করা থাকে, তবে এটি এভাবে চালু করুন:

ahat heap.hprof

অথবা স্বতন্ত্র জারটি চালান:

java -jar ahat.jar heap.hprof

এরপর আপনার ব্রাউজারে http://localhost:7100 খুলুন।

AHAT সংগ্রহ বা তৈরি করার বিষয়ে বিস্তারিত জানতে, AHAT সোর্স রিপোজিটরি দেখুন।

মূল বিশ্লেষণ কর্মপ্রবাহ

লিক খুঁজে বের করা

অ্যালোকেশন ভিউতে আপনার অ্যাক্টিভিটি ক্লাস ( MainActivity ) খুঁজুন।

AHAT ভিউতে ইনস্ট্যান্সগুলো দেখানো হচ্ছে

সমস্ত ইনস্ট্যান্স খুঁজে পেতে ক্লাসটিতে ক্লিক করুন।

AHAT ভিউতে MainActivity ইনস্ট্যান্সগুলো দেখানো হচ্ছে MainActivity ইনস্ট্যান্সটি পরিদর্শন করতে সেটির উপর ক্লিক করুন।

AHAT একটি ইনস্ট্যান্সের বিবরণ দেখাচ্ছে

ইনস্ট্যান্স ভিউতে, আপনি ' স্যাম্পল পাথ ফ্রম জিসি রুট' (Sample Path from GC Root) দেখতে পাবেন, যা সেইসব রেফারেন্সের শৃঙ্খল দেখায় যেগুলো অবজেক্টটিকে গার্বেজ কালেকশন থেকে বাধা দিচ্ছে, এবং ' অবজেক্ট সাইজ' (Object Size) দেখতে পাবেন, যা দেখায় এই নির্দিষ্ট ইনস্ট্যান্সটি কী পরিমাণ মেমরি ধরে রাখছে।

GC রুট থেকে AHAT স্যাম্পল পাথ এবং অবজেক্ট সাইজ

বিটম্যাপ বিশ্লেষণ

AHAT-এ android.graphics.Bitmap অবজেক্ট দেখার জন্য বিশেষ ব্যবস্থা রয়েছে, যেগুলো প্রায়শই প্রচুর মেমরি ব্যবহার করে। একটি Bitmap ইনস্ট্যান্সের উপর ক্লিক করলে এর বিষয়বস্তুর একটি রেন্ডার করা প্রিভিউ দেখা যায়।

AHAT বিটম্যাপ প্রিভিউ

কার্যকলাপ ফাঁসের পৃষ্ঠা

AHAT-এর একটি বিশেষায়িত ভিউ রয়েছে যা লিক হওয়া অ্যাক্টিভিটিগুলো শনাক্ত করতে পারে, যা অ্যান্ড্রয়েডের অন্যতম সাধারণ এবং প্রভাবশালী মেমোরি লিকগুলোর মধ্যে একটি।

  1. করণীয় : মেমোরিল্যাবে, ‘Leak an Activity’- তে ট্যাপ করুন। এটি LeakedActivity চালু করে, যা ইচ্ছাকৃতভাবে নিজেকে ফাঁস করে দেয়।
  2. Dump : Take a heap dump (একসাথে অনেকগুলো মলত্যাগ করা)।
  3. বিশ্লেষণ করুন : AHAT সাইডবারে থাকা ‘Activity Leaks’- এ ক্লিক করুন।
  4. যাচাই করুন : AHAT, com.android.memorylab.LeakedActivity লিকড হিসেবে তালিকাভুক্ত করবে, কারণ এর mDestroyed ফিল্ডটি true (যা অ্যাক্টিভিটির লাইফসাইকেল শেষ হয়ে গেছে নির্দেশ করে), কিন্তু এটি এখনও একটি GC রুট থেকে অ্যাক্সেসযোগ্য।

AHAT কার্যকলাপ ফাঁস পৃষ্ঠা

হিপ ডাম্পের পার্থক্য

দুটি হিপ ডাম্প তুলনা করা মেমোরি সমস্যা শনাক্ত করার অন্যতম শক্তিশালী উপায়। একটি 'পরিষ্কার' বেসলাইন ডাম্পের সাথে কিছু কাজ করার পরে নেওয়া ডাম্প তুলনা করে, আপনি তাৎক্ষণিকভাবে দেখতে পারেন কোন অবজেক্টগুলো জমা হয়েছে।

অনুশীলন: ডিফারেন্সিংয়ের মাধ্যমে লিকেজ শনাক্তকরণ

  1. বেসলাইন : মেমোরিল্যাব চালু করুন এবং একটি বেসলাইন হিপ ডাম্প নিন:

    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. তুলনা করুন : দ্বিতীয় ডাম্পটিকে প্রাথমিক এবং প্রথমটিকে বেসলাইন হিসেবে ব্যবহার করে AHAT শুরু করুন:

    java -jar out/host/linux-x86/framework/ahat.jar leaked.hprof --baseline base.hprof
    
  5. ওভারভিউ বিশ্লেষণ করুন : ওভারভিউ পৃষ্ঠায় এখন একটি Δ (ডেল্টা) কলাম অন্তর্ভুক্ত করা হয়েছে। আপনি app হিপের জন্য একটি বড় ধনাত্মক ডেল্টা দেখতে পাবেন, যা উল্লেখযোগ্য মেমোরি বৃদ্ধি নির্দেশ করে।

ডেল্টার সাথে AHAT-এর সংক্ষিপ্ত বিবরণ

  1. বিস্তারিত দেখুন : মেনুতে থাকা ‘rooted’ অপশনে ক্লিক করুন। এই পেজটি GC রুট থেকে পৌঁছানো যায় এমন অবজেক্টগুলো দেখাবে, যা তাদের রিটেইনড সাইজ অনুযায়ী সাজানো থাকবে। আপনি সবার উপরে একটি বড় ধনাত্মক ডেল্টা সহ MainActivity দেখতে পাবেন।

ডেল্টা সহ AHAT রুটেড ভিউ

অ্যালোকেশন স্ট্যাক ট্রেস রেকর্ড করা

যদিও GC Root থেকে প্রাপ্ত স্যাম্পল পাথ আপনাকে জানায় কেন একটি অবজেক্ট এখনও সচল আছে, কিন্তু এটি কীভাবে তৈরি হয়েছিল তা জানায় না। অ্যালোকেশন স্ট্যাক ট্রেস আপনাকে কোডের সেই সুনির্দিষ্ট লাইনটি জানায় যেখান থেকে একটি অবজেক্ট অ্যালোকেট করা হয়েছিল।

ধারণা ও সীমাবদ্ধতা : প্রতিটি অ্যালোকেশনের স্ট্যাক ট্রেস রেকর্ড করা গণনাগতভাবে ব্যয়বহুল এবং এতে উল্লেখযোগ্য পরিমাণে মেমরি খরচ হয়। একটি বড় প্রোডাকশন অ্যাপে, এটি অ্যাপটিকে প্রায় অকেজো করে তুলতে পারে। তবে, মেমোরিল্যাব যথেষ্ট ছোট একটি অ্যাপ্লিকেশন হওয়ায়, আমরা অ্যালোকেশনের উৎস সঠিকভাবে চিহ্নিত করার জন্য নিরাপদে এই ট্র্যাকিংটি চালু করতে পারি।

অনুশীলন: বাইট অ্যারের উৎস শনাক্তকরণ

  1. ট্র্যাকিং দিয়ে শুরু করুন : মেমোরিল্যাব জোর করে বন্ধ করুন এবং --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[] ইনস্ট্যান্সে যান। (উদাহরণস্বরূপ, MainActivitymJavaAllocations ( ArrayList ) → elementData ( Object[] ) → অ্যারে এলিমেন্ট [0] পরীক্ষা করুন)।

  5. যাচাই করুন : ইনস্ট্যান্স ভিউতে, অ্যালোকেশন সাইট (Allocation Site) বিভাগটি দেখুন। এটি MainActivity.allocateJava পর্যন্ত সম্পূর্ণ স্ট্যাক ট্রেসটি দেখাবে।

AHAT বরাদ্দ সাইট

জাভা মেমরি ডায়নামিক্স বিশ্লেষণ (সম্মিলিত প্রোফাইল)

কোনো অ্যাপ্লিকেশনের মেমরি আচরণের একটি পূর্ণাঙ্গ চিত্র পেতে, আপনি মেমরি কাউন্টার, থ্রেড অ্যাক্টিভিটি এবং কলস্ট্যাক-ভিত্তিক অ্যালোকেশন প্রোফাইলিংকে একটিমাত্র পারফেটটো ট্রেসে একত্রিত করতে পারেন। এর মাধ্যমে আপনি সিস্টেম-ব্যাপী মেমরি মেট্রিকগুলোকে (যেমন RSS এবং হিপ সাইজ) নির্দিষ্ট কোড এক্সিকিউশন এবং অ্যালোকেশন সাইটগুলোর সাথে সম্পর্কযুক্ত করতে পারেন।

আমরা একটি সমন্বিত কনফিগারেশন ব্যবহার করব যা নিম্নলিখিত বিষয়গুলো সক্ষম করে:

  • মেমরি কাউন্টার ( linux.process_stats ): RSS এবং অন্যান্য মেমরি মেট্রিক্স পোল করে।
  • ATrace ( dalvik , memory , sched ক্যাটাগরি): থ্রেডের অবস্থা এবং GC ইভেন্টগুলো ধারণ করে।
  • Heapprofd ( android.heapprofd ): এটি com.android.art (জাভা) এবং libc.malloc (নেটিভ) উভয় হিপকেই লক্ষ্য করে এবং প্রতি ৫ সেকেন্ডে ক্রমাগত ডাম্প প্রদান করে।

অনুশীলন: সম্মিলিত স্মৃতি বিশ্লেষণ

এই অনুশীলনীতে, আমরা মেমোরিল্যাব অ্যাপটি চালাব এবং ট্রেসে বিভিন্ন প্যাটার্ন পর্যবেক্ষণ করার জন্য ধারাবাহিকভাবে কয়েকটি মেমোরি অপারেশন সম্পাদন করব:

  1. বেসলাইন : নিষ্ক্রিয় অবস্থা।
  2. জাভা চার্ন (Java Churn) : অস্থায়ী মেমোরি বরাদ্দ যা তাৎক্ষণিকভাবে গার্বেজ কালেকশনের মাধ্যমে সংগ্রহ করা হয়।
  3. স্থায়ী জাভা অ্যালোকেশন : এমন জাভা অবজেক্ট বরাদ্দ করা যা মেমরিতে থেকে যায়।
  4. বিটম্যাপ অ্যালোকেশন : বৃহৎ গ্রাফিক্স অ্যাসেট বরাদ্দ করা (যা নেটিভ হিপ/গ্রাফিক্স মেমরিতে থাকে)।
  5. পুনরুদ্ধার : বরাদ্দকৃত সকল সম্পদ মুক্ত করা।

১. চালু করুন এবং প্রস্তুত করুন

  1. একটি পরিষ্কার অবস্থা নিশ্চিত করতে অ্যাপটি ফোর্স-স্টপ এবং রিস্টার্ট করুন:

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

২. ট্রেসিং শুরু করুন এবং সিকোয়েন্স ট্রিগার করুন

আমরা একটি ৪০ সেকেন্ডের ট্রেস শুরু করব এবং am broadcast কমান্ড ব্যবহার করে মেমরি ইভেন্টগুলো ট্রিগার করব।

  1. অনুসন্ধান শুরু করুন :

    adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/java_memory.perfetto-trace <<EOF
    buffers: {
        size_kb: 65536
        fill_policy: RING_BUFFER
    }
    data_sources: {
        config {
            name: "android.java_hprof"
            java_hprof_config {
                process_cmdline: "com.example.myapp"
            }
        }
    }
    duration_ms: 10000
    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. বিকল্প (CLI টুল) : আপনি সরাসরি heap_profile স্ক্রিপ্ট ব্যবহার করেও প্রোফাইলটি শুরু করতে পারেন, যা অবিচ্ছিন্ন ডাম্প সহ জাভা এবং নেটিভ হিপ উভয়কেই টার্গেট করে:

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

৩. সম্মিলিত ট্রেস বিশ্লেষণ করা

Perfetto UI- তে সংগৃহীত java_memory.perfetto-trace ফাইলটি খুলুন।

পারফেত্তোর মূল ট্র্যাকগুলি

টাইমলাইন বিশ্লেষণ করার আগে, com.android.memorylab প্রসেসটির জন্য এই অপরিহার্য ট্র্যাকগুলি সনাক্ত করুন:

  1. mem.rss.anon (অ্যানোনিমাস আরএসএস) : প্রসেসের মেমরি সেকশনের অধীনে পাওয়া যায়। এই ট্র্যাকটি অপারেটিং সিস্টেম দ্বারা প্রসেসটিকে বরাদ্দ করা ফিজিক্যাল মেমরি (র‍্যাম) পরিমাপ করে। এটি প্রকৃত মেমরি ফুটপ্রিন্টকে উপস্থাপন করে।
  2. Heap size (KB) : এটিও মেমরি সেকশনের অধীনে রয়েছে। এটি একটি ডালভিক/এআরটি-নির্দিষ্ট কাউন্টার যা জাভা হিপের জন্য সংরক্ষিত ভার্চুয়াল অ্যাড্রেস স্পেসকে নির্দেশ করে। এটি ভিএম-এর অভ্যন্তরীণ হিপ লিমিটকে প্রতিফলিত করে, যা অবজেক্ট অ্যালোকেট করা এবং জিসি চলার সাথে সাথে ওঠানামা করে।
  3. HeapTaskDaemon : প্রসেসের অধীনে থাকা থ্রেডের তালিকায় পাওয়া যায়। এটি হলো ব্যাকগ্রাউন্ড থ্রেড যেখানে ART গার্বেজ কালেক্টর তার বেশিরভাগ কাজ সম্পাদন করে। এখানকার কার্যকলাপ সক্রিয় GC পাস নির্দেশ করে।
  4. ক্রমাগত অ্যালোকেশন ডাম্প (heapprofd) : উপরের টাইমলাইন বরাবর রঙিন স্লাইস হিসাবে দেখানো হয়। প্রতিটি স্লাইস একটি নির্দিষ্ট সময়কালকে প্রতিনিধিত্ব করে। একটি একক স্লাইসে ক্লিক করে বা একটি সময়সীমা নির্বাচন করে আপনি com.android.art (জাভা অ্যালোকেশন) অথবা libc.malloc (নেটিভ অ্যালোকেশন)-এর জন্য ফ্লেমগ্রাফ (নীচের প্যানে) পরীক্ষা করতে পারেন, যা থেকে দেখা যায় সেই সময়কালে কী বরাদ্দ করা হয়েছিল।

কালানুক্রমিক পর্যায় বিশ্লেষণ

অনুশীলনের প্রতিটি পর্যায়ে এই ট্র্যাকগুলি কীভাবে একে অপরের সাথে মিথস্ক্রিয়া করে তা দেখতে, আসুন গতিপথটি কালানুক্রমিকভাবে পর্যালোচনা করি।

পর্যায় ১: ভিত্তিস্তর (০ সেকেন্ড - ৫ সেকেন্ড)
  • যা ঘটছে : অ্যাপটি নিষ্ক্রিয় অবস্থায় কমান্ডের জন্য অপেক্ষা করছে।
  • অবস্থা ট্র্যাক করুন :
    • mem.rss.anon : বেসলাইনে অপরিবর্তিত থাকে (সাধারণত ডিভাইসভেদে ৬০-৮০ মেগাবাইটের কাছাকাছি)।
    • Heap size (KB) : ফ্ল্যাট লাইন, যা প্রাথমিক জাভা হিপ অ্যালোকেশনের সাথে মিলে যায়।
    • HeapTaskDaemon : নিষ্ক্রিয় (কার্য সম্পাদনের কোনো স্লাইস দেখা যাচ্ছে না)।
    • অ্যালোকেশন ডাম্পস : ন্যূনতম বেসলাইন অ্যালোকেশনগুলো দেখায়।

পারফেটটো ইউআই ফেজ ১ বেসলাইন দেখাচ্ছে

পর্যায় ২: জাভা অ্যালোকেশনের দ্রুত পরিবর্তন (৫ সেকেন্ড - ১৫ সেকেন্ড)
  • যা ঘটছে : AllocationChurnThread চালু হয়ে বারবার ১ মেগাবাইটের অ্যারে বরাদ্দ করছে এবং সেগুলো বাতিল করে দিচ্ছে।
  • অবস্থা ট্র্যাক করুন :
    • Heap size (KB) : একটি দ্রুত করাতের দাঁতের মতো প্যাটার্ন দেখায়। অ্যালোকেশন জমা হওয়ার সাথে সাথে হিপ সাইজ বাড়ে এবং জিসি (গার্বেজ কালেকশন) চলার সময় তা দ্রুত কমে যায়।
    • HeapTaskDaemon : প্রায়-স্থির কার্যকলাপ দেখায়, এবং এর এক্সিকিউশন স্লাইসগুলো Heap size করাতের দাঁতের মতো আকৃতির হ্রাসের সাথে নিখুঁতভাবে মিলে যায়।
    • mem.rss.anon : জাভা হিপের কার্যকলাপ ট্র্যাক করে।
    • জাভা হিপ অ্যালোকেশন ডাম্পস : এই ট্র্যাকের স্লাইসগুলো নির্বাচন করলে com.android.art-এর হিপ অ্যালোকেশনগুলো দেখা যায়।

পারফেটটো UI দ্বিতীয় পর্যায়ের গ্রাহক হ্রাস দেখাচ্ছে অ্যালোকেশন স্যাম্পলগুলো থেকে দেখা যায় যে, AllocationChurnThread হলো প্রধান অ্যালোকেটর এবং সমস্ত অ্যালোকেশন একই কলস্ট্যাক ব্যবহার করে, যা MainActivity.java ভেতরের ল্যাম্বডাটিকে নির্দেশ করে।

পারফেটটো UI দ্বিতীয় পর্যায়ের জাভা অ্যালোকেশন দেখাচ্ছে

পর্যায় ৩: স্থায়ী জাভা অ্যালোকেশন (১৫-২০ সেকেন্ড)
  • যা ঘটছে তা হলো : আমরা ১০ মেগাবাইট জাভা অবজেক্ট বরাদ্দ করি এবং mJavaAllocations এ সেগুলোর একটি রেফারেন্স রাখি।
  • অবস্থা ট্র্যাক করুন :
    • Heap size (KB) : স-টুথের বেসলাইন প্রায় ১০ মেগাবাইট বৃদ্ধি পায়।
    • mem.rss.anon : এর আকার প্রায় ১০ মেগাবাইট বৃদ্ধি পায়, কারণ অপারেটিং সিস্টেমকে অবশ্যই নতুন ফিজিক্যাল পেজ দিয়ে এই স্থায়ী বরাদ্দকে সমর্থন করতে হয়।
    • জাভা হিপ অ্যালোকেশন ডাম্পস : এই ট্র্যাকের স্লাইসগুলো নির্বাচন করলে com.android.art-এর হিপ অ্যালোকেশনগুলো দেখা যায়।
    • অ্যালোকেশন ডাম্প (ফ্লেমগ্রাফ) : এই উইন্ডোতে নেওয়া ডাম্পের জন্য com.android.art হিপ পরীক্ষা করে দেখা যায় যে MainActivity.allocateJava থেকে একটি নতুন অ্যালোকেশন পাথ রিটেইনড সাইজে অবদান রাখছে।

পারফেটটো UI ফেজ ৩ স্থায়ী জাভা বরাদ্দ দেখাচ্ছে এমন একটি অ্যালোকেশন স্যাম্পল নির্বাচন করুন যার সময়কাল পারসিস্টেন্ট অ্যালোকেশনের জন্য করা ১০ মেগাবাইট বৃদ্ধির সাথে মিলে যায়। আপনি দেখবেন যে অ্যালোকেশন কলস্ট্যাকগুলো দুটি ভিন্ন সাইটে বিভক্ত হয়ে যাচ্ছে; একটি সাইট পূর্বে দেখা স্বল্পস্থায়ী অ্যালোকেশন পরিবর্তনের জন্য দায়ী, এবং অন্যটি নতুন দীর্ঘস্থায়ী অ্যালোকেশনের জন্য দায়ী।

পারফেটটো UI ফেজ ৩ জাভা অ্যালোকেশন দেখাচ্ছে

পর্যায় ৪: বিটম্যাপ বরাদ্দ (২০-৩০ সেকেন্ড)
  • যা ঘটছে : আমরা ২০ মেগাবাইট বিটম্যাপও বরাদ্দ করি।
  • অবস্থা ট্র্যাক করুন :
    • Heap size (KB) : আগের মতোই।
    • mem.rss.anon : প্রায় ২০ মেগাবাইটের একটি উল্লেখযোগ্য বৃদ্ধি নির্দেশ করে, যা বিটম্যাপ পিক্সেল ডেটার জন্য নির্ধারিত নেটিভ অ্যালোকেশনের সাথে সঙ্গতিপূর্ণ।
    • অ্যালোকেশন ডাম্পস (ফ্লেমগ্রাফ) : এবার libc.malloc (নেটিভ) হিপের স্লাইসগুলোর উপর মনোযোগ দেওয়া যাক।

পারফেট্টো UI চতুর্থ পর্যায়ের বিটম্যাপ দেখাচ্ছে বরাদ্দ নেটিভ অ্যালোকেশন কলস্ট্যাকগুলো নেটিভ গ্রাফিক্স লাইব্রেরি থেকে উদ্ভূত বিটম্যাপ অ্যালোকেশন প্রকাশ করে। এটি নেটিভ অ্যালোকেশন ট্র্যাকিং-এর একটি ভালো ব্যবহার, কারণ আপনি এই বিটম্যাপ অ্যালোকেশনগুলো জাভা হিপে দেখতে পাবেন না।

পারফেটটো UI ফেজ ৪ নেটিভ অ্যালোকেশন দেখাচ্ছে

পর্যায় ৫: পুনরুদ্ধার (৩০-৪০ সেকেন্ড)
  • যা ঘটছে : আমরা FREE_ALL ট্রিগার করি, যা সমস্ত স্থায়ী জাভা অ্যালোকেশন এবং বিটম্যাপের রেফারেন্স মুছে ফেলে, এবং এর পরে একটি সুস্পষ্ট System.gc() করা হয়।
  • অবস্থা ট্র্যাক করুন :
    • Heap size (KB) : বেসলাইন স্তরে ফিরে আসে।
    • mem.rss.anon : এটি আবার নিচে নেমে আসে, যা দেখায় যে অপারেটিং সিস্টেম ফিজিক্যাল পেজগুলো পুনরুদ্ধার করছে।
    • HeapTaskDaemon : গার্বেজ কালেকশন প্রক্রিয়া চলাকালীন এটি শেষবারের মতো দ্রুত সক্রিয় হয়।

পারফেটটো ইউআই ফেজ ৫ পুনরুদ্ধার দেখাচ্ছে

ঐতিহাসিক OOM নিরীক্ষণ (ApplicationExitInfo)

সক্রিয় ডিবাগিংয়ের জন্য LMK ঘটার সাথে সাথে তা শনাক্ত করা খুবই ভালো, কিন্তু ফিল্ড টেলিমেট্রির জন্য আপনি ApplicationExitInfo API ব্যবহার করতে পারেন। এর মাধ্যমে আপনার অ্যাপ জানতে পারে যে পূর্ববর্তী সেশনে কেন এটি বন্ধ হয়ে গিয়েছিল।

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. GC রুটের পাথ পরীক্ষা করুন : যেকোনো লিক হওয়া অবজেক্টের ক্ষেত্রে, ঠিক কোন রেফারেন্সটি সেটিকে সচল রাখছে (যেমন, একটি স্ট্যাটিক ফিল্ড, একটি দীর্ঘ-চলমান থ্রেড, বা একটি রেজিস্টার্ড লিসেনার) তা বোঝার জন্য AHAT-এর ' Path from Root' ভিউ ব্যবহার করুন।

← টুলস | ↑ উপরে | বিটম্যাপস →