مدونة فؤاد المالكي
  • الصفحة الرئيسية
ما هو الـ stacktrace؟

ما هو الـ stacktrace؟

في لغة الجافا، تنقسم ذاكرة الـ JVM إلى عدة أقسام، ومنها ما يسمى بالـ stack. عند استدعاء دالة س (method) فإنه يُنشأ frameبداخله معلومات الدالة س ويوضع هذا الـ frame أعلى الـ stack. وعند الانتهاء من تنفيذ الدالة س، يتم إخراج الـ stack frame الخاص بالدالة س من الـ stack ويرجع مسار التنفيذ (flow of control) إلى الدالة السابقة والتي استدعت الدالة س.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main
{
    public static void main(String[] args)
    {
        x();
    }
    
    public static void x()
    {
        y();
    }
    
    public static void y()
    {
        // ...
    }
}

شكل الـ stack سيكون كالتالي:

بدايةً يقوم الـ JVM بإنشاء الـ main thread ثم يقوم باستدعاء دالة ()main ويضع frame خاص بهذه الدالة في الـ stack. خلال تنفيذ دالة الـ ()main سيقوم باستدعاء الدالة ()x وتباعاً يضع frame خاص بها في الـ stack. وخلال تنفيذ الدالة ()x يستدعي الدالة ()yويضع frame في الـ stack. عند الانتهاء من تنفيذ الدالة ()y يقوم الـ JVM بإزالة الـ frame الموجود بأعلى الـ stack وهو الـ frameالخاص بالدالة ()y. بعد ذلك يعود لاستكمال تنفيذ الدالة ()x ويزيل الـ frame الخاص بها عند الانتهاء من تنفيذها. وأخيراً يعود للدالة()main ويكمل تنفيذها، وعند الانتهاء منها يزيل آخر frame موجود في الـ stack ويقوم بإنهاء الـ main tread (ليسس شرطاً أن ينتهي البرنامج بانتهاء الـ main thread).

ما هو الـ stacktrace؟

الـ stacktrace باختصار هو سلسلة من الاستدعاءات للدوال method invocations مطابقة للـ stack frames. يستخدم الـ stacktrace عادةً في معالجة الأخطاء واكتشاف مصدرها. لنأخذ مثال بسيط لتتضح الصورة:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.IOException;
 
public class Main
{
    public static void main(String[] args) throws Exception
    {
        x();
    }
    
    public static void x() throws Exception
    {
        y();
    }
    
    public static void y() throws IOException
    {
        throw new IOException("IOException in y()");
    }
}

يتم طباعة الـ stacktrace على الشاشة في حالتين:

– إما أن نمسك الـ exception في try and catch وبداخل الـ catch نطبع الـ stacktrace عن طريق الدالة ()e.printStackTraceحيث أن e هو مرجع للخطأ reference for the exception.
– أو نقوم برمي الـ exception وتحويله (exception propagation) إلى الدالة التي استدعت الدالة الحالية، حتى يصل الـ exception إلى آخر frame في الـ stack كما فعلنا في المثال الأخير.

برنامج الجافا في المثال الأخير سيطبع الـ stacktrace التالي:

1
2
3
4
Exception in thread "main" java.io.IOException: IOException in y()
    at Main.y(Main.java:17)
    at Main.x(Main.java:12)
    at Main.main(Main.java:7)

وكقاعدة عامة في أي stacktrace:

