2010-07-19 25 views
7

Sto scrivendo uno strumento Bytecode. In questo momento, sto cercando di scoprire come farlo in presenza di oggetti. Vorrei alcuni chiarimenti su due righe che ho letto nella JVM (sezione 4.9.4):Chiarimenti su Bytecode e oggetti

1) "Il verificatore respinge codice che utilizza il nuovo oggetto prima che sia stato inizializzato."

La mia domanda è, cosa significa "utilizza" significa qui? Suppongo che significhi: passarlo come attributo metodo, chiamando GETFIELD e PUTFIELD su di esso, o chiamando qualsiasi metodo di istanza su di esso. I loro altri usi proibiti? E credo che ne consegue che sono consentite altre istruzioni come DUP, LOAD e STORE.

2) "Prima metodo richiama un altro metodo di inizializzazione istanza di myClass o sua superclasse diretta su questo, l'unica operazione il metodo può eseguire su questo è assegnare campi dichiarate all'interno miaClasse".

Il che significa che in un metodo <init>, getField e PUTFIELD sono consentiti prima che un altro <init> si chiama. Tuttavia, in Java, qualsiasi operazione su un campo di istanza prima di una chiamata a super() o this() genera un errore di compilazione. Qualcuno potrebbe chiarirlo?

3) Ho ancora una domanda. Quando viene inizializzato un riferimento a un oggetto e quindi pronto per essere utilizzato liberamente? Dalla lettura di JVMS, ho trovato la risposta che se un oggetto è inizializzato o meno, dipende da ciascun metodo. Ad un certo punto nel tempo, l'oggetto può essere inizializzato per un metodo ma non per l'altro. In particolare, un oggetto viene inizializzato per un metodo quando <init> chiamato da tale metodo restituisce.

Ad esempio, si consideri che il metodo main() ha creato un oggetto e ha chiamato <init> che poi ha chiamato la superclasse <init>. Dopo il ritorno da super(), l'oggetto è ora considerato inizializzato da <init>, ma non è ancora stato inizializzato per main(). Significa che, in <init> dopo super(), posso passare l'oggetto come parametro a un metodo, anche prima di tornare da main().

Qualcuno potrebbe confermare che tutta questa analisi è vera? Grazie per il tuo tempo.

ps: in realtà ho pubblicato la stessa domanda sui forum Sun ma con risposta. Spero di avere più fortuna qui. Grazie.

Aggiornamento

In primo luogo vi ringrazio per le vostre risposte e il tempo. Sebbene non avessi una risposta chiara (avevo molte domande e alcune di esse erano un po 'vaghe), le tue risposte ed esempi, e gli esperimenti successivi, mi sono stati estremamente utili per capire più profondamente come funziona la JVM.

La cosa principale che ho scoperto è che il comportamento del Verificatore differisce da diverse implementazioni e versioni (il che rende il lavoro di manipolazione del bytecode molto più complicato).Il problema sta nel non conformarsi al JVMS, o nella mancanza di documentazione da parte degli sviluppatori del verificatore, o il JVMS ha qualche sottile vaghezza nell'area del verificatore.

Un'ultima cosa, SO Rocks !!! Ho postato la stessa domanda nel forum ufficiale Sun JVM, e fino ad ora non ho ancora ricevuto risposta.

risposta

1

Suggerisco di scaricare una copia dei sorgenti di OpenJDK e di controllare cosa sta effettivamente verificando il verificatore. Se non altro, ciò può aiutarti a capire che cosa sta dicendo la specifica JMV.

(Tuttavia, @Joachim è giusto. Basandosi su ciò che l'attuazione verificatore fa piuttosto che quello che la specifica dice è piuttosto rischioso.)

+0

Non è una cattiva idea, ma si deve fare attenzione a questo . In passato il verificatore effettivo era meno severo riguardo alle regole rispetto alle specifiche. È del tutto possibile che tali casi si verifichino ancora. –

+0

Sì, ho scoperto che le diverse implementazioni del Verifier hanno diversi livelli di rigore. Ad esempio, il verificatore JustIce di Apache BCEL non consente STORE su riferimenti a oggetti non inizializzati, mentre Java HotSpot 10.0 fa –

4

"Il verificatore rifiuta codice che utilizza il nuovo oggetto prima che è stato inizializzato. "

Nella verifica bytecode, poiché il verificatore funziona in tempo di collegamento, vengono inferiti i tipi di variabili locali dei metodi. I tipi di argomenti del metodo sono noti come sono nella firma del metodo nel file di classe. I tipi di altre variabili locali non sono noti e vengono dedotti, quindi presumo che lo "usi" nella precedente istruzione si riferisca a questo.

EDIT: La sezione 4.9.4 della JVMS legge:

Il metodo di inizializzazione di istanza (§3.9) per la classe myClass vede il nuovo oggetto non inizializzato come questo argomento variabile locale 0. Prima che il metodo richiama un altro metodo di inizializzazione dell'istanza di myClass o la sua superclasse diretta su questo, l'unica operazione che il metodo può eseguire su questo sta assegnando i campi dichiarati all'interno di myClass.

Questa assegnazione di campi nell'istruzione sopra è l'inizializzazione "iniziale" delle variabili istanza di valori iniziali di default (come int è 0, galleggiante è 0.0f etc.) quando la memoria per l'oggetto è allocato. C'è un'altra inizializzazione "corretta" delle variabili di istanza quando la macchina virtuale richiama il metodo di inizializzazione dell'istanza (costruttore) sull'oggetto.


Il link fornito da John Horstmann ha contribuito a chiarire le cose. Quindi queste affermazioni non sono vere. "Questo DOESNOT significa che in un metodo <init>, getfield e putfield sono consentiti prima che venga chiamato un altro <init>." Le istruzioni getfield e putfield vengono utilizzate per accedere (e modificare) le variabili di istanza (campi) di una classe (o istanza di una classe). E questo può avvenire solo quando le variabili di istanza (campi) vengono inizializzati "

Dalle JVM:. Metodo di inizializzazione

Ogni istanza (§3.9), fatta eccezione per il metodo di inizializzazione dell'istanza derivato dal costruttore della classe Object, deve chiamata o un altro metodo di inizializzazione esempio di questo o un metodo di inizializzazione esempio della sua diretta eccellente superclasse prima suoi membri di istanza sono accessibili. Tuttavia, i campi di istanza di questo che sono dichiarati nella classe corrente possono essere assegnati a prima di chiamare qualsiasi metodo di inizializzazione dell'istanza .

Quando la Java Virtual Machine crea una nuova istanza di una classe, implicitamente o esplicitamente, in primo luogo assegna la memoria sul mucchio per contenere variabili di istanza dell'oggetto. La memoria è allocata per tutte le variabili dichiarate nella classe dell'oggetto e in tutte le sue superclassi, comprese le variabili di istanza che sono nascoste. Non appena la macchina virtuale ha messo da parte la memoria heap per un nuovo oggetto, inizializza immediatamente le variabili di istanza ai valori iniziali predefiniti. Una volta che la macchina virtuale ha allocato la memoria per il nuovo oggetto e inizializzato le variabili di istanza ai valori predefiniti, è pronta a fornire alle variabili di istanza i loro valori iniziali corretti. Java Virtual Machine utilizza due tecniche per fare ciò, a seconda che l'oggetto venga creato a causa di una chiamata clone(). Se l'oggetto viene creato a causa di un clone(), la macchina virtuale copia i valori delle variabili di istanza dell'oggetto clonato nel nuovo oggetto. Altrimenti, la macchina virtuale invoca un metodo di inizializzazione dell'istanza sull'oggetto. Il metodo di inizializzazione dell'istanza inizializza le variabili di istanza dell'oggetto ai rispettivi valori iniziali. E solo dopo questo è possibile utilizzare getfield e putfield.

Il compilatore java genera almeno un metodo di inizializzazione dell'istanza (costruttore) per ogni classe che compila. Se la classe non dichiara esplicitamente costruttori, il compilatore ha generato un costruttore no-arg predefinito che invoca solo il costruttore della non-guida della superclasse. E giustamente facendo qualsiasi operazione su un campo di istanza prima che una chiamata a super() o this() comporti un errore di compilazione.

Un metodo <init> può contenere tre tipi di codice: un'invocazione di un altro metodo <init>, codice che implementa eventuali initializers variabili istanza, e il codice per il corpo del costruttore. Se un costruttore inizia con un'invocazione esplicita di un altro costruttore della stessa classe (a this() invocazione) il corrispondente <init> metodo sarà composta da due parti:

  • invocazione della stessa classe <init> metodo
  • i bytecode che implementano il corpo del corrispondente costruttore

Se un costruttore non inizia con un this() invocazione e la classe non è oggetto, la <init> metodo avrà tre componenti:

  • invocazione di una superclasse metodo <init>
  • i bytecode per qualsiasi esempio initializers variabili
  • i bytecode che implementano il corpo del costruttore corrispondente


Se un costruttore non inizia con un invoc this() Ation e la classe è Object (e Object non ha superclasse), quindi il suo metodo <init> non può iniziare con una chiamata alla superclasse <init>. Se un costruttore inizia con una chiamata esplicita di un costruttore di superclasse (una chiamata super()), il suo metodo <init> invocherà il metodo superclasse corrispondente <init>.



penso che questo risponde alla tua prima e la seconda domanda.

aggiornamento:

Ad esempio,

class Demo 
    { 
    int somint; 

    Demo() //first constructor 
    { 
     this(5); 
     //some other stuff.. 
    } 

    Demo(int i) //second constructor 
    { 
     this.somint = i; 
     //some other stuff...... 
    } 
    Demo(int i, int j) //third constructor 
    { 
     super(); 
     //other stuffff...... 
    } 
    } 

Heres il bytecode per le tre costruttori dal compilatore (javac):

Demo(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: aload_0 
    1: iconst_5 
    2: invokespecial #1; //Method "<init>":(I)V 
    5: return 

Demo(int); 
    Code: 
    Stack=2, Locals=2, Args_size=2 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: aload_0 
    5: iload_1 
    6: putfield  #3; //Field somint:I 
    9: return 

Demo(int, int); 
    Code: 
    Stack=1, Locals=3, Args_size=3 
    0: aload_0 
    1: invokespecial #2; //Method java/lang/Object."<init>":()V 
    4: return 

Nel primo costruttore, il metodo <init> inizia con la chiamata alla metamatica <init> della stessa classe od e poi eseguito il corpo del costruttore corrispondente. Poiché il costruttore inizia con this(), il suo metodo <init> corrispondente non contiene il bytecode per l'inizializzazione delle variabili di istanza.

Nel secondo costruttore, il metodo <init> per il costruttore ha

  • eccellente metodo di classe <init>, cioè, invocazione della superclasse costruttore (senza metodo arg), il compilatore generato per default perché nessuna esplicita super() è stata trovata come prima affermazione.
  • il bytecode per l'inizializzazione della variabile di istanza someint.
  • bytecode per il resto del materiale nel corpo del costruttore .
+0

PS: Non ho scritto da solo uno strumento bytecode ma sto leggendo materiale sulla JVM ultimamente come Inside the JVM. – Zaki

+0

Innanzitutto grazie per il tuo tempo !!! Ho ancora alcune domande: hai detto che un costruttore senza questo() ha 3 componenti. Tuttavia, ho letto il bytecode e il compilatore non aggiunge mai bytecodes per gli inizializzatori delle variabili di istanza (0 per int, null per oggetti). Sembra che la JVM inizializzi i campi di istanza senza bisogno del bytecode che lo faccia esplicitamente. –

+0

@HH, vedere risposta aggiornata – Zaki

3

Contrariamente a quanto il linguaggio Java specifica, a livello di bytecode che è possibile accedere ai campi di una classe in un costruttore prima di chiamare il costruttore della superclasse. Il codice seguente utilizza la libreria asm per creare una classe:

package asmconstructortest; 

import java.io.FileOutputStream; 
import org.objectweb.asm.*; 
import org.objectweb.asm.util.CheckClassAdapter; 
import static org.objectweb.asm.Opcodes.*; 

public class Main { 

    public static void main(String[] args) throws Exception { 
     //ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"}); 
     ClassWriter cw = new ClassWriter(0); 
     CheckClassAdapter ca = new CheckClassAdapter(cw); 

     ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null); 

     { 
      FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null); 
      fv.visitEnd(); 
     } 

     { 
      MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 
      mv.visitCode(); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitInsn(ICONST_1); 
      mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I"); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); 
      mv.visitInsn(RETURN); 
      mv.visitMaxs(2, 1); 
      mv.visitEnd(); 
     } 

     ca.visitEnd(); 

     FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class"); 
     out.write(cw.toByteArray()); 
     out.close(); 
    } 
} 

istanze di questa classe funziona bene, senza errori di verifica:

package asmconstructortest; 

public class Main2 { 
    public static void main(String[] args) { 
     Test2 test2 = new Test2(); 
     System.out.println(test2.property); 
    } 
} 
+1

Ho anche testato questa idea usando jbe (editor bytecode java) e passa sul verificatore di HotSpot 10 (ma non su JustIce di Apache BCEL) –