জাভা ৮-এর আগে জেভিএম ক্লাস মেটাডেটা সংরক্ষণের জন্য PermGen নামের একটি নির্দিষ্ট মেমোরি অঞ্চল ব্যবহার করত। কিন্তু PermGen-এ ক্লাস লোডিং সংক্রান্ত জটিলতা ও মেমোরি লিকের কারণে জাভা ৮ থেকে এটিকে সরিয়ে Metaspace চালু করা হয়। Metaspace হল নেটিভ মেমোরির উপর ভিত্তি করে তৈরি একটি স্বয়ংক্রিয় সম্প্রসারণশীল অঞ্চল, যা ডিফল্টভাবে সীমাহীন আকারের। এই টিউটোরিয়ালে আমরা Metaspace-এর ধারণা, কনফিগারেশন, মনিটরিং এবং সাধারণ সমস্যা সমাধানের পদ্ধতি শিখব।

ভূমিকা ও পটভূমি

জাভার পুরনো সংস্করণগুলোতে প্রতিটি লোড হওয়া ক্লাসের মেটাডেটা PermGen নামক একটি নির্দিষ্ট আকারের মেমোরি অঞ্চলে জমা হতো। PermGen-এর প্রধান অসুবিধা ছিল এর সীমাবদ্ধ আকার—একবার -XX:MaxPermSize দিয়ে সর্বোচ্চ সীমা বেঁধে দিলে অ্যাপ্লিকেশন চলাকালীন আর বাড়ানো যেত না। বড় অ্যাপ্লিকেশন, বিশেষত অনেক ক্লাস লোড করা ফ্রেমওয়ার্ক (যেমন স্প্রিং, হাইবারনেট) বা ডায়নামিক প্রক্সি তৈরির ক্ষেত্রে বারবার OutOfMemoryError: PermGen space ত্রুটি দেখা দিত। এই সমস্যা সমাধানে জাভা ৮ থেকে PermGen সরিয়ে সম্পূর্ণ নতুন মেমোরি মডেল Metaspace প্রবর্তন করা হয়।

Metaspace নেটিভ মেমোরি থেকে সরাসরি বরাদ্দ নেয়, তাই এটি ডিফল্টভাবে প্রয়োজন অনুযায়ী বাড়তে পারে (তবে অপারেটিং সিস্টেমের মেমোরির মধ্যে সীমাবদ্ধ)। এতে PermGen-এর মতো নির্দিষ্ট সীমা সংক্রান্ত ত্রুটির পরিমাণ কমেছে, এবং ক্লাস মেটাডেটা এখন গার্বেজ কালেকশনের আওতায় আসা সহজ হয়েছে। তবুও অসতর্ক ক্লাস লোডিং বা ভুল কনফিগারেশনের কারণে OutOfMemoryError: Metaspace ত্রুটি এখনও ঘটতে পারে, যা আমাদের নিয়ন্ত্রণ করতে হবে।

পূর্বশর্ত

এই টিউটোরিয়ালটি অনুসরণ করতে আপনার যে বিষয়গুলোর সাথে পরিচিতি থাকা দরকার:

  • জাভা ডেভেলপমেন্ট কিট (JDK) ৮ বা তার পরবর্তী সংস্করণ ইনস্টল করা
  • জেভিএম মেমোরি আর্কিটেকচার সম্পর্কে প্রাথমিক ধারণা
  • কমান্ড-লাইন থেকে জাভা প্রোগ্রাম চালানোর অভিজ্ঞতা
  • কোনো জাভা আইডিই (Eclipse, IntelliJ IDEA) অথবা একটি সাধারণ টেক্সট এডিটর
  • OutOfMemoryError এবং গার্বেজ কালেকশন (GC) কীভাবে কাজ করে সে বিষয়ে মৌলিক জ্ঞান

