Quale parte del lancio di un'eccezione è costosa?


256

In Java, usare il comando throw / catch come parte della logica quando in realtà non c'è un errore è generalmente una cattiva idea (in parte) perché lanciare e catturare un'eccezione è costoso e farlo molte volte in un ciclo è di solito molto più lento di altri strutture di controllo che non comportano eccezioni al lancio.

La mia domanda è: è il costo sostenuto nel lancio / cattura stesso o quando si crea l'oggetto Exception (poiché riceve molte informazioni di runtime incluso lo stack di esecuzione)?

In altre parole, se lo faccio

Exception e = new Exception();

ma non lanciarlo, è la maggior parte del costo del lancio, oppure il tiro + cattura è costoso?

Non sto chiedendo se inserire il codice in un blocco try / catch si aggiunge al costo dell'esecuzione di quel codice, sto chiedendo se catturare l'eccezione sia la parte costosa o creare (chiamare il costruttore per) l'eccezione è la parte costosa .

Un altro modo di chiedere questo è, se ho creato un'istanza di Exception e la ho lanciata e catturata più volte, sarebbe significativamente più veloce della creazione di una nuova eccezione ogni volta che lancio?


20
Credo che stia compilando e popolando la traccia dello stack.
Elliott Frisch,


"se ho creato un'istanza di Exception e l'ho lanciata e catturata più e più volte", quando viene creata l'eccezione viene riempita la sua stack stack, il che significa che sarà sempre la stessa stactrace indipendentemente dal luogo da cui è stata lanciata. Se stacktrace non è importante per te di quanto potresti provare la tua idea ma questo potrebbe rendere il debug molto difficile se non impossibile in alcuni casi.
Pshemo,

2
@Pshemo Non ho intenzione di farlo davvero nel codice, sto chiedendo delle prestazioni e usando questa assurdità come esempio in cui potrebbe fare la differenza.
Martin Carney,

@MartinCarney Ho aggiunto una risposta al tuo ultimo paragrafo, vale a dire che la memorizzazione nella cache di un'eccezione comporterebbe un miglioramento delle prestazioni. Se è utile posso aggiungere il codice, in caso contrario posso eliminare la risposta.
Harry

Risposte:


267

La creazione di un oggetto eccezione non è più costosa della creazione di altri oggetti regolari. Il costo principale è nascosto nel fillInStackTracemetodo nativo che attraversa lo stack di chiamate e raccoglie tutte le informazioni necessarie per creare una traccia dello stack: classi, nomi dei metodi, numeri di riga ecc.

Il mito degli alti costi di eccezione deriva dal fatto che la maggior parte dei Throwablecostruttori chiama implicitamente fillInStackTrace. Tuttavia, esiste un costruttore per creare una Throwabletraccia senza stack. Ti consente di creare lanciatori che sono molto veloci da istanziare. Un altro modo per creare eccezioni leggere è la sostituzione fillInStackTrace.


Che ne dici di lanciare un'eccezione?
In realtà, dipende da dove viene rilevata un'eccezione generata .

