Java মেমরি লিক: সাধারণ কারণ ও সনাক্তকরণ

Backend2026-09-27TryQuickToolBox

আপনার Java অ্যাপ্লিকেশন দ্রুত শুরু হয় কিন্তু কয়েক ঘন্টা বা দিন পরে এটি অত্যন্ত ধীর হয়ে যায়, OutOfMemoryError ছুঁড়ে দেয়, বা কন্টেইনার দ্বারা নিহত হয়। আপনি এটি পুনরায় চালু করেন, এবং চক্রটি পুনরাবৃত্তি হয়। এটি একটি Java মেমরি লিকের ক্লাসিক বৈশিষ্ট্য: যে অবজেক্টগুলি আর প্রয়োজন নেই কিন্তু অ্যাক্সেসযোগ্য থাকে, তাই গার্বেজ কালেক্টর সেগুলি পুনরুদ্ধার করতে পারে না। C/C++ এর বিপরীতে, Java এর GC বেশিরভাগ মেমরি স্বয়ংক্রিয়ভাবে পরিচালনা করে, তবে রেফারেন্সগুলি অনিচ্ছাকৃতভাবে ধরে রাখলে লিক এখনও ঘটে। এই নিবন্ধটি সবচেয়ে সাধারণ কারণগুলি ব্যাখ্যা করে এবং সেগুলি সনাক্ত ও সমাধান করার জন্য একটি ব্যবহারিক কর্মপ্রবাহ দেয়।

Java মেমরি লিক কী?

একটি মেমরি লিক তখন ঘটে যখন অবজেক্টগুলি অ্যাপ্লিকেশন দ্বারা আর ব্যবহৃত হয় না কিন্তু এখনও রেফারেন্স করা হয়, যা গার্বেজ কালেকশন প্রতিরোধ করে। সময়ের সাথে সাথে, হিপ ব্যবহার বাড়ে, GC আরও ঘন ঘন চলে, এবং শেষ পর্যন্ত JVM OutOfMemoryError: Java heap space ছুঁড়ে দেয়। লিকগুলি ছোট এবং ধীর (প্রতি অনুরোধে কয়েক KB) বা বড় এবং দ্রুত (সম্পূর্ণ ফলাফল সেট ক্যাশ করা) হতে পারে।

Java মেমরি লিকের সাধারণ কারণ

1. স্ট্যাটিক কালেকশন যা চিরকাল বাড়ে

স্ট্যাটিক ফিল্ড JVM এর জীবনকাল ধরে বেঁচে থাকে। একটি স্ট্যাটিক Map যা ইভিকশন ছাড়া ক্যাশ হিসাবে ব্যবহৃত হয় একটি ক্লাসিক লিক। উদাহরণস্বরূপ:

public class UserCache {
    private static final Map<Long, User> CACHE = new HashMap<>();

    public static void put(Long id, User user) {
        CACHE.put(id, user); // never removed
    }
}

প্রতিটি যোগ করা ব্যবহারকারী চিরকাল থাকে। সমাধান: আকার সীমা এবং মেয়াদ শেষ হওয়ার সাথে Caffeine বা Guava এর মতো একটি সীমাবদ্ধ ক্যাশ ব্যবহার করুন।

2. বন্ধ না করা রিসোর্স

স্ট্রিম, কানেকশন এবং রিডার বন্ধ করতে হবে। যদি আপনি ভুলে যান, নেটিভ রিসোর্স এবং তাদের Java র্যাপার লিক হয়। সর্বদা try-with-resources ব্যবহার করুন:

try (InputStream in = new FileInputStream(file)) {
    // use in
} // automatically closed

এটি ডেটাবেস কানেকশন, HTTP ক্লায়েন্ট এবং থ্রেড পুলের ক্ষেত্রেও প্রযোজ্য।

3. লিসেনার এবং কলব্যাক সরানো হয় না

যখন আপনি একটি লিসেনার নিবন্ধন করেন (যেমন addListener) কিন্তু কখনও সরান না, প্রকাশক লিসেনারের একটি রেফারেন্স ধরে রাখে, যা আপনার সম্পূর্ণ অবজেক্ট গ্রাফের একটি রেফারেন্স ধরে রাখতে পারে। এটি GUI, ইভেন্ট বাস এবং দীর্ঘজীবী কনটেক্সটে সাধারণ।