– السطر الأول يتكون من ٣‎ معلومات مهمة: اسم الـ thread، ونوع الـ exception، ورسالة الخطأ (إن وجدت). في المثال السابق اسم الـthread كان main، ونوع الـ exception كان java.io.IOException، ورسالة الخطأ كانت ()IOExcpetion in y.
– السطر الثاني يكون دائماً مصدر الـ exception، وفي الكود يكون المصدر هو الـ constructor الخاص بالـ exception. في المثال السابق كان مصدر الخطأ في السطر ١٧ وهو ;(“()throw new IOException(“IOException in y.
– السطر الأخير يحتوي على اسم الـ method التي تكون مدخل الـ thread وبدايته. في المثال السابق، مدخل الـ thread هو الدالة main.

الـ stacktrace مع Caused by

أحياناً يكون خطأ ما سببه خطأ آخر، ولذلك يمكن في لغة الجافا ربط exception معين على أنه سبب لـ exception آخر عن طريق تمريره كـ parameter في الـ constructor أو عن طريق استخدام الدالة ()initCause على مرجع الـ exception الجديد. وربما الخطأ س سببه الخطأ ص، والخطأ ص سببه الخطأ ع .. وهكذا. الـ stacktrace الناتج سيحتوي على معلومات الـ exception الجديد وجميع الـexceptions المتسببة فيه مفصولةة فيما بينهم بالجملة Caused by. المثال التالي سيوضح ذلك:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.IOException;
 
public class Main
{
    public static void main(String[] args) throws Exception
    {
        x();
    }
    
    public static void x() throws Exception
    {
        try
        {
            y();
        }
        catch(IOException e)
        {
            // ex is caused by e
            Exception ex = new Exception("Exception in x()");
            ex.initCause(e);
            throw ex;
            
            // or
            // throw new Exception("Exception in x()", e)
        }
    }
    
    public static void y() throws IOException
    {
        throw new IOException("IOException in y()");
    }
}

والـ stacktrace الناتج يكون كالتالي:

1
2
3
4
5
6
7
Exception in thread "main" java.lang.Exception: Exception in x()
    at Main.x(Main.java:19)
    at Main.main(Main.java:7)
Caused by: java.io.IOException: IOException in y()
    at Main.y(Main.java:30)
    at Main.x(Main.java:14)
    ... 1 more

نفس القاعدة السابقة تنطبق على الـ stacktrace الحالي باستثناء أنه لا يكتب اسم الـ thread في الـ exception الموجود بعد Caused by. أيضاً يوجد اختلاف طفيف هنا وهو السطر الأخير. السطر المفقود هذا يمكنك الحصول عليه من الـ exception السابق له. المثال الآتي سيوضح كيف يمكنك الحصول على السطور المفقودة من الـ stacktrace:

لنفرض أنه لدينا الـ stacktrace التالي:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Exception in thread "main" ExceptionA
    at A5
    at A4
    at A3
    at A2
    at A1
Caused by: ExceptionB
    at B6
    at B5
    at B4
    ... 3 more
Caused by: ExceptionC
    at C8
    at C7
    at C6
    ... 5 more

كقاعدة عامة، إذا كان المفقود س من السطور في exception معين، نأخذ آخر س من السطور من الـ exception السابق له. وبالتالي الـstacktrace الكامل سيكون بالشكل التالي:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Exception in thread "main" ExceptionA
    at A5
    at A4
    at A3
    at A2
    at A1
Caused by: ExceptionB
    at B6
    at B5
    at B4
    at A3
    at A2
    at A1
Caused by: ExceptionC
    at C8
    at C7
    at C6
    at B5
    at B4
    at A3
    at A2
    at A1

الـ stacktrace مع javac options -g & -g:none

عند عمل compile بالأمر javac -g فإن الـ stacktrace يكون محتوياً على أرقام الأسطر كما في الأمثلة السابقة، بينما لو تم عملcompile بالأمر javac -g:none فإن الـ stacktrace للمثال الأخير سيكون كالتالي:

1
2
3
4
5
6
Exception in thread "main" java.lang.Exception: Exception in x()
    at Main.x(Unknown Source)
    at Main.main(Unknown Source)
Caused by: java.io.IOException: IOException in y()
    at Main.y(Unknown Source)
    ... 2 more

لاحظ الاختلاف!

الـ stacktrace بدون exception

يمكن في لغة الجافا الحصول على stacktrace من أي مكان في الكود وطباعته مباشرة، دون الحاجة إلى exception. لاحظ المثال التالي:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main
{
    public static void main(String[] args)
    {
        x();
    }
    
    public static void x()
    {
        y();
    }
    
    public static void y()
    {
        Thread currentThread = Thread.currentThread();
        StackTraceElement[] elements = currentThread.getStackTrace();
        for(StackTraceElement element : elements)
        {
            System.out.println(element);
        }
    }
}

والناتج سيكون كالتالي:

1
2
3
4
java.lang.Thread.getStackTrace(Unknown Source)
Main.y(Main.java:16)
Main.x(Main.java:10)
Main.main(Main.java:5)

شارك هذه التدوينة

أضف تعليقك

إلغاء الرد

لن يتم نشر بريدك الإلكتروني. الحقول الإلزامية مشار إليها برمز (*) .

السابق التالي

أداة البحث

التصنيفات

  • أمن معلومات (1)
  • برمجة (7)

آخر المقالات

  • جمل switch المحسنة في جافا 14
  • بعض المهارات التقنية التي يجب على كل مبرمج معرفتها والإلمام بأساسياتها
  • الـ Lambda Expressions في لغة جافا
  • شهادات الـ SSL وطريقة عمل البروتوكول الآمن HTTPS
  • نبذة عن الجافا
  • الطريق إلى شهادة الجافا OCA 1Z0-803
  • ما هو الـ stacktrace؟
  • تعريف الـ Thread
جميع الحقوق محفوظة ٢‎٠٢‎٠‬ © فؤاد المالكي