Se viene catturato nello stesso metodo (o, più precisamente, nello stesso contesto, dal momento che il contesto può includere diversi metodi a causa dell'inclinazione), throwè veloce e semplice come goto(ovviamente, dopo la compilazione JIT).

Tuttavia, se un catchblocco è da qualche parte più profondo nello stack, allora JVM deve sciogliere i frame dello stack e questo può richiedere molto più tempo. Ci vuole ancora più tempo, se ci sono synchronizedblocchi o metodi coinvolti, poiché svolgersi implica il rilascio di monitor di proprietà di frame stack rimossi.


Ho potuto confermare le affermazioni di cui sopra con parametri di riferimento adeguati, ma per fortuna non ho bisogno di farlo, dal momento che tutti gli aspetti sono già perfettamente trattati nel post dell'ingegnere di performance di HotSpot Alexey Shipilev: The Exceptional Performance of Lil 'Exception .


8
Come osservato nell'articolo e qui accennato, il risultato è che il costo del lancio / cattura delle eccezioni dipende fortemente dalla profondità delle chiamate. Il punto qui è che l'affermazione "le eccezioni sono costose" non è proprio corretta. Un'affermazione più corretta è che le eccezioni "possono" essere costose. Onestamente, penso che dire solo l'uso delle eccezioni per "casi veramente eccezionali" (come nell'articolo) sia troppo fortemente formulato. Sono perfetti per qualsiasi cosa al di fuori del normale flusso di ritorno ed è difficile rilevare l'impatto delle prestazioni di usarli in questo modo in un'applicazione reale.
JimmyJames

14
Potrebbe valerne la pena quantificare il sovraccarico di eccezioni. Anche nel caso peggiore riportato in questo articolo piuttosto esauriente (lanciare e catturare un'eccezione dinamica con uno stack stack effettivamente interrogato, 1000 frame di profondità in profondità), impiega 80 micro secondi. Ciò può essere significativo se il sistema deve elaborare migliaia di eccezioni al secondo, ma per il resto non vale la pena preoccuparsi. E questo è il caso peggiore; se le tue stacktrace sono un po 'più sanitarie o non esegui query su stacktrace, possiamo elaborare quasi un milione di eccezioni al secondo.
meriton

13
Sottolineo questo perché molte persone, leggendo che le eccezioni sono "costose", non si fermano mai a chiedere "costose rispetto a cosa", ma presumono che siano "parte costosa del loro programma", cosa che raramente lo sono.
meriton

2
C'è una parte che non è menzionata qui: il costo potenziale nel prevenire l'applicazione delle ottimizzazioni. Un esempio estremo sarebbe la JVM che non si schiera per evitare di "confondere" le tracce dello stack, ma ho visto dei (micro) parametri di riferimento in cui la presenza o l'assenza di eccezioni avrebbe apportato o interrotto le ottimizzazioni in C ++ prima.
Matthieu M.

3
@MatthieuM. Eccezioni e blocchi try / catch non impediscono l'allineamento di JVM. Per i metodi compilati, le tracce di stack reali vengono ricostruite dalla tabella di frame dello stack virtuale memorizzata come metadati. Non riesco a ricordare un'ottimizzazione JIT incompatibile con try / catch. La struttura Try / catch in sé non aggiunge nulla al codice del metodo, esiste solo come tabella delle eccezioni a parte il codice.
apangin

72

La prima operazione nella maggior parte dei Throwablecostruttori è quella di compilare la traccia dello stack, che è dove si trova la maggior parte delle spese.

Esiste, tuttavia, un costruttore protetto con un flag per disabilitare la traccia dello stack. Questo costruttore è accessibile anche durante l'estensione Exception. Se si crea un tipo di eccezione personalizzato, è possibile evitare la creazione della traccia dello stack e ottenere prestazioni migliori a scapito di meno informazioni.

Se si crea una singola eccezione di qualsiasi tipo in modo normale, è possibile ripeterla più volte senza il sovraccarico di compilare la traccia dello stack. Tuttavia, la sua traccia dello stack rifletterà dove è stata costruita, non dove è stata lanciata in una particolare istanza.

Le versioni attuali di Java fanno alcuni tentativi per ottimizzare la creazione della traccia dello stack. Il codice nativo viene richiamato per compilare la traccia dello stack, che registra la traccia in una struttura nativa più leggera. Corrispondenti Java StackTraceElementoggetti sono pigramente creati da questo record solo quando i getStackTrace(), printStackTrace()o altri metodi che richiedono la traccia sono chiamati.

Se si elimina la generazione della traccia dello stack, l'altro costo principale sta svolgendo lo stack tra il lancio e il fermo. Meno frame intermedi si incontrano prima che venga rilevata l'eccezione, più veloce sarà.

Progetta il tuo programma in modo tale da generare eccezioni solo in casi davvero eccezionali e ottimizzazioni come queste sono difficili da giustificare.



25

C'è una buona scrittura su Eccezioni qui.

http://shipilev.net/blog/2014/exceptional-performance/

La conclusione è che la costruzione della traccia dello stack e lo svolgimento dello stack sono le parti costose. Il codice seguente sfrutta una funzionalità in 1.7cui è possibile attivare e disattivare le tracce dello stack. Possiamo quindi utilizzarlo per vedere che tipo di costi hanno diversi scenari

Di seguito sono riportati i tempi per la sola creazione dell'oggetto. Ho aggiunto Stringqui in modo da poter vedere che senza lo stack scritto non c'è quasi alcuna differenza nella creazione di un JavaExceptionoggetto e un String. Con la scrittura in pila attivata, la differenza è drammatica, cioè almeno un ordine di grandezza più lento.

Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with    stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)

Quanto segue mostra quanto tempo ci è voluto per tornare da un tiro ad una profondità particolare un milione di volte.

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|           1428|             243| 588 (%)|
|   15|           1763|             393| 449 (%)|
|   14|           1746|             390| 448 (%)|
|   13|           1703|             384| 443 (%)|
|   12|           1697|             391| 434 (%)|
|   11|           1707|             410| 416 (%)|
|   10|           1226|             197| 622 (%)|
|    9|           1242|             206| 603 (%)|
|    8|           1251|             207| 604 (%)|
|    7|           1213|             208| 583 (%)|
|    6|           1164|             206| 565 (%)|
|    5|           1134|             205| 553 (%)|
|    4|           1106|             203| 545 (%)|
|    3|           1043|             192| 543 (%)| 

Quanto segue è quasi certamente una semplificazione eccessiva ...

Se prendiamo una profondità di 16 con la scrittura dello stack attiva, la creazione degli oggetti impiega circa il 40% circa del tempo, la traccia dello stack reale rappresenta la stragrande maggioranza di ciò. ~ 93% di un'istanza dell'oggetto JavaException è dovuto alla traccia dello stack. Ciò significa che in questo caso lo svolgersi dello stack richiede l'altro 50% delle volte.

Quando disattiviamo lo stack trace, la creazione di oggetti rappresenta una frazione molto più piccola, ovvero il 20%, e lo stack svolgendo ora rappresenta l'80% delle volte.

In entrambi i casi, lo svolgimento della pila richiede una grande parte del tempo complessivo.

public class JavaException extends Exception {
  JavaException(String reason, int mode) {
    super(reason, null, false, false);
  }
  JavaException(String reason) {
    super(reason);
  }

  public static void main(String[] args) {
    int iterations = 1000000;
    long create_time_with    = 0;
    long create_time_without = 0;
    long create_string = 0;
    for (int i = 0; i < iterations; i++) {
      long start = System.nanoTime();
      JavaException jex = new JavaException("testing");
      long stop  =  System.nanoTime();
      create_time_with += stop - start;

      start = System.nanoTime();
      JavaException jex2 = new JavaException("testing", 1);
      stop = System.nanoTime();
      create_time_without += stop - start;

      start = System.nanoTime();
      String str = new String("testing");
      stop = System.nanoTime();
      create_string += stop - start;

    }
    double interval_with    = ((double)create_time_with)/1000000;
    double interval_without = ((double)create_time_without)/1000000;
    double interval_string  = ((double)create_string)/1000000;

    System.out.printf("Time to create %d String objects: %.2f (ms)\n", iterations, interval_string);
    System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)\n", iterations, interval_with);
    System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", iterations, interval_without);

    JavaException jex = new JavaException("testing");
    int depth = 14;
    int i = depth;
    double[] with_stack    = new double[20];
    double[] without_stack = new double[20];

    for(; i > 0 ; --i) {
      without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
      with_stack[i]    = jex.timerLoop(i, iterations, 1)/1000000;
    }
    i = depth;
    System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|\n");
    for(; i > 0 ; --i) {
      double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
      System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
      //System.out.printf("%d\t%.2f (ms)\n", i, ratio);
    }
  }
 private int thrower(int i, int mode) throws JavaException {
    ExArg.time_start[i] = System.nanoTime();
    if(mode == 0) { throw new JavaException("without stack", 1); }
    throw new JavaException("with stack");
  }
  private int catcher1(int i, int mode) throws JavaException{
    return this.stack_of_calls(i, mode);
  }
  private long timerLoop(int depth, int iterations, int mode) {
    for (int i = 0; i < iterations; i++) {
      try {
        this.catcher1(depth, mode);
      } catch (JavaException e) {
        ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
      }
    }
    //long stop = System.nanoTime();
    return ExArg.time_accum[depth];
  }

  private int bad_method14(int i, int mode) throws JavaException  {
    if(i > 0) { this.thrower(i, mode); }
    return i;
  }
  private int bad_method13(int i, int mode) throws JavaException  {
    if(i == 13) { this.thrower(i, mode); }
    return bad_method14(i,mode);
  }
  private int bad_method12(int i, int mode) throws JavaException{
    if(i == 12) { this.thrower(i, mode); }
    return bad_method13(i,mode);
  }
  private int bad_method11(int i, int mode) throws JavaException{
    if(i == 11) { this.thrower(i, mode); }
    return bad_method12(i,mode);
  }
  private int bad_method10(int i, int mode) throws JavaException{
    if(i == 10) { this.thrower(i, mode); }
    return bad_method11(i,mode);
  }
  private int bad_method9(int i, int mode) throws JavaException{
    if(i == 9) { this.thrower(i, mode); }
    return bad_method10(i,mode);
  }
  private int bad_method8(int i, int mode) throws JavaException{
    if(i == 8) { this.thrower(i, mode); }
    return bad_method9(i,mode);
  }
  private int bad_method7(int i, int mode) throws JavaException{
    if(i == 7) { this.thrower(i, mode); }
    return bad_method8(i,mode);
  }
  private int bad_method6(int i, int mode) throws JavaException{
    if(i == 6) { this.thrower(i, mode); }
    return bad_method7(i,mode);
  }
  private int bad_method5(int i, int mode) throws JavaException{
    if(i == 5) { this.thrower(i, mode); }
    return bad_method6(i,mode);
  }
  private int bad_method4(int i, int mode) throws JavaException{
    if(i == 4) { this.thrower(i, mode); }
    return bad_method5(i,mode);
  }
  protected int bad_method3(int i, int mode) throws JavaException{
    if(i == 3) { this.thrower(i, mode); }
    return bad_method4(i,mode);
  }
  private int bad_method2(int i, int mode) throws JavaException{
    if(i == 2) { this.thrower(i, mode); }
    return bad_method3(i,mode);
  }
  private int bad_method1(int i, int mode) throws JavaException{
    if(i == 1) { this.thrower(i, mode); }
    return bad_method2(i,mode);
  }
  private int stack_of_calls(int i, int mode) throws JavaException{
    if(i == 0) { this.thrower(i, mode); }
    return bad_method1(i,mode);
  }
}

class ExArg {
  public static long[] time_start;
  public static long[] time_accum;
  static {
     time_start = new long[20];
     time_accum = new long[20];
  };
}

I frame dello stack in questo esempio sono minuscoli rispetto a ciò che normalmente si trova.

Puoi dare un'occhiata al bytecode usando javap

javap -c -v -constants JavaException.class

cioè questo è per il metodo 4 ...

   protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
  stack=3, locals=3, args_size=3
     0: iload_1       
     1: iconst_3      
     2: if_icmpne     12
     5: aload_0       
     6: iload_1       
     7: iload_2       
     8: invokespecial #6                  // Method thrower:(II)I
    11: pop           
    12: aload_0       
    13: iload_1       
    14: iload_2       
    15: invokespecial #17                 // Method bad_method4:(II)I
    18: ireturn       
  LineNumberTable:
    line 63: 0
    line 64: 12
  StackMapTable: number_of_entries = 1
       frame_type = 12 /* same */

Exceptions:
  throws JavaException

13

La creazione del Exceptioncon una nulltraccia dello stack è di circa il tempo che l' throwe try-catchblocco insieme. Tuttavia, riempire la traccia dello stack richiede in media 5 volte più a lungo .

Ho creato il seguente benchmark per dimostrare l'impatto sulle prestazioni. Ho aggiunto il -Djava.compiler=NONEcomando Esegui configurazione per disabilitare l'ottimizzazione del compilatore. Per misurare l'impatto della creazione della traccia dello stack, ho esteso la Exceptionclasse per sfruttare il costruttore senza stack:

class NoStackException extends Exception{
    public NoStackException() {
        super("",null,false,false);
    }
}

Il codice di riferimento è il seguente:

public class ExceptionBenchmark {

    private static final int NUM_TRIES = 100000;

    public static void main(String[] args) {

        long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0;

        for (int i = 0; i < 30; i++) {
            throwCatchTime += throwCatchLoop();
            newExceptionTime += newExceptionLoop();
            newObjectTime += newObjectLoop();
            noStackExceptionTime += newNoStackExceptionLoop();
        }

        System.out.println("throwCatchTime = " + throwCatchTime / 30);
        System.out.println("newExceptionTime = " + newExceptionTime / 30);
        System.out.println("newStringTime = " + newObjectTime / 30);
        System.out.println("noStackExceptionTime = " + noStackExceptionTime / 30);

    }

    private static long throwCatchLoop() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {

                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newObjectLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new Object();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newNoStackExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            NoStackException e = new NoStackException();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

}

Produzione:

throwCatchTime = 19
newExceptionTime = 77
newObjectTime = 3
noStackExceptionTime = 15

Ciò implica che la creazione di a NoStackExceptionè approssimativamente costosa rispetto al lancio ripetuto dello stesso Exception. Mostra anche che la creazione Exceptione il riempimento della traccia dello stack richiede circa 4x in più.


1
Potresti aggiungere un altro caso in cui crei un'istanza di eccezione prima dell'ora di inizio, quindi lanciare + catturarla ripetutamente in un ciclo? Ciò mostrerebbe il costo del solo lancio + cattura.
Martin Carney,

@MartinCarney Ottimo suggerimento! Ho aggiornato la mia risposta per fare proprio questo.
Austin D

Ho apportato alcune modifiche al tuo codice di test e sembra che il compilatore stia eseguendo alcune ottimizzazioni che ci impediscono di ottenere numeri precisi.
Martin Carney,

@MartinCarney Ho aggiornato la risposta all'ottimizzazione del compilatore di sconti
Austin D

Cordiali saluti, dovresti probabilmente leggere le risposte a Come posso scrivere un micro-benchmark corretto in Java? Suggerimento: non è così.
Daniel Pryden,

4

Questa parte della domanda ...

Un altro modo di chiedere questo è, se ho creato un'istanza di Exception e la ho lanciata e catturata più volte, sarebbe significativamente più veloce della creazione di una nuova eccezione ogni volta che lancio?

Sembra chiedere se la creazione di un'eccezione e la memorizzazione nella cache da qualche parte migliora le prestazioni. Sì lo fa. È come disattivare lo stack in fase di scrittura sulla creazione dell'oggetto perché è già stato fatto.

Questi sono i tempi che ho ricevuto, per favore leggi l'avvertenza dopo questo ...

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|            193|             251| 77 (%)| 
|   15|            390|             406| 96 (%)| 
|   14|            394|             401| 98 (%)| 
|   13|            381|             385| 99 (%)| 
|   12|            387|             370| 105 (%)| 
|   11|            368|             376| 98 (%)| 
|   10|            188|             192| 98 (%)| 
|    9|            193|             195| 99 (%)| 
|    8|            200|             188| 106 (%)| 
|    7|            187|             184| 102 (%)| 
|    6|            196|             200| 98 (%)| 
|    5|            197|             193| 102 (%)| 
|    4|            198|             190| 104 (%)| 
|    3|            193|             183| 105 (%)| 

Ovviamente il problema è che la traccia dello stack ora punta a dove hai istanziato l'oggetto e non da dove è stato lanciato.


3

Usando la risposta di @ AustinD come punto di partenza, ho apportato alcune modifiche. Codice in fondo.

Oltre ad aggiungere il caso in cui viene generata ripetutamente un'istanza di eccezione, ho anche disattivato l'ottimizzazione del compilatore in modo da poter ottenere risultati prestazionali accurati. Ho aggiunto -Djava.compiler=NONEagli argomenti VM, come da questa risposta . (In eclipse, modifica Esegui configurazione → Argomenti per impostare questo argomento VM)

I risultati:

new Exception + throw/catch = 643.5
new Exception only          = 510.7
throw/catch only            = 115.2
new String (benchmark)      = 669.8

Quindi la creazione dell'eccezione costa circa 5 volte tanto quanto lanciarla + catturarla. Supponendo che il compilatore non ottimizzi gran parte del costo.

Per fare un confronto, ecco lo stesso test eseguito senza disabilitare l'ottimizzazione:

new Exception + throw/catch = 382.6
new Exception only          = 379.5
throw/catch only            = 0.3
new String (benchmark)      = 15.6

Codice:

public class ExceptionPerformanceTest {

    private static final int NUM_TRIES = 1000000;

    public static void main(String[] args) {

        double numIterations = 10;

        long exceptionPlusCatchTime = 0, excepTime = 0, strTime = 0, throwTime = 0;

        for (int i = 0; i < numIterations; i++) {
            exceptionPlusCatchTime += exceptionPlusCatchBlock();
            excepTime += createException();
            throwTime += catchBlock();
            strTime += createString();
        }

        System.out.println("new Exception + throw/catch = " + exceptionPlusCatchTime / numIterations);
        System.out.println("new Exception only          = " + excepTime / numIterations);
        System.out.println("throw/catch only            = " + throwTime / numIterations);
        System.out.println("new String (benchmark)      = " + strTime / numIterations);

    }

    private static long exceptionPlusCatchBlock() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw new Exception();
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createException() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createString() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new String("" + i);
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long catchBlock() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }
}

Disabilitazione dell'ottimizzazione = ottima tecnica! Modificherò la mia risposta originale per non fuorviare nessuno
Austin D

3
Disabilitare l'ottimizzazione non è affatto meglio che scrivere un benchmark errato, poiché la modalità interpretata pura non ha nulla a che fare con le prestazioni del mondo reale. Il potere di JVM è il compilatore JIT, quindi qual è il punto di misurare qualcosa che non riflette il funzionamento dell'applicazione reale?
aprile

2
Ci sono molti più aspetti nel creare, lanciare e catturare eccezioni rispetto a quelli convertiti in questo "benchmark". Ti consiglio vivamente di leggere questo post .
aprile
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.