NullPointerException di Java tanpa StackTrace


333

Saya telah memiliki contoh kode Java kami menangkap NullPointerException, tetapi ketika saya mencoba untuk log StackTrace (yang pada dasarnya berakhir memanggil Throwable.printStackTrace()), yang saya dapatkan adalah:

java.lang.NullPointerException

Adakah yang menemukan ini? Saya mencoba googling untuk "java null pointer empty stack trace" tetapi tidak menemukan yang seperti ini.


Apa konteksnya? Apakah ada beberapa utas yang terlibat? Saya mengalami masalah saat mencoba melacak jejak pengecualian di SwingWorker.
Michael Myers

Tidak ada threading yang terlibat di sini, hanya Jawa tua biasa.
Edward Shtern

1
@ Bozho - nggak - belum yakin bagaimana cara mereproduksi NullPointer.
Edward Shtern


Info lebih lanjut tentang -XX:-OmitStackTraceInFastThrowdi dup: stackoverflow.com/questions/4659151/…
Vadzim

Jawaban:


407

Anda mungkin menggunakan JVM HotSpot (awalnya oleh Sun Microsystems, kemudian dibeli oleh Oracle, bagian dari OpenJDK), yang melakukan banyak optimasi. Untuk mendapatkan jejak stack kembali, Anda harus meneruskan opsi -XX:-OmitStackTraceInFastThrowke JVM.

Optimalisasi adalah ketika pengecualian (biasanya NullPointerException) terjadi untuk pertama kalinya, jejak tumpukan penuh dicetak dan JVM mengingat jejak tumpukan (atau mungkin hanya lokasi kode). Ketika pengecualian itu terjadi cukup sering, jejak tumpukan tidak dicetak lagi, baik untuk mencapai kinerja yang lebih baik dan tidak membanjiri log dengan jejak tumpukan yang identik.

Untuk melihat bagaimana ini diterapkan di HotSpot JVM, ambil salinannya dan cari variabel global OmitStackTraceInFastThrow. Terakhir kali saya melihat kode (pada 2019), itu ada di file graphKit.cpp .


1
Terima kasih atas tipnya. Adakah yang tahu jika ada gotchas tersembunyi untuk melewati opsi ini (sepertinya cukup berbahaya selama aplikasi saya tidak membuang banyak pengecualian)?
Edward Shtern

Tidak ada gotcha tersembunyi yang saya tahu. Ketika Anda melihat kode sumber Hotspot, Anda dapat melihat bahwa opsi ini hanya digunakan di satu tempat (graphKit.cpp). Dan itu terlihat baik bagi saya.
Roland Illig

34
Saya pikir saya akan menambahkan sedikit informasi tambahan bahwa ketika jejak stack dioptimalkan, itu karena telah ditangani sepenuhnya setidaknya satu kali: jawspeak.com/2010/05/26/…
sharakan

1
Saya menjalankan OpenJDK JVM, versi 1.8.0u171 (Debian 9), dan sepertinya menerima -XX:-OmitStackTraceInFastThrowflag juga. Saya belum mengkonfirmasi apakah itu sebabnya saya juga gagal mencetak tumpukan-jejak (misalnya, menggunakan e.printStackTrace), tetapi tampaknya sangat mungkin. Saya telah memperluas jawaban untuk mencerminkan penemuan ini.
Chris W.

Dalam kasus kami pertama 125 pengecualian memiliki jejak stack, dan kemudian sisanya di 3 rotasi file log tidak punya. Jawaban ini sangat membantu dalam menemukan pelakunya.
sukhmel

61

Seperti yang Anda sebutkan dalam komentar, Anda menggunakan log4j. Saya menemukan (secara tidak sengaja) tempat di mana saya telah menulis

LOG.error(exc);

bukannya tipikal

LOG.error("Some informative message", e);

melalui kemalasan atau mungkin hanya tidak memikirkannya. Bagian yang disayangkan dari ini adalah bahwa itu tidak berperilaku seperti yang Anda harapkan. API logger sebenarnya mengambil Object sebagai argumen pertama, bukan string - dan kemudian memanggil toString () pada argumen. Jadi alih-alih mendapatkan jejak tumpukan yang bagus, itu hanya mencetak toString - yang dalam kasus NPE sangat tidak berguna.

