java - listas - tipos de estructura de datos pilas
Entendiendo la pila de Java (7)
Además, los objetos de tipo Error
no son Exceptions
, sino que representan condiciones excepcionales. Errors
representan situaciones inusuales que no son causadas por errores de programa, generalmente no ocurren durante la ejecución del programa, como JVM sin memoria. Aunque comparten una superclase común Throwable
, lo que significa que ambos pueden lanzarse, puede colocarse en una catch
pero en general no se debe capturar, ya que representan condiciones excepcionales y difíciles de manejar.
Hay este código:
public class Main {
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
doAnything();
} catch (final Error e) {
System.out.print("y");
}
}
}
Y ahí está la salida:
1yyyyyyyy2
¿Por qué imprime "y" ocho veces y no más? ¿Cómo puede Java llamar a println()
cuando se encuentra StackOverflowError
?
Aquí está detectando el Error
y no la Exception
en cuyo caso su programa se habría bloqueado.
Si intentas este código (modificado para agregar un contador estático)
public class StackError {
static int i = 1;
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
i++;
// System.out.println(i);
doAnything();
} catch (Error e) {
System.out.print("y"+i+"-");
}
}
}
Salida
1y6869-2
Por lo tanto, tiene stackerror
6869 veces (cambios para diferentes ejecuciones) y se imprime el último valor. Si solo imprime la letra y
como lo hizo anteriormente, es posible que la salida se búdele y no se vacíe, ya que no se println
.
Actualizar
System.out.println
llama internamente a PrintStream
que está en búfer. No pierdes ningún dato del búfer, se escribe todo en la salida (terminal en tu caso) después de que se llena, o cuando llamas explícitamente a flush en él.
Volviendo a este escenario, depende de la dinámica interna de cuánto se llena la pila y cuántas sentencias de impresión se pudieron ejecutar desde la captura en doAnything()
y la cantidad de caracteres que se escribieron en el búfer. En el reverso principal se imprime finalmente con el número 2
.
Desbordamiento de pila.
Solo está imprimiendo en la excepción, mientras tanto el programa recurre en desbordamiento.
En qué punto esto ocurre depende de sistemas individuales, memoria, etc.
¿Cuál es el propósito del programa?
La primera vez que se produce el Error
, la llamada al último doAnything()
se cancela y el control se devuelve al bloque catch desde el último doAnything()
.
Sin embargo, debido a que la pila aún está prácticamente llena, el simple hecho de llamar a System.out.print("y")
causará otro Error
debido a la necesidad de ingresar algún valor en la pila y luego realizar una llamada a la función de print()
.
Por lo tanto, otro Error
vuelve a ocurrir y la devolución ahora se devuelve en el bloque catch {} del doAnything()
anterior doAnything()
; donde sucederá otro Error
porque la necesidad de espacio de pila requerido para hacer una sola llamada a System.out.println("y")
es mayor que la cantidad de espacio liberado de devolver una llamada desde doAnything()
.
Solo cuando haya suficiente espacio en la pila para ejecutar una llamada a System.out.print("y")
este proceso se detendrá y se completará con éxito un bloque catch. Podemos verlo ejecutando el siguiente fragmento de código:
public class Principal3b {
static int a = 0;
static int i = 0;
static int j = 0;
public static void main(String[] args) {
System.out.println("X");
doAnything();
System.out.println("Y");
System.out.println(i);
System.out.println(j);
}
private static void doAnything() {
a++;
int b = a;
try {
doAnything();
} catch (final Error e) {
i++;
System.out.println(a);
System.out.println(b);
j++;
}
}
}
Observe que se utiliza println(a)
lugar de print(a)
; por lo tanto, debe imprimirse una nueva línea después de cada valor de a
si todo funciona correctamente.
Sin embargo, cuando lo ejecuto, obtengo el siguiente resultado:
X
62066206620662066206620662066206
6190
Y
17
1
Esto significa que ha habido 17 intentos para ejecutar el bloque catch. De estas ejecuciones de bloque de captura, 9 no pueden imprimir nada antes de generarse un Error; 7 pueden imprimir el valor de 6190, pero no pueden imprimir una nueva línea después de que ellos mismos vuelvan a cometer un error y, finalmente, hay uno que puede imprimir el valor de 6190 y la nueva línea después de él; por lo tanto, finalmente permite que su bloque catch se complete sin ningún nuevo Error y regrese con elegancia en la pila de llamadas.
Como estamos tratando con Error, estos números son solo un ejemplo y variarán enormemente no solo entre máquinas sino también entre ejecuciones y el simple hecho de agregar o eliminar cualquier tipo de instrucciones también debería cambiar estos valores. Sin embargo, el patrón visto aquí debe seguir siendo el mismo.
Mi apuesta es que al invocar la print
en el bloque catch, Error
otro Error
que es atrapado por el bloque externo. Algunas de estas llamadas no tendrán suficiente pila para escribir realmente la secuencia de salida.
El JLS dice que:
Tenga en cuenta que Error puede lanzarse de forma sincrónica por invocación de método, así como de forma asíncrona debido a la ejecución del método nativo o las limitaciones de recursos de la máquina virtual Java.
La plataforma Java SE permite que ocurra una cantidad pequeña pero limitada de ejecución antes de que se lance una excepción asíncrona.
Se permite el retraso señalado anteriormente para permitir que el código optimizado detecte y lance estas excepciones en los puntos donde sea práctico manejarlas mientras se obedece la semántica del lenguaje de programación Java. Una implementación simple podría sondear las excepciones asíncronas en el punto de cada instrucción de transferencia de control. Como un programa tiene un tamaño finito, esto proporciona un límite en el retraso total en la detección de una excepción asíncrona.
Pues el no. de veces el error de desbordamiento de pila se golpea es indefinido. Sin embargo, la JVM le permite recuperarse del error Error
y continuar la ejecución del sistema normalmente.
Está probado por el siguiente código:
public class Really {
public static void main(final String[] args) throws Exception {
System.out.print("1");
doAnything();
System.out.println("2");
}
private static void doAnything() {
try {
throw new Error();
//doAnything();
}
catch(final Error e){
System.out.print("y");
}
}
}
Sin embargo, tenga en cuenta que, como dijo @Javier, el JVM lanza el Error
forma síncrona o asíncrona (lo que significa que puede ser lanzado por otro hilo, posiblemente un hilo nativo), por lo que no es posible obtener el seguimiento de la pila del error. El no. muchas veces los hilos que golpean el bloque catch()
no están definidos.
Una cosa es clara que System.out.print ("y"); En la captura se crea este rompecabezas. Si cambiamos el código como
static int n;
public static void main(final String[] args) throws Exception {
System.out.println("1");
doAnything();
System.out.println(n);
}
private static void doAnything() {
try {
doAnything();
} catch (Error e) {
n++;
}
}
se imprime
1
1