Apa itu pelacakan tumpukan, dan bagaimana cara menggunakannya untuk men-debug kesalahan aplikasi saya?

Oct 21 2010

Terkadang ketika saya menjalankan aplikasi saya, itu memberi saya kesalahan yang terlihat seperti:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Orang-orang menyebut ini sebagai "jejak tumpukan". Apa itu jejak tumpukan? Apa yang dapat saya ceritakan tentang kesalahan yang terjadi dalam program saya?


Tentang pertanyaan ini - cukup sering saya melihat pertanyaan muncul di mana programmer pemula "mendapatkan kesalahan", dan mereka hanya menempelkan jejak tumpukan mereka dan beberapa blok kode acak tanpa memahami apa itu jejak tumpukan atau bagaimana mereka dapat menggunakannya. Pertanyaan ini dimaksudkan sebagai referensi bagi pemrogram pemula yang mungkin membutuhkan bantuan untuk memahami nilai pelacakan tumpukan.

Jawaban

623 RobHruska Oct 21 2010 at 21:52

Sederhananya, pelacakan tumpukan adalah daftar panggilan metode tempat aplikasi berada di tengah-tengah saat Pengecualian dilemparkan.

Contoh Sederhana

Dengan contoh yang diberikan dalam pertanyaan, kita dapat menentukan dengan tepat di mana pengecualian itu dilemparkan dalam aplikasi. Mari kita lihat jejak tumpukan:

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Ini adalah jejak tumpukan yang sangat sederhana. Jika kita mulai dari awal daftar "di ...", kita dapat mengetahui di mana kesalahan kita terjadi. Yang kami cari adalah pemanggilan metode teratas yang merupakan bagian dari aplikasi kami. Dalam hal ini, ini:

at com.example.myproject.Book.getTitle(Book.java:16)

Untuk men-debug ini, kita dapat membuka Book.javadan melihat baris 16, yaitu:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

Ini akan menunjukkan bahwa sesuatu (mungkin title) ada nulldalam kode di atas.

Contoh dengan rangkaian pengecualian

Terkadang aplikasi akan menangkap Pengecualian dan membuangnya kembali sebagai penyebab Pengecualian lain. Ini biasanya terlihat seperti:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

Ini mungkin memberi Anda pelacakan tumpukan yang terlihat seperti:

Exception in thread "main" java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.java:22)
        at com.example.myproject.Author.getBookIds(Author.java:36)
        ... 1 more

Yang berbeda dari yang satu ini adalah "Disebabkan oleh". Terkadang pengecualian akan memiliki beberapa bagian "Disebabkan oleh". Untuk ini, Anda biasanya ingin mencari "akar masalah", yang akan menjadi salah satu bagian "Disebabkan oleh" terendah dalam pelacakan tumpukan. Dalam kasus kami, ini:

Caused by: java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.java:22) <-- important line

Sekali lagi, dengan pengecualian ini kita ingin melihat garis 22dari Book.javauntuk melihat apa yang mungkin menyebabkan NullPointerExceptionsini.

Contoh yang lebih menakutkan dengan kode perpustakaan

Biasanya jejak tumpukan jauh lebih kompleks daripada dua contoh di atas. Berikut adalah contohnya (ini panjang, tetapi menunjukkan beberapa tingkat pengecualian berantai):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) at org.mortbay.jetty.Server.handle(Server.java:326) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542) at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228) at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166) at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30) ... 27 more Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity] at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822) at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268) at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321) at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210) at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195) at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689) at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
    ... 32 more
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
    ... 54 more

Dalam contoh ini, ada lebih banyak lagi. Yang paling kami khawatirkan adalah mencari metode yang berasal dari kode kami , yang mana saja di dalam com.example.myprojectpaket. Dari contoh kedua (di atas), pertama-tama kami ingin mencari akar penyebabnya, yaitu:

Caused by: java.sql.SQLException

Namun, semua panggilan metode di bawah itu adalah kode perpustakaan. Jadi kita akan beralih ke "Disebabkan oleh" di atasnya, dan mencari pemanggilan metode pertama yang berasal dari kode kita, yaitu:

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

Seperti pada contoh sebelumnya, kita harus melihat MyEntityService.javapada baris 59, karena dari situlah kesalahan ini berasal (yang ini agak jelas apa yang salah, karena SQLException menyatakan kesalahan, tetapi prosedur debugging adalah apa yang kita cari).

82 Dakkaron Oct 14 2015 at 17:40

Saya memposting jawaban ini sehingga jawaban teratas (bila diurutkan berdasarkan aktivitas) bukanlah jawaban yang salah.

Apa itu Stacktrace?

Stacktrace adalah alat debugging yang sangat membantu. Ini menunjukkan tumpukan panggilan (artinya, tumpukan fungsi yang dipanggil hingga titik itu) pada saat pengecualian yang tidak tertangkap dilemparkan (atau saat stacktrace dibuat secara manual). Ini sangat berguna karena tidak hanya menunjukkan di mana kesalahan terjadi, tetapi juga bagaimana program berakhir di tempat kode itu. Ini mengarah ke pertanyaan berikutnya:

Apa itu Pengecualian?

Pengecualian adalah apa yang digunakan lingkungan runtime untuk memberi tahu Anda bahwa terjadi kesalahan. Contoh populer adalah NullPointerException, IndexOutOfBoundsException, atau ArithmeticException. Masing-masing disebabkan ketika Anda mencoba melakukan sesuatu yang tidak mungkin dilakukan. Misalnya, NullPointerException akan muncul saat Anda mencoba mendereferensi objek Null:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

Bagaimana cara menangani Stacktraces / Pengecualian?

Pertama-tama, cari tahu apa yang menyebabkan Pengecualian tersebut. Coba googleing nama pengecualiannya untuk mencari tahu, apa penyebab dari pengecualian itu. Sebagian besar waktu itu akan disebabkan oleh kode yang salah. Dalam contoh yang diberikan di atas, semua pengecualian disebabkan oleh kode yang salah. Jadi untuk contoh NullPointerException Anda dapat memastikan bahwa atidak pernah nol pada saat itu. Anda dapat, misalnya, menginisialisasi aatau menyertakan tanda centang seperti ini:

if (a!=null) {
    a.toString();
}

Dengan cara ini, garis yang menyinggung tidak dieksekusi jika a==null. Hal yang sama berlaku untuk contoh lainnya.

Terkadang Anda tidak dapat memastikan bahwa Anda tidak mendapatkan pengecualian. Misalnya, jika Anda menggunakan koneksi jaringan dalam program Anda, Anda tidak dapat menghentikan komputer dari kehilangan koneksi internetnya (misalnya Anda tidak dapat menghentikan pengguna dari memutuskan koneksi jaringan komputer). Dalam hal ini perpustakaan jaringan mungkin akan mengeluarkan pengecualian. Sekarang Anda harus menangkap pengecualian dan menanganinya . Artinya, dalam contoh dengan koneksi jaringan, Anda harus mencoba membuka kembali koneksi atau memberi tahu pengguna atau sesuatu seperti itu. Selain itu, setiap kali Anda menggunakan catch, selalu tangkap hanya pengecualian yang ingin Anda tangkap, jangan gunakan pernyataan tangkapan luas seperticatch (Exception e) itu akan menangkap semua pengecualian. Ini sangat penting, karena jika tidak, Anda mungkin tidak sengaja menangkap pengecualian yang salah dan bereaksi dengan cara yang salah.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

Mengapa saya tidak boleh menggunakan catch (Exception e)?

Mari gunakan contoh kecil untuk menunjukkan mengapa Anda tidak harus menangkap semua pengecualian:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

Apa yang kode ini coba lakukan adalah menangkap yang ArithmeticExceptiondisebabkan oleh kemungkinan pembagian dengan 0. Tetapi kode ini juga menangkap kemungkinan NullPointerExceptionyang dilemparkan jika aatau bada null. Artinya, Anda mungkin mendapatkan NullPointerExceptiontetapi Anda akan memperlakukannya sebagai ArithmeticException dan mungkin melakukan hal yang salah. Dalam kasus terbaik, Anda masih melewatkan bahwa ada NullPointerException. Hal-hal seperti itu membuat proses debug menjadi jauh lebih sulit, jadi jangan lakukan itu.

TLDR

  1. Cari tahu apa penyebab pengecualian dan perbaiki, sehingga tidak ada pengecualian sama sekali.
  2. Jika 1. tidak memungkinkan, tangkap pengecualian spesifik dan tangani.

    • Jangan pernah hanya menambahkan coba / tangkap dan kemudian abaikan pengecualiannya! Jangan lakukan itu!
    • Jangan pernah gunakan catch (Exception e), selalu tangkap Pengecualian tertentu. Itu akan menghemat banyak sakit kepala.
21 Woot4Moo Oct 21 2010 at 22:05

Untuk menambah apa yang disebutkan Rob. Menetapkan break point dalam aplikasi Anda memungkinkan pemrosesan tumpukan langkah demi langkah. Hal ini memungkinkan pengembang untuk menggunakan debugger untuk melihat pada titik mana tepatnya metode melakukan sesuatu yang tidak terduga.

Karena Rob telah menggunakan NullPointerException(NPE) untuk mengilustrasikan sesuatu yang umum, kami dapat membantu menghilangkan masalah ini dengan cara berikut:

jika kita memiliki metode yang mengambil parameter seperti: void (String firstName)

Dalam kode kami, kami ingin mengevaluasi yang firstNameberisi nilai, kami akan melakukan ini seperti:if(firstName == null || firstName.equals("")) return;

Hal di atas mencegah kami menggunakan firstNamesebagai parameter yang tidak aman. Oleh karena itu dengan melakukan pemeriksaan null sebelum diproses, kami dapat membantu memastikan bahwa kode kami akan berjalan dengan baik. Untuk memperluas contoh yang menggunakan objek dengan metode, kita dapat melihat di sini:

if(dog == null || dog.firstName == null) return;