উদাহরণ কোডগুলো পরীক্ষার জন্য আপনি ম্যাভেন বা গ্রেডল ব্যবহার করতে পারেন, তবে শুধুমাত্র জাভা স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করেও Metaspace-এর ধারণা বোঝা সম্ভব। আমরা পরবর্তী অংশে তৃতীয়-পক্ষের লাইব্রেরি ব্যবহারের প্রয়োজনীয়তাও আলোচনা করব।

ধাপে ধাপে কনফিগারেশন

Metaspace নিয়ন্ত্রণের জন্য জেভিএম আমাদের কিছু গুরুত্বপূর্ণ ফ্ল্যাগ দিয়েছে। এই ফ্ল্যাগগুলো ঠিকমতো ব্যবহার করলে আপনি মেমোরি ব্যবহার নিখুঁতভাবে পরিচালনা করতে পারবেন।

  • -XX:MetaspaceSize: প্রাথমিক Metaspace সাইজ নির্ধারণ করে। এটি একটি সূচনা বিন্দু; GC প্রয়োজনে এটিকে বাড়াতে বা কমাতে পারে।
  • -XX:MaxMetaspaceSize: Metaspace-এর সর্বোচ্চ সীমা। এটি নির্ধারিত না করলে ডিফল্টে কোনো সীমা থাকে না (অর্থাৎ নেটিভ মেমোরি শেষ না হওয়া পর্যন্ত বাড়তে থাকে)।
  • -XX:MinMetaspaceFreeRatio: একটি GC-পরবর্তী Metaspace ন্যূনতম কত শতাংশ খালি রাখার চেষ্টা করবে, তা নির্ধারণ করে।
  • -XX:MaxMetaspaceFreeRatio: একটি GC-পরবর্তী Metaspace সর্বোচ্চ কত শতাংশ খালি রাখার চেষ্টা করবে, তা নির্ধারণ করে।

উদাহরণস্বরূপ, যদি আপনি চান আপনার অ্যাপ্লিকেশন সর্বোচ্চ ১২৮ মেগাবাইট Metaspace ব্যবহার করতে পারবে, এবং প্রাথমিক আকার ৬৪ মেগাবাইট হোক, তাহলে নিচের মতো করে জেভিএম চালু করতে হবে:

java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m -jar myapp.jar

অনেক সময় MaxMetaspaceSize নির্ধারণ করা ভালো অভ্যাস, কারণ এতে কোনো ক্লাসলোডার লিক অসীম পরিমাণ মেমোরি খেয়ে ফেলতে পারে না। ফ্ল্যাগগুলো নির্ধারণের সময় একক হিসেবে k (কিলোবাইট), m (মেগাবাইট) বা g (গিগাবাইট) ব্যবহার করুন।

একটি সাধারণ স্প্রিং বুট অ্যাপ্লিকেশনে application.properties বা application.yml-এ তেমন সরাসরি Metaspace সংক্রান্ত কনফিগারেশন দেওয়া না গেলেও, আপনি রান কনফিগারেশনে বা ডকার ইমেজে উপরোক্ত জেভিএম অপশনগুলো যোগ করতে পারেন।

সম্পূর্ণ উদাহরণ: Metaspace ওভারফ্লো তৈরি ও প্রতিকার

এবার একটি পূর্ণাঙ্গ জাভা প্রোগ্রাম দেখি যা ইচ্ছাকৃতভাবে Metaspace পূর্ণ করে এবং OutOfMemoryError তৈরি করে। এর মাধ্যমে আমরা বুঝতে পারব সমস্যাটি কীভাবে চিহ্নিত করতে হয় এবং কনফিগারেশন দিয়ে কীভাবে প্রতিকার করা যায়। উদাহরণটিতে আমরা CGLIB লাইব্রেরি ব্যবহার করব, কারণ এটি রানটাইমে অসংখ্য ডায়নামিক সাবক্লাস তৈরি করতে পারে।

প্রথমে ম্যাভেন pom.xml-এ CGLIB নির্ভরতা যোগ করুন:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

