2011-12-13 16 views
11

De forma predeterminada, la JVM de Sun carga las clases con pereza y se inicializa de forma lenta (es decir, llama a sus métodos <clinit>). Considere la siguiente clase, ClinitBomb, que arroja un Exception durante un bloque static{}.¿Cómo se desactiva la carga/inicialización de la clase perezosa en la JVM de Sun?

public class ClinitBomb { 
    static { 
     explode(); 
    } 
    private static void explode() { 
     throw new RuntimeException("boom!"); 
    }  
} 

Ahora, consideremos cómo activar la bomba:

public class Main { 
    public static void main(String[] args) { 
     System.out.println("A"); 
     try { 
      Class.forName("ClinitBomb"); 
     } catch (Exception e) { 
      e.printStackTrace(System.out); 
     } 
     System.out.println("B"); 
     ClinitBomb o2 = new ClinitBomb(); 
     System.out.println("C"); 
    } 
} 

Estamos garantizada la explosión sucede antes del punto B, ya que forName 's documentación dice así; la pregunta es si ocurre antes del punto A (cuando se carga Main.) En JVM de Sun, a pesar de que main() contiene una referencia estática a ClinitBomb, que ocurre después de A.

quiero una manera de decirle a la JVM para cargar y inicialice ClinitBomb tan pronto como inicialice Main (por lo que la bomba explota antes punto A). En general, quiero decir, "cada vez que cargue/inicialice la clase X, hágalo también para las clases Y a las que hace referencia".

¿Hay alguna manera de hacerlo?

+0

¿Un bloque estático se refiere a ClinitBomb en Main? –

+0

@ Thorbjørn Ravn Andersen que no resolverá el problema general. Pero supongo que un cargador de clases personalizado podría inyectar bloques estáticos en todas y cada una de las clases cargadas. – emory

+0

A falta de agregar una gran lista de 'Class.forName()' No lo sé. OTOH, ¿por qué es tan importante? – Bill

Respuesta

8

No hay forma de hacerlo. El JLS dice, en §12.4.1 When Initialization Occurs (el énfasis es mío):

inicialización de una clase consiste en la ejecución de sus inicializadores estáticos y los inicializadores de campos estáticos declarados en la clase. [...]

una clase o tipo de interfaz T se inicializará inmediatamente antes de la primera aparición de uno cualquiera de los siguientes:

  • T es una clase y se crea una instancia de T.
  • T es una clase y se invoca un método estático declarado por T.
  • Se asigna un campo estático declarado por T.
  • Se utiliza un campo estático declarado por T y el campo no es una variable constante (§4.12.4).
  • T es una clase de nivel superior, y se ejecuta una sentencia de afirmación (§14.10) anidada léxicamente dentro de T.

La invocación de ciertos métodos reflectantes en la clase Class y en el paquete java.lang.reflect también provoca la inicialización de la clase o la interfaz. Una clase o interfaz no se inicializará bajo ninguna otra circunstancia.

Una implementación de Java que inicializó las clases tan pronto como se cargaron violaría el JLS.

Aunque lo que podría hacer sería usar la JVM instrumentation API para escribir un ClassFileTransformer que agrega un bloque estático a cada clase que inicializó explícitamente sus clases referenciadas (a través de Class.forName, probablemente). Tan pronto como una clase se inicialice, todas las clases accesibles desde ella se inicializarán.Eso podría darte el resultado que buscas. ¡Sin embargo, es un montón de trabajo!

+1

Gracias. (También, bummer. Hubiera sido muy conveniente para la depuración en este caso. Heredé una base de código en la que la mayoría de las clases declaran un campo 'static Logger final ', que es genial, excepto que se crean utilizando una infraestructura de registro que golpea RMI Entonces, básicamente, en puntos razonablemente arbitrarios en la ejecución, de repente tienes llamadas remotas donde una falla te da un 'ExceptionInInitializerError'. ¿No suena divertido?) – jon

+0

Hmm ... curiosamente, la clase de ClinitBomb no es pareja * cargado * hasta el forName(), que parece realmente extraño. (Es decir: eliminar ClinitBomb.class aún permite imprimir "A" antes de lanzar un NoClassDefFoundError). Según JLS 12.1.2, depende de la implementación; ¿Alguna idea de cómo cambiar el comportamiento predeterminado en Sun JVM? – jon

+0

No conozco ninguno. Ejecute 'java -XX: + UnlockDiagnosticVMOptions -XX: + PrintFlagsFinal' y vea si algo se sugiere a sí mismo: no pude ver nada con el JRE que tengo. –

1
Class.forName("...", true /*initialize*/, getClassLoader()); 

Estabas a mitad de camino.

+0

Puede usar su propio ClassLoader con ganas. –

+0

No. El código que ha escrito ya inicializará 'ClinitBomb' en la llamada' forName'; la [versión de argumento único de forName] (http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29) "es equivalente a : Class.forName (className, true, currentLoader) ". Su deseo es tener 'ClinitBomb' inicializado incluso antes de eso. –

+0

@Tom Anderson. Estoy corregido. –

Cuestiones relacionadas