Stack Overflow হল একটি কল স্ট্যাক ওভারফ্লো ত্রুটি (java.lang.StackOverflowError) যা থ্রেডের সর্বোচ্চ স্ট্যাক গভীরতা অতিক্রম করলে ঘটে। Java Virtual Machine Specification অনুসারে, 64-বিট সিস্টেমের জন্য JVM-এ সাধারণ স্ট্যাক গভীরতা হল 1024 ফ্রেম। প্রধান কারণ হল বেস কন্ডিশন ছাড়া অসীম রিকার্সন।
মূল পয়েন্ট
StackOverflowError হল জাভা ভার্চুয়াল মেশিন (JVM) বা Android Runtime (ART)-এর একটি মারাত্মক ত্রুটি যা ঘটে যখন একটি থ্রেডের কল স্ট্যাক সর্বোচ্চ অনুমোদিত গভীরতায় পৌঁছায়। OutOfMemoryError (Heap-এর অভাব) থেকে ভিন্ন, StackOverflowError মেমোরির একটি ভিন্ন এলাকা — স্ট্যাকের সাথে সম্পর্কিত, যেখানে মেথড কল ফ্রেম এবং লোকাল ভেরিয়েবল সংরক্ষিত হয়।
প্রতিটি মেথড কল স্ট্যাকে একটি ফ্রেম তৈরি করে: রিটার্ন অ্যাড্রেস, প্যারামিটার এবং লোকাল ভেরিয়েবল। মেথড থেকে ফিরলে ফ্রেমটি ধ্বংস হয়ে যায়। যদি কোনো মেথড বেস কন্ডিশন ছাড়া নিজেকে কল করে (রিকার্সন), তাহলে ফ্রেমগুলি জমতে থাকে যতক্ষণ না স্ট্যাক পূর্ণ হয়। JVM একটি নতুন ফ্রেম বরাদ্দ করতে পারে না এবং «null» বার্তা (Java-তে) বা অবিরাম পুনরাবৃত্ত স্ট্যাক লাইনের ইঙ্গিত সহ StackOverflowError থ্রো করে।
একটি থ্রেডের স্ট্যাকের আকার তৈরি করার সময় নির্ধারিত হয় এবং কার্যকর করার সময় পরিবর্তিত হয় না। Android-এ, প্রধান থ্রেডের সাধারণ স্ট্যাক আকার 32–48 KB, যা অনেক লোকাল ভেরিয়েবল ছাড়া মেথডের জন্য প্রায় 512–1024 ফ্রেমের গভীরতা দেয়। পটভূমি থ্রেডের জন্য, ডিফল্ট আকার ছোট — 16–24 KB।
কল স্ট্যাক (Call Stack) হল একটি LIFO (Last In, First Out) ডেটা স্ট্রাকচার যা মেথড এক্সিকিউশনের ক্রম পরিচালনা করে। প্রতিবার প্রোগ্রাম যখন কোনো মেথড কল করে, JVM স্ট্যাকে একটি ফ্রেম তৈরি করে এবং এটি উপরে রাখে। মেথড সম্পূর্ণ হলে, ফ্রেমটি সরিয়ে ফেলা হয়।
প্রতিটি ফ্রেমে থাকে: অপারেন্ড স্ট্যাক (বাইটকোড নির্দেশের জন্য), লোকাল ভেরিয়েবলের অ্যারে (this সহ), কনস্ট্যান্ট পুলের রেফারেন্স এবং রিটার্ন অ্যাড্রেস। কোনো মেথডের যত বেশি লোকাল ভেরিয়েবল থাকবে, তার ফ্রেমের আকার তত বড় হবে এবং স্ট্যাক পূর্ণ হওয়ার আগে তত কম মেথড কল করা যাবে। 10টি প্যারামিটার এবং 20টি লোকাল ভেরিয়েবলযুক্ত একটি মেথড প্যারামিটারবিহীন মেথডের তুলনায় প্রায় 3 গুণ বেশি স্থান নেয়।
Android-এ, ART তার নিজস্ব স্ট্যাক বাস্তবায়ন ব্যবহার করে, যা Desktop JVM থেকে ভিন্ন। ART নির্দিষ্ট সীমার মধ্যে স্ট্যাক গতিশীলভাবে বাড়াতে পারে, তবে প্রতিটি থ্রেডের জন্য একটি কঠোর সীমা এখনও বিদ্যমান। প্রধান থ্রেডের (UI থ্রেড) স্ট্যাক সবচেয়ে বড়, কারণ এটি সম্পূর্ণ Activity লাইফসাইকেল এবং ইভেন্ট প্রসেসিং পরিচালনা করে।
// রিকার্সন যা StackOverflowError-এর দিকে নিয়ে যায়
fun recursiveCall(depth: Int): Int {
return recursiveCall(depth + 1) // কোনো বেস কন্ডিশন নেই
}
// কল ~1000 গভীরতায় StackOverflowError ঘটাবে
recursiveCall(0)
পাঁচটি সাধারণ পরিস্থিতি মোবাইল অ্যাপ্লিকেশনে StackOverflowError ঘটায়। এগুলোর বেশিরভাগই রিকার্সনের সাথে সম্পর্কিত, তবে কম স্পষ্ট কারণও রয়েছে।
সবচেয়ে সাধারণ কারণ। ডেভেলপার থামার শর্ত ছাড়া বা এমন শর্ত সহ একটি রিকার্সিভ মেথড লেখেন যা কখনই true হয় না। প্রতিটি কল একটি ফ্রেম যোগ করে, এবং ফ্রেমের আকার অনুযায়ী স্ট্যাক 500–2000 পুনরাবৃত্তিতে পূর্ণ হয়। একটি সাধারণ উদাহরণ: n == 0 পরীক্ষা না করে n! ফ্যাক্টোরিয়াল গণনা করা।
প্রতিটি রিকার্সিভ মেথডের শুরুতে বেস কন্ডিশন পরীক্ষা করুন। Kotlin-এ, প্যারামিটার যাচাইয়ের জন্য require() বা check() ব্যবহার করুন। গভীর রিকার্সনের (100 স্তরের বেশি) জন্য, পুনরাবৃত্তিমূলক পদ্ধতি দিয়ে প্রতিস্থাপনের কথা বিবেচনা করুন।
ক্লাস A B-এর ইনস্ট্যান্স তৈরি করে, ক্লাস B A-এর ইনস্ট্যান্স তৈরি করে — এটি কনস্ট্রাক্টরে একটি চক্রীয় নির্ভরতা। A তৈরি করার চেষ্টা করলে, B-এর কনস্ট্রাক্টর কল হয়, যা A-এর কনস্ট্রাক্টরকে কল করে, এবং এভাবে StackOverflowError পর্যন্ত। DI ফ্রেমওয়ার্ক (Dagger, Hilt) কম্পাইল সময়ে এই ধরনের চক্র সনাক্ত করে, কিন্তু ম্যানুয়াল অবজেক্ট তৈরি তাদের ধরে না।
নির্ভরতা গ্রাফ সহ ডিপেন্ডেন্সি ইনজেকশন ব্যবহার করুন: Dagger বা Koin বিল্ড সময়ে চক্র পরীক্ষা করে। যদি চক্র এড়ানো না যায়, তাহলে সরাসরি নির্ভরতাকে lazy ইনিশিয়ালাইজেশন বা Provider ফ্যাক্টরি সহ একটি ইন্টারফেস দিয়ে প্রতিস্থাপন করুন।
// চক্রীয় নির্ভরতা — StackOverflowError
class A(private val b: B)
class B(private val a: A)
// Lazy সমাধান
class A(private val bProvider: Provider<B>)
View ট্রি (ViewGroup.getChildAt()), ফাইল সিস্টেম বা JSON কাঠামোর রিকার্সনের মাধ্যমে ট্রাভার্সাল 500–1000 এর বেশি উপাদানের গভীরতায় স্ট্যাক সীমা অতিক্রম করতে পারে। 20 স্তরের নেস্টিং সহ Android ViewGroup বিরল, কিন্তু 2000 নেস্টেড অবজেক্ট সহ রিকার্সিভ JSON পার্সিং একটি বাস্তব পরিস্থিতি।
রিকার্সিভ ট্রাভার্সালকে স্পষ্ট Stack<T> বা ArrayDeque-এর মাধ্যমে পুনরাবৃত্তিমূলক ট্রাভার্সাল দিয়ে প্রতিস্থাপন করুন। এটি স্ট্যাক ওভারফ্লোর ঝুঁকি সম্পূর্ণরূপে দূর করে, কারণ heap অবজেক্ট স্ট্যাক সীমা দ্বারা সীমাবদ্ধ নয়। Queue-এর মাধ্যমে BFS (Breadth-First Search)ও সমস্যার সমাধান করে।
Android-নির্দিষ্ট কারণ: কনফিগারেশনের ভুল হ্যান্ডলিং-এ লাইফসাইকেল মেথডের চক্রীয় কল। উদাহরণস্বরূপ, onConfigurationChanged-এর ভিতরে recreate() কল করা, যা আবার onConfigurationChanged কল করে, এবং এভাবে StackOverflowError পর্যন্ত। একইভাবে: onLayout()-এর ভিতরে setContentView(), যা আরেকটি মাপ এবং লেআউট ট্রিগার করে।
কনফিগারেশন পরিবর্তনের সাথে সম্পর্কিত মেথডের ভিতরে recreate() কল করবেন না। থিম পরিবর্তন করার সময় UI আপডেট করতে, recreate ছাড়া setTheme() ব্যবহার করুন। গতিশীল ওরিয়েন্টেশন পরিবর্তনের জন্য — কনফিগারেশনে ফ্ল্যাগ ছাড়া, requestOrientation() একবার কল করুন।
Gson, Moshi বা Kotlin Serialization যখন চক্রীয় রেফারেন্স (A, B-কে রেফার করে, B, A-কে রেফার করে) সহ অবজেক্ট সিরিয়ালাইজ করার চেষ্টা করে, তখন অসীম রিকার্সনে চলে যায় এবং StackOverflowError-এর সাথে ক্র্যাশ করে। দ্বিমুখী সম্পর্ক (JPA, Room with ForeignKey) সহ Entity সিরিয়ালাইজ করার সময় এটি একটি সাধারণ সমস্যা।
চক্রের এক পাশে @Transient, @JsonIgnore বা @kotlinx.serialization.Transient ব্যবহার করুন। Gson-এর জন্য — স্পষ্ট গভীরতা সীমা সহ JsonSerializer। Room-এর জন্য — Entity কখনও সরাসরি সিরিয়ালাইজ করবেন না, DTO ম্যাপার ব্যবহার করুন।
StackOverflowError নির্ণয় অন্যান্য মেমোরি ত্রুটির তুলনায় সহজ: স্ট্যাক ট্রেস বেশিরভাগ ক্ষেত্রে কলের একটি পুনরাবৃত্ত ক্রম দেখায়। এটি অবিলম্বে রিকার্সনের দিকে নির্দেশ করে।
StackOverflowError-এর স্ট্যাক ট্রেস অনন্য: প্রথম 200–500 লাইনের পরে, কলের একই প্যাটার্ন পুনরাবৃত্তি হতে শুরু করে। JVM শেষে পুনরাবৃত্ত লাইনগুলি কেটে ফেলে এবং «... 1234 more» দেখায়। «...»-এর আগে অ-পুনরাবৃত্ত লাইনের সংখ্যা ত্রুটির কারণ রিকার্সন গভীরতা নির্দেশ করে।
স্ট্যাক ট্রেসের প্রথম লাইনগুলি পড়ুন — তারা দেখায় কোন মেথড থেকে পুনরাবৃত্তি শুরু হয়েছে। সেই মেথডটি খুঁজুন যা নিজেকে কল করে বা একটি কল চেইন তৈরি করে যা তাতে ফিরে আসে। বেস কন্ডিশন ঠিক করুন বা রিকার্সনকে লুপ দিয়ে প্রতিস্থাপন করুন।
অস্থায়ীভাবে, JVM ফ্ল্যাগ -Xss-এর মাধ্যমে স্ট্যাক আকার বাড়িয়ে সমস্যার সমাধান করা যেতে পারে। Android-এর জন্য, স্ট্যাক আকার AndroidManifest-এর মাধ্যমে সেট করা হয়: android:largeHeap স্ট্যাককে প্রভাবিত করে না। কোডে থ্রেড স্ট্যাক বাড়ানোর জন্য: Thread(ThreadGroup, Runnable, name, stackSize)। stackSize হল বাইটে কাঙ্ক্ষিত আকার।
// বর্ধিত স্ট্যাক সহ থ্রেড তৈরি করা
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()
গুরুত্বপূর্ণ: স্ট্যাক বাড়ানো সমস্যার সমাধান করে না, শুধু বিলম্বিত করে। 10,000 স্তরের রিকার্সনে, 64 KB স্ট্যাক 128 KB স্ট্যাক দিয়ে প্রতিস্থাপিত হবে, যা 20,000 স্তর দেবে — কিন্তু ত্রুটি এখনও ঘটবে, শুধু পরে। একমাত্র সঠিক সমাধান হল রিকার্সনের পুনরাবৃত্তিমূলক প্রতিস্থাপন।
পুনরাবৃত্তিমূলক অ্যালগরিদম মধ্যবর্তী অবস্থা সংরক্ষণের জন্য কল স্ট্যাক ব্যবহার করে না — তারা heap-এ সংরক্ষণ করে (Stack<T> বা ArrayDeque)। বাইনারি ট্রি ট্রাভার্সাল, ফ্যাক্টোরিয়াল গণনা, ফিবোনাচি — যেকোনো রিকার্সনকে স্পষ্ট স্ট্যাকের মাধ্যমে পুনরাবৃত্তিতে রূপান্তর করা যেতে পারে।
// পুনরাবৃত্তিমূলক ট্রি ট্রাভার্সাল — StackOverflow-এর কোনো ঝুঁকি নেই
fun traverseIterative(root: Node?) {
val stack = ArrayDeque<Node>()
stack.push(root)
while (stack.isNotEmpty()) {
val node = stack.pop() ?: continue
process(node)
node.right?.let { stack.push(it) }
node.left?.let { stack.push(it) }
}
}
StackOverflowError প্রতিরোধ হল নিয়ম এবং সরঞ্জামের একটি সেট যা প্রোডাকশনে পৌঁছানোর আগে সম্ভাব্য রিকার্সিভ চক্র চিহ্নিত করে।
ডিবাগ বিল্ডে রিকার্সিভ মেথডে একটি প্রতিরক্ষামূলক গভীরতা কাউন্টার যোগ করুন। যদি গভীরতা একটি সীমা (যেমন 1000) অতিক্রম করে, তাহলে স্পষ্ট বার্তা সহ একটি ব্যতিক্রম থ্রো করুন। এটি অপাঠ্য ট্রেস সহ StackOverflowError-কে বোধগম্য ব্যবসায়িক ব্যতিক্রমে রূপান্তরিত করে।
fun safeRecursive(n: Int, depth: Int = 0): Int {
if (depth > 1000) {
throw IllegalStateException("রিকার্সন 1000 স্তর অতিক্রম করেছে")
}
return if (n <= 1) n
else safeRecursive(n - 1, depth + 1)
}
Detekt (Kotlin) এবং Infer (Facebook) স্ট্যাটিক বিশ্লেষণ স্তরে সম্ভাব্য অসীম রিকার্সন খুঁজে পায়। Detekt-এ PotentiallyInfiniteRecursion নিয়ম রয়েছে যা প্যারামিটার পরিবর্তন না করে স্ব-কল সম্পর্কে সতর্ক করে। এটি CI নিয়ম সেটে সক্ষম করুন এবং severity error-এ সেট করুন।
কোড পর্যালোচনায়, মনোযোগ দিন: যেকোনো স্ব-কল মেথড, ল্যাম্বডার ভিতরে রিকার্সিভ কল (Kotlin ইনলাইন ফাংশন), বিভিন্ন ক্লাসের মধ্যে চক্রীয় কল, প্রপার্টি ডেলিগেটে রিকার্সন। প্রতিটি রিকার্সিভ মেথডের জন্য পরীক্ষা করুন: বেস কন্ডিশন আছে কি, প্রতিটি ধাপে প্যারামিটার পরিবর্তিত হয় কি, প্যারামিটার পরিবর্তন বেস কন্ডিশনে পৌঁছানোর গ্যারান্টি দেয় কি।
Kotlin tailrec মডিফায়ার সমর্থন করে: যদি একটি রিকার্সিভ মেথড tailrec দিয়ে চিহ্নিত করা হয় এবং কলটি টেল (শেষ অপারেশন) হয়, তাহলে কম্পাইলার এটিকে পুনরাবৃত্তিতে রূপান্তরিত করে। তবে, tailrec শুধুমাত্র স্ব-কলের জন্য কাজ করে (মেথড নিজেকে সরাসরি কল করে), পারস্পরিক রিকার্সনের জন্য কাজ করে না, এবং Android-সঙ্গতিপূর্ণ Kotlin সংস্করণ 1.5-এর আগে সমর্থিত নয়।
tailrec fun factorial(n: Int, acc: Int = 1): Int {
return if (n <= 1) acc
else factorial(n - 1, acc * n) // টেল কল
}
সচরাচর জিজ্ঞাসিত প্রশ্ন
হ্যাঁ, কিন্তু শুধুমাত্র Java স্তরে। Error, Exception-এর মতো, Throwable। তবে, StackOverflowError-এর পর স্ট্যাক ক্ষতিগ্রস্ত হয় — যে ফ্রেমগুলি ফিট হয়নি তারা সঠিকভাবে সম্পূর্ণ হতে পারে না। catch ব্লকে একটি নতুন অবজেক্ট তৈরি করার চেষ্টা আরেকটি StackOverflowError সৃষ্টি করতে পারে।
প্রধান থ্রেডের জন্য — 32–48 KB, পটভূমি থ্রেডের জন্য — 16–24 KB। সঠিক আকার Android সংস্করণ এবং ডিভাইস প্রস্তুতকারকের উপর নির্ভর করে। ART গতিশীল স্ট্যাক সম্প্রসারণ ব্যবহার করে তবে প্রাথমিক মানের 2×-এর বেশি নয়।
Kotlin-এ — হ্যাঁ, যদি মেথডটি tailrec দিয়ে চিহ্নিত করা হয়। কম্পাইলার টেল রিকার্সনকে পুনরাবৃত্তিতে রূপান্তরিত করে, স্ট্যাক বৃদ্ধি সম্পূর্ণরূপে দূর করে। Java-তে, টেল রিকার্সন JVM দ্বারা অপটিমাইজ করা হয় না (Scala-র মতো ফাংশনাল ভাষার বিপরীতে)।
স্ট্যাকের আকার এমুলেটর এবং প্রকৃত ডিভাইসে ভিন্ন হতে পারে। এমুলেটর সাধারণ 512–1024 KB স্ট্যাক সহ Desktop JVM ব্যবহার করে, যেখানে Android ART 32–48 KB ব্যবহার করে। ত্রুটিটি ART-তে Desktop JVM-এর তুলনায় আগে দেখা দেবে।
মেমোরি এলাকা: StackOverflowError স্ট্যাক ত্রুটি (কল ফ্রেম), OutOfMemoryError heap ত্রুটি (অবজেক্ট)। StackOverflowError প্রায় সবসময় রিকার্সনের কারণে হয়, যখন OutOfMemoryError মেমোরি লিক বা বড় অবজেক্টের কারণে হয়।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন