জাভা প্রোগ্রামিংয়ের জগতে মেমোরি ম্যানেজমেন্ট একটি মৌলিক কিন্তু প্রায়ই ভীতিকর অধ্যায়। আপনার লেখা প্রতিটি ভ্যারিয়েবল, প্রতিটি new কিওয়ার্ড, এবং প্রতিটি মেথড কল গভীরভাবে জড়িত জাভার দুই প্রধান মেমোরি সেগমেন্টের সাথে — হিপ (Heap) ও স্ট্যাক (Stack)। এদের ভুল বোঝাবুঝি থেকে জন্ম নেয় ভয়ংকর OutOfMemoryError অথবা রহস্যজনক StackOverflowError। এই টিউটোরিয়ালে আমরা জাভার হিপ ও স্ট্যাক মেমোরির পার্থক্য, তাদের কাজের ধরন, এবং বাস্তব কোড উদাহরণের মাধ্যমে এই দুই অঞ্চলের আচরণ পরিষ্কারভাবে বুঝব। টিউটোরিয়াল শেষে আপনি স্ট্যাক ও হিপের মধ্যে ডেটা কীভাবে প্রবাহিত হয় তা আত্মবিশ্বাসের সাথে ব্যাখ্যা করতে পারবেন এবং মেমোরি সংশ্লিষ্ট সাধারণ সমস্যাগুলোর সমাধান করতে শিখবেন।
ভূমিকা: জাভার স্মৃতির দুই স্তম্ভ
জাভা ভার্চুয়াল মেশিন (JVM) প্রতিটি অ্যাপ্লিকেশন চালানোর সময় অপারেটিং সিস্টেম থেকে একটি নির্দিষ্ট পরিমাণ মেমোরি বরাদ্দ করে নেয়। এই মেমোরিকে প্রধানত দুটি বিশাল অঞ্চলে ভাগ করা হয়: হিপ (Heap) এবং স্ট্যাক (Stack)। প্রতিটি অঞ্চলের নিজস্ব দায়িত্ব, গঠন ও জীবনচক্র রয়েছে। সহজভাবে বলতে গেলে, হিপ হলো জাভার বিশাল মালগুদাম যেখানে সমস্ত অবজেক্ট ও তাদের ইনস্ট্যান্স ভ্যারিয়েবল সংরক্ষিত থাকে। অন্যদিকে, স্ট্যাক হলো একটি সুশৃঙ্খল ওয়ার্কবেঞ্চ যেখানে প্রতিটি থ্রেড তার মেথড কল, লোকাল ভ্যারিয়েবল ও আংশিক ফলাফল জমা রাখে এবং কল শেষে পরিপাটি করে সরিয়ে ফেলে।
এই ভাগাভাগির পেছনে রয়েছে ডেটার ধরন ও জীবনকালের গুরুত্বপূর্ণ নকশা। হিপে রাখা ডেটা তুলনামূলকভাবে দীর্ঘস্থায়ী এবং একাধিক থ্রেড থেকে ভাগ করা যায়, কিন্তু সেটি অ্যাক্সেস করতে সময় লাগে তুলনামূলক বেশি। স্ট্যাকে রাখা ডেটা অস্থায়ী, দ্রুত প্রবেশযোগ্য, এবং প্রতিটি থ্রেডের নিজস্ব একটি স্ট্যাক থাকে, যা থ্রেড-সেফটি নিশ্চিত করে। জাভা ডেভেলপার হিসেবে new কিওয়ার্ড ব্যবহার করার সাথে সাথে আপনি হিপ সচল করছেন, আর একটি প্রিমিটিভ ভ্যারিয়েবল ডিক্লেয়ার করলেই স্ট্যাকের বাসিন্দা তৈরি করছেন। এই দুই অঞ্চলের সমন্বয় বোঝা আপনাকে দক্ষ ও স্মৃতিমিতব্যয়ী কোড লিখতে সাহায্য করবে।
পূর্বশর্ত
এই টিউটোরিয়াল থেকে সম্পূর্ণ সুবিধা পেতে আপনার কয়েকটি প্রাথমিক জিনিস জানা থাকা দরকার। এগুলো খুবই সোজা এবং একজন জুনিয়র ডেভেলপার হিসাবে আপনার ইতিমধ্যেই এগুলোর সাথে পরিচিত থাকার কথা।
- জাভা প্রোগ্রামিংয়ের বেসিক: প্রিমিটিভ ডেটা টাইপ (
int,double,booleanইত্যাদি), রেফারেন্স টাইপ (ক্লাস ও অবজেক্ট), এবং মেথড কল কীভাবে কাজ করে, সে সম্পর্কে স্পষ্ট ধারণা। - JDK ও একটি IDE: আপনার সিস্টেমে JDK 8 বা তার পরের কোনো সংস্করণ এবং ইন্টেলিজ IDEA, Eclipse বা VS Code-এর মতো একটি উন্নয়ন পরিবেশ ইনস্টল থাকা। আমরা পুরো উদাহরণ সরাসরি চালিয়ে দেখতে পারলে শেখা সবচেয়ে ফলপ্রসূ হয়।
mainমেথড: একটি এক্সিকিউটেবল জাভা ক্লাস কী এবংpublic static void main(String[] args)-এর ভূমিকা কী, তা জানা থাকলে উদাহরণগুলো অনুসরণ করা সহজ হবে।
আপনি যদি এতটুকু জেনে থাকেন, তাহলে আপনি হিপ ও স্ট্যাকের গভীরে ডুব দিতে একদম প্রস্তুত। আমরা কোনো জটিল টুল বা লাইব্রেরি ব্যবহার করব না — খালি জাভা (plain Java) আর পর্যবেক্ষণই যথেষ্ট।
ধাপে ধাপে বাস্তবায়ন: হিপ ও স্ট্যাক কীভাবে কাজ করে
আমরা এবার কোডের মাধ্যমে ধাপে ধাপে হিপ ও স্ট্যাকের আচরণ বিশ্লেষণ করব। প্রতিটি ধাপে আমরা একটি ছোট কোড স্নিপেট দেখব, তার ব্যাখ্যা করব এবং বুঝব সংশ্লিষ্ট মেমোরিতে কী ঘটছে।
১. প্রিমিটিভ লোকাল ভ্যারিয়েবল এবং স্ট্যাক
স্ট্যাক মূলত লোকাল ভ্যারিয়েবল ও মেথড ফ্রেমের জন্য নির্ধারিত। নিচের কোডটি দেখুন:
public class StackDemo {
public static void main(String[] args) {
int x = 10;
int y = 20;
int sum = add(x, y);
System.out.println("Sum: " + sum);
}
public static int add(int a, int b) {
int result = a + b;
return result;
}
}
এই কোডে main মেথড কল হলে main-এর জন্য স্ট্যাকে একটি নতুন ফ্রেম তৈরি হয়। এই ফ্রেমেই x, y, এবং sum ভ্যারিয়েবলগুলোর জন্য জায়গা হয়। যখন add মেথড কল হয়, তখন স্ট্যাকের উপরে আরেকটি ফ্রেম গেঁথে যায়, যেখানে a, b, এবং result রাখা হয়। add মেথড শেষ হওয়ার সাথে সাথেই এর পুরো ফ্রেমটি স্ট্যাক থেকে মুছে ফেলা হয় (পপ করা হয়), এবং প্রোগ্রাম পুনরায় main-এর ফ্রেমে ফিরে আসে। System.out.println চালানোর জন্য সাময়িক আরেকটি ফ্রেম তৈরি ও ধ্বংস হবে।
মূল বিষয় হলো, এই সমস্ত লোকাল প্রিমিটিভ ভ্যারিয়েবলের জীবনকাল তাদের নিজ নিজ মেথডের ব্লকের সাথেই শেষ। মেমোরি বরাদ্দ ও নিষ্পত্তি এখানে অত্যন্ত দ্রুত এবং সম্পূর্ণ স্বয়ংক্রিয়, লাস্ট-ইন, ফার্স্ট-আউট (LIFO) নীতিতে। কোনো গারবেজ কালেকশন এখানে প্রয়োজন হয় না।
২. অবজেক্ট তৈরি এবং হিপ
এবার আমরা একটি Car ক্লাস তৈরি করি এবং দেখি হিপ কীভাবে কাজ করে।
public class Car {
String model;
int year;
public Car(String model, int year) {
this.model = model;
this.year = year;
}
}
এখন একটি main মেথড থেকে এই Car-এর একটি ইন্সট্যান্স তৈরি করা যাক।
public class HeapDemo {
public static void main(String[] args) {
Car myCar = new Car("Toyota", 2023);
System.out.println(myCar.model);
}
}
এখানে কী ঘটছে? main-এর স্ট্যাক ফ্রেমে একটি রেফারেন্স ভ্যারিয়েবল myCar তৈরি হয়, যা প্রকৃত অবজেক্টের অবস্থান (একটি মেমোরি অ্যাড্রেস) ধারণ করে। কিন্তু new Car("Toyota", 2023) অংশটি হিপ-এ গিয়ে Car অবজেক্টটির জন্য মেমোরি বরাদ্দ করে। সেই অবজেক্টের model স্ট্রিং (যা নিজেও আরেকটি হিপ অবজেক্ট) এবং year প্রিমিটিভ ফিল্ডগুলি হিপেই সংরক্ষিত থাকে। রেফারেন্স myCar এখন এই হিপ অবজেক্টটিকে নির্দেশ করছে। পরবর্তীতে যখন myCar.model ব্যবহার করা হয়, JVM প্রথমে স্ট্যাকের myCar থেকে রেফারেন্স নিয়ে হিপের অবজেক্টে গিয়ে model ফিল্ডের মান বের করে আনে।
একটি গুরুত্বপূর্ণ পর্যবেক্ষণ: new কিওয়ার্ড দিয়ে তৈরি সমস্ত অবজেক্ট, তাদের ইনস্ট্যান্স ভ্যারিয়েবল সহ, ডায়নামিকভাবে হিপে বরাদ্দ হয়। এই অবজেক্টগুলোর জীবনকাল একটি মেথড কলের বাইরে পর্যন্ত বিস্তৃত হতে পারে। যখন রেফারেন্সের বলয় শেষ হয়ে যায় বা রেফারেন্স নাল করে দেওয়া হয়, তখনই কেবল হিপের এই অবজেক্টটি গারবেজ কালেক্টরের নজরে আসে এবং সুবিধাজনক সময়ে তা মুছে ফেলা হয়। স্ট্যাকের মতো এখানে নিয়তিবাদী (deterministic) নিষ্পত্তি ঘটে না।
৩. স্ট্যাক ও হিপের সংযোগ: একটি পূর্ণাঙ্গ চিত্র
আমরা একটি জটিল দৃশ্য মনে মনে কল্পনা করি: একটি প্রোগ্রামে একাধিক থ্রেড চলছে।
- প্রতিটি থ্রেডের নিজস্ব একটি প্রাইভেট স্ট্যাক আছে। সেই স্ট্যাক অন্য কোনো থ্রেড দেখতে বা তাতে প্রবেশ করতে পারে না।
- সম্পূর্ণ JVM-এ একটি মাত্র হিপ অঞ্চল আছে, যা সমস্ত থ্রেডের মধ্যে ভাগাভাগি করা (shared) হয়।
এর অর্থ, যদি একটি মেথড লোকাল ভ্যারিয়েবল int count = 5 ঘোষণা করে, তাহলে সেই count সেই থ্রেডের ব্যক্তিগত সম্পত্তি এবং সম্পূর্ণ থ্রেড-সেইফ। কিন্তু যদি দুটি থ্রেড একই Car অবজেক্টের দিকে নির্দেশিত দুটি রেফারেন্স ধারণ করে, তাহলে তারা উভয়েই হিপের সেই একক Car ইন্সট্যান্সের model বা year পরিবর্তন করতে সক্ষম, যদি না আমরা সিঙ্ক্রোনাইজেশন মেকানিজম ব্যবহার করি। এই ভাগাভাগিই জাভার মাল্টি-থ্রেডিংকে শক্তিশালী করে, আবার এর সঠিক ব্যবহার না জানলে রেস কন্ডিশনের মতো জটিল বাগ সৃষ্টি করে।
আরেকটি গুরুত্বপূর্ণ বিষয় হলো সাইজ। স্ট্যাকের সাইজ সাধারণত ছোট ও স্থির (যেমন ডিফল্ট 1MB, -Xss ফ্ল্যাগ দিয়ে পরিবর্তনযোগ্য) হয়। হিপের সাইজ বিশাল এবং ডায়নামিক হতে পারে (যেমন -Xms ও -Xmx ফ্ল্যাগ দিয়ে সেট করা হয়)। যদি স্ট্যাকের সীমা অতিক্রান্ত হয় (যেমন অসীম রিকার্সন), তখন StackOverflowError পাবেন। আর যদি হিপ ভরে যায়, তখন মাথাচাড়া দিয়ে উঠবে OutOfMemoryError।
সম্পূর্ণ উদাহরণ: একটি পর্যবেক্ষণযোগ্য ডেমো
নিচের কোডটি একটি সম্পূর্ণ রানেবল জাভা প্রোগ্রাম যা স্পষ্টভাবে প্রিমিটিভ ভ্যারিয়েবলের (স্ট্যাক) এবং অবজেক্টের (হিপ) আচরণ প্রদর্শন করে। প্রোগ্রামটি বস্তুর কাউন্টার বাড়ায় এবং গারবেজ কালেকশনের ইঙ্গিত দিতে পারে।
public class HeapVsStackDemo {
// একটি সাধারণ ইন্টিজার কাউন্টার যা হিপে সংরক্ষিত হবে
static class Counter {
int value;
public Counter(int value) {
this.value = value;
}
}
public static void main(String[] args) {
// লোকাল প্রিমিটিভ ভ্যারিয়েবল (স্ট্যাকে)
int localNumber = 100;
// রেফারেন্স ভ্যারিয়েবল (স্ট্যাকে) এবং অবজেক্ট (হিপে)
Counter firstCounter = new Counter(5);
Counter secondCounter = new Counter(10);
System.out.println("প্রথমে লোকাল সংখ্যা: " + localNumber);
System.out.println("প্রথম কাউন্টারের মান: " + firstCounter.value);
System.out.println("দ্বিতীয় কাউন্টারের মান: " + secondCounter.value);
// একটি মেথড কল যা হিপের অবজেক্টকে পরিবর্তন করে
incrementAndPrint(localNumber, firstCounter);
System.out.println("মেথড কলের পর লোকাল সংখ্যা (অপরিবর্তিত থাকা উচিত): " + localNumber);
System.out.println("মেথড কলের পর প্রথম কাউন্টারের মান (পরিবর্তিত): " + firstCounter.value);
// রেফারেন্স ছেঁটে ফেলা, গারবেজ কালেক্টরকে অবজেক্ট মুছে ফেলার অনুমতি দেওয়া
secondCounter = null;
// সিস্টেমকে গারবেজ কালেক্টর চালনার অনুরোধ (গ্যারান্টি নয়)
System.gc();
System.out.println("দ্বিতীয় কাউন্টারকে null করা হয়েছে এবং gc() কল করা হয়েছে।");
// বড় পরিমাণে অবজেক্ট তৈরি করে হিপের ব্যবহার দেখানো (সতর্কতা: এটি ধীরগতির হতে পারে)
System.out.println("হিপে এক মিলিয়ন অবজেক্ট তৈরি শুরু...");
for (int i = 0; i < 1_000_000; i++) {
Counter temp = new Counter(i);
// temp রেফারেন্সটি প্রতি লুপেই স্কোপের বাইরে চলে যাচ্ছে, ফলে অবজেক্টগুলি গারবেজ হয়
}
System.out.println("এক মিলিয়ন অবজেক্ট তৈরি শেষ। আপনার হিপ মনিটর করে দেখতে পারেন।");
}
public static void incrementAndPrint(int num, Counter cnt) {
// num প্রিমিটিভ লোকাল ভ্যারিয়েবল, তাই এটি স্ট্যাকের কপি। আসল localNumber অপরিবর্তিত।
num = num + 100;
// cnt একটি রেফারেন্স, এর মাধ্যমে হিপের আসল অবজেক্ট পরিবর্তন হয়।
cnt.value = cnt.value + 50;
System.out.println("incrementAndPrint এর মধ্যে num: " + num + ", cnt.value: " + cnt.value);
}
}
এই উদাহরণে প্রথমেই আমরা localNumber নামক একটি প্রিমিটিভ লোকাল ভ্যারিয়েবল তৈরি করি, যা নিশ্চিতভাবেই স্ট্যাকে থাকে। এরপর firstCounter ও secondCounter নামের দুটি রেফারেন্স ভ্যারিয়েবল স্ট্যাকের main ফ্রেমে তৈরি হলো, কিন্তু new Counter(...) এর মাধ্যমে Counter-এর বাস্তব ইন্সট্যান্সটি তৈরি হলো হিপে। incrementAndPrint মেথডটি কল করার সময় num প্যারামিটার হিসাবে আমরা localNumber-এর একটি কপি স্ট্যাকে পাই। মেথডের ভেতরে num পরিবর্তন করলেও তা মূল localNumber-এর কোনো পরিবর্তন ঘটায় না, কারণ প্রিমিটিভ পাস-বাই-ভ্যালু হয়। অন্যদিকে, cnt প্যারামিটারটি আমরা firstCounter রেফারেন্সের একটি কপি হিসাবে পাই, যা একই হিপ অবজেক্টকে নির্দেশ করছে। ফলে cnt.value পরিবর্তন করলে মূল firstCounter-এর অবস্থাও চিরস্থায়ীভাবে বদলে যায়। শেষ লুপটিতে আমরা লক্ষ লক্ষ অস্থায়ী Counter অবজেক্ট তৈরি করে দেখাই কীভাবে হিপ ভরে ওঠে এবং গারবেজ কালেক্টর ধাপে ধাপে পরিষ্কার করে। রান করলে আপনি জাভা মনিটরিং টুল (jconsole, jvisualvm) খুলে হিপ মেমোরির ওঠানামা প্রত্যক্ষ করতে পারবেন।
সাধারণ সমস্যা ও সমাধান
হিপ ও স্ট্যাক সংক্রান্ত ভুল অনেক কষ্টের কারণ হতে পারে। নিচে বহুল-পরিচিত কিছু সমস্যা এবং তাদের সমাধানের উপায় দেওয়া হলো।
StackOverflowError: এটি ঘটে যখন মেথড কলের গভীরতা অত্যন্ত বেড়ে যায় এবং স্ট্যাকের নির্ধারিত সাইজ পূর্ণ হয়ে যায়। সবচেয়ে সাধারণ কারণ হলো ভুল বেস কেস সহ রিকার্সিভ মেথড।- সমাধান: প্রথমে রিকার্সিভ মেথডের বেস কেস যুক্তিসঙ্গত সময়ে পৌঁছাচ্ছে কিনা নিশ্চিত করুন। অসীম রিকার্সন ভেঙে ফেলুন। যদি রিকার্সন সঠিক হয় এবং বড় ইনপুটের জন্যই সমস্যা হয়, তাহলে JVM-এর স্ট্যাক সাইজ বাড়াতে
-XssJVM আর্গুমেন্ট ব্যবহার করতে পারেন (যেমন-Xss2m)। তবে সাধারণত রিকারসনের বদলে ইটারেটিভ সমাধান প্রয়োগ করাই অধিক নিরাপদ।
- সমাধান: প্রথমে রিকার্সিভ মেথডের বেস কেস যুক্তিসঙ্গত সময়ে পৌঁছাচ্ছে কিনা নিশ্চিত করুন। অসীম রিকার্সন ভেঙে ফেলুন। যদি রিকার্সন সঠিক হয় এবং বড় ইনপুটের জন্যই সমস্যা হয়, তাহলে JVM-এর স্ট্যাক সাইজ বাড়াতে
OutOfMemoryError: Java heap space: অ্যাপ্লিকেশন যখন প্রয়োজনাতিরিক্ত অবজেক্ট তৈরি করে এবং সেগুলো গারবেজ কালেক্টর সরাতে না পারার আগেই হিপ ভরে যায়, তখন এটি ঘটে।- সমাধান: প্রোফাইলিং টুল ব্যবহার করে মেমোরি লিক শনাক্ত করুন। অনেক সময় সংগৃহীত ডেটা স্ট্রাকচার (যেমন
Map,List) থেকে ভুলবশত অবজেক্টগুলোকে সরানো হয় না।try-with-resourcesব্যবহার করে স্ট্রীম ও কানেকশন বন্ধ করা নিশ্চিত করুন। JVM-এর হিপ সাইজ বাড়াতে-Xmxপ্যারামিটার ব্যবহার করুন (যেমন-Xmx2048m), তবে মেমোরি লিক থাকলে সেটা কেবল সাময়েক প্রতিকার।
- সমাধান: প্রোফাইলিং টুল ব্যবহার করে মেমোরি লিক শনাক্ত করুন। অনেক সময় সংগৃহীত ডেটা স্ট্রাকচার (যেমন
NullPointerException: এটি স্ট্যাকের একটি রেফারেন্স ভ্যারিয়েবল যখন হিপে কোনো বাস্তব অবজেক্ট নির্দেশ করে না (null), আর আপনি তার উপর অপারেশন চালানোর চেষ্টা করেন, তখন হয়।- সমাধান: প্রতিটি অবজেক্ট ব্যবহারের আগে
nullচেক করুন, জাভা ৮ থেকে আসাOptionalব্যবহার করেnull-এর নিরাপদ ব্যবস্থাপনা করুন, এবং কোন পদ্ধতিতে অবজেক্ট পাস বা রিটার্ন করা হচ্ছে তা ভালভাবে ডিজাইন করুন। স্ট্যাক রেফারেন্স নাল করে দেওয়ার পর গারবেজ কালেক্টর হিপের অবজেক্ট মুছে দিতে পারে; নাল করার পূর্ববর্তী রেফারেন্স ব্যবহারের চেষ্টা করলে এই ত্রুটি পাওয়ার সম্ভাবনা থাকে।
- সমাধান: প্রতিটি অবজেক্ট ব্যবহারের আগে
ভালো প্র্যাকটিস হিসাবে, জাভা প্রোফাইলিং টুল (JVisualVM, JProfiler বা IntelliJ Profiler) ব্যবহার করতে শিখুন। এগুলো আপনাকে রিয়েল-টাইমে স্ট্যাক ট্রেস এবং হিপ ডাম্প দেখাবে, যা মেমোরি সমস্যা সমাধানে অপরিহার্য।
সারসংক্ষেপ
জাভার মেমোরি জগতে হিপ ও স্ট্যাক একে অপরের পরিপূরক এবং অ্যাপ্লিকেশন সুষ্ঠুভাবে চালানোর জন্য অত্যাবশ্যক। পুরো আলোচনার নির্যাস কয়েকটি বুলেট পয়েন্টে টেনে আনা যায়:
- স্ট্যাক হলো লাস্ট-ইন, ফার্স্ট-আউট (LIFO) মেমোরি, যেখানে প্রতিটি থ্রেডের জন্য আলাদাভাবে মেথড ফ্রেম, প্রিমিটিভ লোকাল ভ্যারিয়েবল এবং রেফারেন্স ভ্যারিয়েবল সংরক্ষিত থাকে। এখানে মেমোরি বরাদ্দ ও মুক্তি স্বয়ংক্রিয় ও দ্রুত।
- হিপ হলো শেয়ারড, ডায়নামিক মেমোরির বিশাল অঞ্চল, যেখানে
newকিওয়ার্ডের সাহায্যে নির্মিত সমস্ত অবজেক্ট এবং তাদের ইনস্ট্যান্স ভ্যারিয়েবল রাখা হয়। এর ব্যবস্থাপনা গারবেজ কালেক্টরের মাধ্যমে স্বয়ংক্রিয়, কিন্তু স্ট্যাকের চেয়ে ধীর। - প্রিমিটিভ ভ্যারিয়েবল মেথডের মাধ্যমে পাস করলে পাস-বাই-ভ্যালু ঘটে এবং মূল ভ্যারিয়েবল সুরক্ষিত থাকে। অবজেক্ট রেফারেন্স পাস করলেও তা পাস-বাই-ভ্যালু হয়, কিন্তু সেই রেফারেন্সের মাধ্যমে হিপের একই অবজেক্টে পরিবর্তন আনা সম্ভব।
- সাধারণ ত্রুটি
StackOverflowErrorওOutOfMemoryErrorযথাক্রমে অনিয়ন্ত্রিত রিকার্সন ও অতিরিক্ত ডেটা জমার লক্ষণ, এবং-Xssও-XmxJVM অপশন অথবা বাগ ফিক্সের মাধ্যমে এদের সমাধান করা যায়।
এই ধারণাগুলো আয়ত্ত করে আপনি আরো কার্যকর, রেস-কন্ডিশনমুক্ত এবং মেমোরি-এফিশিয়েন্ট জাভা অ্যাপ্লিকেশন ডিজাইন করতে পারবেন। যখনই আপনি একটি new লিখবেন বা একটি রিকার্সিভ ফাংশন কল করবেন, মনে রাখবেন আপনি হিপ ও স্ট্যাকের মধ্যে এক নীরব যান্ত্রিক নৃত্য পরিচালনা করছেন।