4. ThreadLocal ভেরিয়েবল

ThreadLocal মানগুলি থ্রেডের ThreadLocalMap এ সংরক্ষিত থাকে। থ্রেড পুলে, থ্রেডগুলি পুনরায় ব্যবহার করা হয়, তাই যদি আপনি remove() কল না করেন, মানটি থ্রেডের সাথে সংযুক্ত থাকে এবং লিক হয়। সর্বদা পরিষ্কার করুন:

try {
    threadLocal.set(context);
    // work
} finally {
    threadLocal.remove();
}

5. ইনার ক্লাস যা আউটার রেফারেন্স ধরে রাখে

নন-স্ট্যাটিক ইনার ক্লাস (বেনামী ক্লাস সহ) আউটার ইনস্ট্যান্সের একটি অন্তর্নিহিত রেফারেন্স ধরে রাখে। যদি একটি ইনার ক্লাস ইনস্ট্যান্স আউটার অবজেক্টের চেয়ে বেশি বাঁচে (যেমন একটি স্ট্যাটিক তালিকায় সংরক্ষিত), আউটার অবজেক্ট সংগ্রহ করা যায় না। ইনার ক্লাসগুলিকে স্ট্যাটিক করুন যখন তাদের আউটার ইনস্ট্যান্সের প্রয়োজন হয় না।

6. অনুপযুক্ত equals() এবং hashCode()

যদি আপনি একটি HashMap এ কী হিসাবে অবজেক্ট ব্যবহার করেন কিন্তু hashCode() ছাড়া equals() ওভাররাইড করেন (বা বিপরীত), লুকআপ ব্যর্থ হয় এবং এন্ট্রিগুলি জমা হয়। সর্বদা উভয়ই সামঞ্জস্যপূর্ণভাবে ওভাররাইড করুন।

7. স্ট্রিং ইন্টার্নিং এবং সাবস্ট্রিং

Java 7 এর আগে, String.substring() মূল char অ্যারে শেয়ার করত, তাই একটি ছোট সাবস্ট্রিং একটি বড় অ্যারে ধরে রাখতে পারত। আধুনিক Java কপি করে, কিন্তু অনেক অনন্য স্ট্রিং ইন্টার্ন করা (যেমন String.intern()) এখনও স্ট্রিং পুল পূরণ করতে পারে।

Java মেমরি লিক কীভাবে সনাক্ত করবেন

সনাক্তকরণ একটি পদ্ধতিগত প্রক্রিয়া অনুসরণ করে। এখানে একটি ধাপে ধাপে পদ্ধতি:

  1. সময়ের সাথে হিপ ব্যবহার নিরীক্ষণ করুন। JVisualVM, JConsole, বা আপনার APM ব্যবহার করুন। একটি সুস্থ অ্যাপ একটি করাতের দাঁতের প্যাটার্ন দেখায়: হিপ বাড়ে, GC পুনরুদ্ধার করে, হিপ কমে। একটি লিক প্রতিটি GC এর পরে একটি ক্রমবর্ধমান বেসলাইন দেখায়।
  2. GC লগিং সক্ষম করুন। -Xlog:gc*:file=gc.log:time,uptime,level,tags (Java 9+) বা -XX:+PrintGCDetails (Java 8) যোগ করুন। GCeasy এর মতো সরঞ্জাম দিয়ে লগ বিশ্লেষণ করুন যাতে দেখা যায় ওল্ড জেন বাড়ছে কিনা।
  3. একটি হিপ ডাম্প ক্যাপচার করুন। হিপ উচ্চ হলে, jmap -dump:live,format=b,file=heap.hprof <pid> চালান বা jcmd <pid> GC.heap_dump heap.hprof ব্যবহার করুন। আপনি -XX:+HeapDumpOnOutOfMemoryError দিয়ে OutOfMemoryError এ একটি ডাম্প ট্রিগার করতে পারেন।
  4. হিপ ডাম্প বিশ্লেষণ করুন। Eclipse MAT বা VisualVM এ এটি খুলুন। সর্বাধিক মেমরি ধরে রাখা অবজেক্টগুলি খুঁজতে ডমিনেটর ট্রি দেখুন। বড় রিটেইনড সাইজ সহ সন্দেহজনক কালেকশন পরীক্ষা করুন।
  5. একটি দ্বিতীয় ডাম্প নিন এবং তুলনা করুন। যদি ডাম্পের মধ্যে একই অবজেক্টগুলি বৃদ্ধি পায়, আপনি লিক খুঁজে পেয়েছেন।
  6. লাইভ বিশ্লেষণের জন্য একটি প্রফাইলার ব্যবহার করুন। YourKit, JProfiler, বা async-profiler এর মতো সরঞ্জামগুলি অ্যাপ বন্ধ না করে অ্যালোকেশন সাইট এবং রেফারেন্স চেইন ট্র্যাক করতে পারে।