Mungkin ini yang Anda alami?


+1: Ini akan menjelaskan perilaku yang dijelaskan, dan Anda bukan satu-satunya yang menemukan ini :)
Peter Lang

4
Kami sebenarnya memiliki kebijakan standar untuk tidak pernah menggunakan formulir pertama di atas (LOG.error (exc);) - kami selalu menggunakan tanda tangan 2 parameter sehingga kami menambahkan beberapa pernyataan deskriptif ke log, bukan hanya stacktrace mentah.
Edward Shtern

5
Tentu, tetapi kebijakan tidak berarti selalu dijalankan dengan benar! Kupikir itu layak disebut, setidaknya.
Steven Schlansker

Benar, tetapi dalam kasus ini ;-)
Edward Shtern

28

Kami telah melihat perilaku yang sama di masa lalu. Ternyata, untuk beberapa alasan gila, jika NullPointerException terjadi di tempat yang sama dalam kode beberapa kali, setelah beberapa saat menggunakan Log.error(String, Throwable)akan berhenti termasuk jejak tumpukan penuh.

Coba cari lebih jauh ke belakang di log Anda. Anda mungkin menemukan pelakunya.

EDIT: bug ini kedengarannya relevan, tetapi sudah diperbaiki sejak lama mungkin itu bukan penyebabnya.


2
Bug telah ditutup, tetapi flag -XX: -OmitStackTraceInFastThrow masih diperlukan untuk menyelesaikan masalah optimasi kinerja.
Joshua Goldberg

Saya sudah sering melihat ini. Adakah petunjuk tentang apa yang menyebabkan ini, atau bagaimana cara memperbaikinya? Sistem penebangan mungkin telah berlangsung selama berhari-hari, dan penyebab sebenarnya diputar, tidak pernah menghiraukan pencarian yang membosankan ...
Pawel Veselov

5
Pawel, sudahkah Anda mencoba -XX:-OmitStackTraceInFastThrowbendera JVM yang disarankan oleh Joshua? Lihat juga stackoverflow.com/a/2070568/6198 .
Matt Solnit

1
Ini untuk kita. Terima kasih.
Andrew Cheong

20

Berikut ini penjelasannya: Hotspot menyebabkan pengecualian untuk kehilangan jejak tumpukan mereka dalam produksi - dan perbaikannya

Saya sudah mengujinya di Mac OS X

  • versi java "1.6.0_26"
  • Java (TM) SE Runtime Environment (build 1.6.0_26-b03-383-11A511)
  • Java HotSpot (TM) 64-Bit Server VM (build 20.1-b02-383, mode campuran)

    Object string = "abcd";
    int i = 0;
    while (i < 12289) {
        i++;
        try {
            Integer a = (Integer) string;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Untuk fragmen kode khusus ini, 12288 iterasi (frekuensi +?) Tampaknya menjadi batas di mana JVM telah memutuskan untuk menggunakan pengecualian yang telah dialokasikan sebelumnya ...


10

exception.toString tidak memberi Anda StackTrace, itu hanya mengembalikan

deskripsi singkat tentang lemparan ini. Hasilnya adalah gabungan dari:

* the name of the class of this object
* ": " (a colon and a space)
* the result of invoking this object's getLocalizedMessage() method

Gunakan exception.printStackTracesebaliknya untuk menampilkan StackTrace.


Maaf, saya salah mengeja di posting asli saya. Saya masuk ini melalui Log4J, yang tidak menggunakan printStackTrace ().
Edward Shtern

1
Sudahkah Anda mencoba menggunakan getStackTrace()untuk memastikan masalahnya bukan pada logger Anda?
Peter Lang

1
Jika Anda menggunakan log4j, pastikan untuk mengirim pengecualian sebagai bagian dari argumen ke metode log. Saya akan mengirim jawaban dengan itu.
Ravi Wallau

@raviaw titik valid! @ Edward Shtern: dapatkah Anda mengonfirmasi bahwa Anda pasti menggunakan bentuk 2-arg dari metode log4j? Saya tahu Anda menyebutkan dalam jawaban lebih jauh di bawah bahwa itu adalah kebijakan perusahaan untuk melakukannya, tetapi apakah Anda BENAR-BENAR yakin bahwa dalam hal ini Anda mengikuti kebijakan tersebut?
KarstenF

Ini mungkin merupakan pukulan panjang, tetapi apakah mungkin pengecualian tersebut berasal dari beberapa kode pihak ke-3? Mungkin itu adalah pembungkus pengecualian (yang ditulis dengan buruk), yang toString () hanya mengembalikan nama kelas pengecualian yang dibungkus, dan yang gagal memberikan jejak tumpukan yang mendasarinya. Coba masukkan sesuatu seperti logger.info ("Exception class =" + exc.class.getCanonicalName ()) ke dalam blok tangkapan Anda dan lihat apa yang Anda dapatkan.
KarstenF

4

Saran alternatif - jika Anda menggunakan Eclipse, Anda bisa menetapkan breakpoint pada NullPointerException itu sendiri (dalam perspektif Debug, buka tab "Breakpoints" dan klik pada ikon kecil yang memiliki! Di dalamnya)

Periksa opsi "tertangkap" dan "tidak tertangkap" - sekarang ketika Anda memicu NPE, Anda akan segera menerobos dan Anda kemudian dapat melangkah dan melihat bagaimana tepatnya ditangani dan mengapa Anda tidak mendapatkan jejak tumpukan.


1

toString()hanya mengembalikan nama pengecualian dan pesan opsional. Saya sarankan menelepon

exception.printStackTrace()

untuk membuang pesan, atau jika Anda memerlukan detail berdarah:

 StackTraceElement[] trace = exception.getStackTrace()

Lihat di atas - Saya salah bicara - Saya menggunakan printStackTrace ().
Edward Shtern

1

(Pertanyaan Anda masih belum jelas tentang apakah kode Anda memanggil printStackTrace() atau ini sedang dilakukan oleh penangan log.)

Berikut adalah beberapa penjelasan yang mungkin tentang apa yang mungkin terjadi:

  • Logger / handler yang digunakan telah dikonfigurasi untuk hanya menampilkan string pesan pengecualian, bukan jejak stack penuh.

  • Aplikasi Anda (atau pustaka pihak ketiga) sedang mencatat pengecualian menggunakan LOG.error(ex);daripada bentuk 2-argumen dari (misalnya) metode log4j Logger.

  • Pesan tersebut datang dari tempat yang berbeda dari tempat Anda pikir; misalnya itu sebenarnya datang beberapa metode perpustakaan pihak ketiga, atau beberapa hal acak yang tersisa dari upaya sebelumnya untuk debug.

  • Pengecualian yang sedang dicatat telah membebani beberapa metode untuk mengaburkan stacktrace. Jika demikian, pengecualian tidak akan menjadi NullPointerException asli, tetapi akan menjadi subtipe khusus NPE atau bahkan beberapa pengecualian yang tidak terhubung.

Saya pikir penjelasan terakhir yang mungkin sangat tidak mungkin, tetapi orang setidaknya berpikir untuk melakukan hal semacam ini untuk "mencegah" rekayasa terbalik. Tentu saja itu hanya berhasil membuat hidup menjadi sulit bagi pengembang yang jujur.


1

Saat Anda menggunakan AspectJ dalam proyek Anda, mungkin saja beberapa aspek menyembunyikan bagiannya dari jejak tumpukan. Misalnya, hari ini saya punya:

java.lang.NullPointerException:
  at com.company.product.MyTest.test(MyTest.java:37)

Jejak tumpukan ini dicetak saat menjalankan tes melalui Maven's surefire.

Di sisi lain, ketika menjalankan tes di IntelliJ, jejak tumpukan yang berbeda dicetak:

java.lang.NullPointerException
  at com.company.product.library.ArgumentChecker.nonNull(ArgumentChecker.java:67)
  at ...
  at com.company.product.aspects.CheckArgumentsAspect.wrap(CheckArgumentsAspect.java:82)
  at ...
  at com.company.product.MyTest.test(MyTest.java:37)

0

Ini akan menghasilkan Pengecualian, gunakan hanya untuk debug Anda harus menangani pengecualian Anda lebih baik.

import java.io.PrintWriter;
import java.io.StringWriter;
    public static String getStackTrace(Throwable t)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.