এখন মূল MetaspaceOOMDemo.java ক্লাস তৈরি করি:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MetaspaceOOMDemo {

    public static void main(String[] args) {
        System.out.println("Metaspace ওভারফ্লো টেস্ট শুরু হচ্ছে...");
        try {
            for (int i = 0; ; i++) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Object.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args,
                                            MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                // প্রতিবার একটি অনন্য ডায়নামিক ক্লাস তৈরি করছে
                enhancer.create();
                if (i % 1000 == 0) {
                    System.out.println("তৈরি হয়েছে " + i + "টি ডায়নামিক ক্লাস");
                }
            }
        } catch (OutOfMemoryError e) {
            System.out.println("Metaspace পূর্ণ! OutOfMemoryError ধরা পড়েছে:");
            e.printStackTrace(System.out);
        }
    }
}

উপরের কোডে প্রথম ব্যাখ্যা: আমরা CGLIB-এর Enhancer ব্যবহার করে প্রতিটি ইটারেশনে একটি নতুন ডায়নামিক সাবক্লাস তৈরি করছি। setUseCache(false) কল করার ফলে CGLIB তার নিজস্ব ক্যাশে ক্লাস জমা করে না, ফলে প্রতিবার সম্পূর্ণ নতুন ক্লাস Metaspace-এ যুক্ত হয়।
দ্বিতীয় ব্যাখ্যা: লুপটি অসীম চলতে থাকে, এবং প্রতি ১০০০টি ক্লাস তৈরির পর কনসোলে একটি লগ প্রিন্ট করে, যাতে আমরা অগ্রগতি দেখতে পারি। যখন Metaspace আর জায়গা পায় না, তখন জেভিএম OutOfMemoryError ছুঁড়ে দেয় এবং ক্যাচ ব্লকে আমরা তা ধরছি।

এখন এই প্রোগ্রামটি চালানোর জন্য নিচের কমান্ডটি ব্যবহার করুন, যেখানে আমরা MaxMetaspaceSize ৩২ মেগাবাইট নির্ধারণ করে দিচ্ছি:

java -XX:MaxMetaspaceSize=32m MetaspaceOOMDemo

প্রোগ্রামটি কিছুক্ষণের মধ্যেই OutOfMemoryError: Metaspace ছুঁড়ে ফেলবে। এর ফলে আপনি বুঝতে পারবেন যে বাস্তবে অনেক বেশি ডায়নামিক ক্লাস তৈরি হলে কিংবা কোনো ক্লাসলোডার লিক থাকলে কীভাবে মেমোরি শেষ হয়।
সমাধান হিসেবে আপনি -XX:MaxMetaspaceSize বাড়িয়ে দিতে পারেন (যদি বাস্তবে এত ক্লাস প্রয়োজন হয়), কিন্তু তার চেয়ে গুরুত্বপূর্ণ হলো কেন এত ক্লাস তৈরি হচ্ছে তা খতিয়ে দেখা। উদাহরণস্বরূপ, অনেক এওপি প্রক্সি অপ্রয়োজনীয়ভাবে জমা হচ্ছে কিনা বা ডায়নামিক ক্লাস লোডিংয়ের পুনরাবৃত্তি হচ্ছে কিনা তা নিরূপণ করা।

সাধারণ সমস্যা ও সমাধান