Di atas adalah urutan yang tepat untuk memeriksa nol, kita mulai dengan objek dasar, anjing dalam kasus ini, dan kemudian mulai berjalan menyusuri pohon kemungkinan untuk memastikan semuanya valid sebelum diproses. Jika pesanan dibalik, NPE berpotensi terlempar dan program kami akan macet.

15 przemekhertel Sep 17 2014 at 00:34

Ada satu lagi fitur stacktrace yang ditawarkan oleh keluarga Throwable - kemungkinan untuk memanipulasi informasi pelacakan tumpukan.

Perilaku standar:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Jejak tumpukan:

Exception in thread "main" java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.java:9)
    at test.stack.trace.SomeClass.main(SomeClass.java:27)

Jejak tumpukan yang dimanipulasi:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Jejak tumpukan:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)
15 KevinLi Jul 26 2016 at 09:24

Untuk memahami namanya : Jejak tumpukan adalah daftar Pengecualian (atau Anda dapat mengatakan daftar "Penyebab oleh"), dari Pengecualian yang paling permukaan (mis. Pengecualian Lapisan Layanan) hingga yang paling dalam (mis. Pengecualian Basis Data). Sama seperti alasan kami menyebutnya 'tumpukan' adalah karena tumpukan adalah First in Last out (FILO), pengecualian terdalam terjadi di awal, kemudian rangkaian pengecualian dihasilkan serangkaian konsekuensi, Exception permukaan adalah yang terakhir satu terjadi pada waktunya, tetapi kami melihatnya sejak awal.

Kunci 1 : Hal yang rumit dan penting di sini perlu dipahami adalah: penyebab terdalam mungkin bukan "akar penyebab", karena jika Anda menulis beberapa "kode buruk", ini dapat menyebabkan beberapa pengecualian di bawahnya yang lebih dalam dari lapisannya. Misalnya, query sql yang buruk dapat menyebabkan koneksi SQLServerException reset di bottem, bukan kesalahan syndax, yang mungkin hanya di tengah tumpukan.

-> Cari akar penyebab di tengah adalah pekerjaan Anda.

Kunci 2 : Hal lain yang rumit tetapi penting ada di dalam setiap blok "Penyebab oleh", baris pertama adalah lapisan terdalam dan berada di tempat pertama untuk blok ini. Contohnya,

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
           at com.example.myproject.Author.getBookTitles(Author.java:25)
               at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

Book.java:16 dipanggil oleh Auther.java:25 yang dipanggil oleh Bootstrap.java:14, Book.java:16 adalah penyebab utamanya. Di sini lampirkan diagram sortir tumpukan jejak dalam urutan kronologis.

8 EugeneS Apr 19 2016 at 10:43

Hanya untuk menambah contoh lainnya, ada kelas dalam (bersarang) yang muncul dengan $tanda. Sebagai contoh:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

Akan menghasilkan pelacakan tumpukan ini:

Exception in thread "main" java.lang.RuntimeException
        at Test.privateMethod(Test.java:4)
        at Test.access$000(Test.java:1)
        at Test$1.run(Test.java:10)
        at Test.main(Test.java:13)
6 rghome Mar 12 2015 at 16:34

Posting lain menjelaskan apa itu pelacakan tumpukan, tetapi masih sulit untuk dikerjakan.

Jika Anda mendapatkan pelacakan tumpukan dan ingin melacak penyebab pengecualian, titik awal yang baik untuk memahaminya adalah dengan menggunakan Java Stack Trace Console di Eclipse . Jika Anda menggunakan IDE lain, mungkin ada fitur serupa, tetapi jawaban ini tentang Eclipse.

Pertama, pastikan Anda memiliki semua sumber Java yang dapat diakses di proyek Eclipse.

Kemudian dalam perspektif Java , klik pada tab Console (biasanya di bagian bawah). Jika tampilan Console tidak terlihat, masuk ke menu opsi Window -> Show View dan pilih Console .

Kemudian di jendela konsol, klik tombol berikut (di sebelah kanan)

lalu pilih Java Stack Trace Console dari daftar drop-down.

Tempelkan jejak tumpukan Anda ke konsol. Ini kemudian akan memberikan daftar tautan ke kode sumber Anda dan kode sumber lain yang tersedia.

Inilah yang mungkin Anda lihat (gambar dari dokumentasi Eclipse):

Panggilan metode terbaru yang dilakukan akan menjadi yang teratas dari tumpukan, yang merupakan baris teratas (tidak termasuk teks pesan). Menurunkan tumpukan kembali ke masa lalu. Baris kedua adalah metode yang memanggil baris pertama, dll.

Jika Anda menggunakan perangkat lunak sumber terbuka, Anda mungkin perlu mengunduh dan melampirkan sumber proyek Anda jika ingin memeriksanya. Unduh jar sumber, dalam proyek Anda, buka folder Perpustakaan yang Dirujuk untuk menemukan tabung Anda untuk modul sumber terbuka Anda (yang memiliki file kelas) lalu klik kanan, pilih Properti dan lampirkan tabung sumber.