এখানে সাধারণ সরঞ্জামগুলির একটি দ্রুত তুলনা:

সরঞ্জামধরনসেরা জন্য
Eclipse MATহিপ ডাম্প বিশ্লেষকডমিনেটর এবং লিক সন্দেহভাজন খুঁজে পাওয়া
VisualVMমনিটরিং + হিপ ডাম্পদ্রুত লাইভ চেক এবং মৌলিক বিশ্লেষণ
JProfiler / YourKitপ্রফাইলারঅ্যালোকেশন ট্র্যাকিং এবং রেফারেন্স চেইন
async-profilerকম-ওভারহেড প্রফাইলারফ্লেম গ্রাফ সহ প্রোডাকশন প্রফাইলিং

লিক সমাধান এবং প্রতিরোধ

একবার আপনি লিক সনাক্ত করলে, অনিচ্ছাকৃত রেফারেন্স সরিয়ে এটি সমাধান করুন। সাধারণ সমাধান:

প্রতিরোধ চিকিৎসার চেয়ে ভাল। আপনার CI/CD পাইপলাইনে মেমরি মনিটরিং যোগ করুন, হিপ চেক সহ লোড টেস্ট চালান, এবং -Xmx যথাযথভাবে সেট করুন। সাধারণ প্যাটার্ন ধরতে SpotBugs এর মতো স্ট্যাটিক বিশ্লেষণ সরঞ্জাম ব্যবহার করুন।

FAQ

মেমরি লিক এবং মেমরি স্পাইকের মধ্যে পার্থক্য কী?

একটি মেমরি স্পাইক হল হিপ ব্যবহারের একটি অস্থায়ী বৃদ্ধি যা GC পুনরুদ্ধার করে। একটি লিক হল সময়ের সাথে একটি স্থির বৃদ্ধি কারণ অবজেক্টগুলি অ্যাক্সেসযোগ্য থাকে এবং সংগ্রহ করা যায় না।

একটি Java মেমরি লিক কি হিপ বড় হলেও OutOfMemoryError সৃষ্টি করতে পারে?

হ্যাঁ। যদি লিকের হার GC এর পুনরুদ্ধার করার ক্ষমতার চেয়ে বেশি হয়, হিপ আকার নির্বিশেষে শেষ পর্যন্ত পূর্ণ হবে। হিপ বাড়ানো কেবল ব্যর্থতা বিলম্বিত করে।

ডাউনটাইম ছাড়া প্রোডাকশনে মেমরি লিক কীভাবে খুঁজে পাব?

async-profiler বা JFR (Java Flight Recorder) এর মতো কম-ওভারহেড সরঞ্জাম ব্যবহার করে অ্যালোকেশন এবং হিপ পরিসংখ্যান রেকর্ড করুন। আপনি অ্যাপ চলাকালীন jcmd দিয়ে একটি হিপ ডাম্প ট্রিগার করতে পারেন, যদিও এটি সংক্ষিপ্তভাবে বিরতি দিতে পারে।

GC লগ বা অন্যান্য টেক্সট-ভিত্তিক লগ বিশ্লেষণ করতে হবে? লগ প্যাটার্নগুলি দ্রুত পার্স এবং ভিজ্যুয়ালাইজ করতে আমাদের Nginx Log Analyzer ব্যবহার করে দেখুন।