Metaspace নিয়ে কাজ করার সময় ডেভেলপাররা প্রায়ই যে সমস্যাগুলোর মুখোমুখি হন, সেগুলো ও তাদের সমাধান নিচে দেওয়া হল:

  • OutOfMemoryError: Metaspace ঘন ঘন দেখা দেওয়া
    কারণ: ডিফল্ট MaxMetaspaceSize সেট না থাকায় ক্লাসলোডার লিক বা অতিরিক্ত ডায়নামিক ক্লাস জেনারেশনে সব নেটিভ মেমোরি শেষ হয়ে যাওয়া।
    সমাধান: -XX:MaxMetaspaceSize দিয়ে একটি সীমা নির্ধারণ করুন। মেমোরি লিক চিহ্নিত করতে jstat -gc <pid> বা jcmd <pid> GC.class_stats (আনলক করতে -XX:+UnlockDiagnosticVMOptions প্রয়োজন) ব্যবহার করুন।

  • Metaspace দ্রুত বাড়ছে কিন্তু GC ফ্রি করছে না
    কারণ: কিছু ক্লাস এখনও রেফারেন্সড এবং ক্লাসলোডার নিজে GC-র আওতায় আসছে না।
    সমাধান: -verbose:class বা -XX:+TraceClassLoading এবং -XX:+TraceClassUnloading ফ্ল্যাগ ব্যবহার করে দেখুন কোন ক্লাসগুলো লোড/আনলোড হচ্ছে। প্রয়োজনীয় ক্লাসলোডার রেফারেন্স সরিয়ে দিন।

  • মেটাস্পেস সংক্রান্ত ফ্ল্যাগের ভুল ব্যবহার
    কারণ: যেমন MetaspaceSize খুব ছোট সেট করায় ঘন ঘন GC ট্রিগার হচ্ছ।
    সমাধান: পর্যাপ্ত MetaspaceSize দিন (অ্যাপ্লিকেশনভেদে ৬৪-১২৮ মেগাবাইট প্রাথমিক মান হিসাবে কাজ করতে পারে), এবং MaxMetaspaceSize-কে বাস্তবসম্মত মানে সেট করুন। মনিটরিং টুল (JConsole, Java Mission Control) এর সাহায্যে ব্যবহারের ধারা পর্যবেক্ষণ করুন।

  • ক্লাসলোডার লিক ও ডায়নামিক প্রক্সি
    কারণ: হাইবারনেট, স্প্রিং, বা অন্যান্য ফ্রেমওয়ার্ক রানটাইমে অনেক প্রক্সি ক্লাস তৈরি করে, যা ক্লাসলোডারের সাথে আটকে থেকে যায়।
    সমাধান: ফ্রেমওয়ার্ক কনফিগারেশন বদলান (যেমন স্প্রিং-এ CGLIB প্রক্সির পরিবর্তে JDK ডায়নামিক প্রক্সি ব্যবহার, যেখানে সম্ভব), অথবা লাইব্রেরির সর্বশেষ সংস্করণে আপগ্রেড করুন যেখানে লিক সমাধান করা হয়েছে।

সারসংক্ষেপ

এই টিউটোরিয়ালে আমরা শিখেছি যে জাভা ৮-এর Metaspace ক্লাস মেটাডেটা সংরক্ষণের জন্য একটি যুগান্তকারী পরিবর্তন, যা PermGen-এর সীমাবদ্ধতা দূর করেছে। Metaspace ডিফল্টভাবে সীমাহীন হলেও, সঠিক নিয়ন্ত্রণের জন্য -XX:MetaspaceSize-XX:MaxMetaspaceSize এর মতো জেভিএম ফ্ল্যাগগুলোর ব্যবহার অপরিহার্য। একটি পূর্ণাঙ্গ উদাহরণের মাধ্যমে আমরা দেখিয়েছি কীভাবে অতিরিক্ত ডায়নামিক ক্লাস লোডিং OutOfMemoryError: Metaspace তৈরি করতে পারে এবং কীভাবে তা প্রতিকার করা যায়। সাধারণ সমস্যাগুলো চিহ্নিত করে আপনার অ্যাপ্লিকেশনকে আরও স্থিতিশীল ও মেমোরি-দক্ষ করতে পারবেন। মনিটরিং সরঞ্জাম ও লগিং ফ্ল্যাগ কাজে লাগিয়ে Metaspace-সম্পর্কিত যেকোনো ত্রুটি সহজেই নিরূপণ ও সমাধান করুন।

Share