Mașina Virtuală Java

Budai László

Java este un termen la modă în lumea tehnologiei informatice. Cu toții știm că acest limbaj de programare a dat aripi noi lumii informatice din ultimii ani, în primul rând prin faptul că un program Java, numit aplet, poate să ruleze într-o pagină Web, mărind astfel posibilitățile de comunicare a acestora. Dar limbajul Java nu se rezumă doar la aplicații tip aplet, ci dă cale liberă programatorilor pentru realizarea aplicațiilor de sine stătătoare.

O aplicație de sine stătătoare, scrisă în Java, este compilată și rulează pe o așa numită Mașină Virtuală Java. Acest lucru a făcut posibilă separarea de platformă a limbajului. Astfel, un program Java nu este compilat pentru o platformă anume (Sun, MacOS, Win32) ci este transformat în codul Mașinii Virtuale Java.

Mașina Virtuală Java reprezintă un calculator abstract. Ca și calculatoarele reale, aceasta dispune de un set de instrucțiuni, un set de registre și utilizează diferite zone de memorie. Acestea sunt componente logice abstracte. Ele nu impun o implementare anume, acest lucru înseamnă că Mașina Virtuală Java se poate implementa ca interpretor de cod binar, compilator (generează cod nativ), sau pe Siliciu, dar să se păstreze funcționalitatea.

Setul de instrucțiuni

Setul de instrucțiuni Java este echivalentul de limbaj de asamblare a unei aplicații Java. Ca și o aplicație C, o aplicație Java este compilată dar rezultatul este un cod binar Java și nu un cod binar al unui microprocesor anume.

O instrucțiune Java este formată dintr-un cod operație (opcode) și operanzi. Codul operație are lungimea de un octet, operandul putând avea lungime variabilă. Dacă un operand are lungimea mai mare decât un octet, atunci se va păstra în memorie, după modelul „big-endian", adică octetul mai semnificativ primul. Astfel, o valoare stocată pe 16 biți se va calcula după cum urmează: primul_octet*256+al_doilea_octet.

Instrucțiunile binare Java prelucrează operanzii din memoria de date a Mașinii Virtuale Java, ca aparținând unui grup redus de tipuri primitive. Aceste tipuri primitive de date sunt: întreg lung, numere în virgulă flotantă cu simplă și dublă precizie, octet, și întreg scurt. Toate tipurile numerice sunt cu semn. Întregul scurt fără semn există doar pentru a folosi la caracterele Unicode. Aceste tipuri primitive de date sunt tratate de către compilator și nu de programul Java compilat, sau mediul de execuție Java. Instrucțiunile Java conțin cod operație diferit pentru diferitele tipuri de date primitive (ex: iadd, ladd, fadd și dadd pentru adunarea a două numere).

Regiștri

Regiștrii Mașinii Virtuale Java păstrează starea acesteia în timpul operațiilor, la fel ca și regiștrii microprocesoarelor. Mașina Virtuală Java are patru regiștrii:
• pc – numărătorul de program
• optop – pointer către vârful stivei de operanzi
• frame – pointer către mediul de execuție a metodei curente
• vars – pointer către prima variabilă locală a metodei curente (variabila cu indexul 0).

Mașina Virtuală Java definește fiecare registru ca fiind de 32 biți. Anumite implementări pot să nu utilizeze toți regiștrii (ex. Dacă se generează cod nativ, atunci nu mai avem nevoie de pc).

Mașina virtuală Java este bazată pe stivă, adică nu folosește regiștrii pentru transferuri de parametrii, aceasta fiind o decizie luată în favoarea simplității codului și, totodată, permite implementare eficientă pe diferite procesoare gazdă, care au un număr redus de regiștrii, cum ar fi cele din familia x86.

Stiva Java

Mașina virtuală Java folosește stiva pentru a furniza parametrii pentru operații, pentru a prelua rezultatele acestora, pentru a transfera parametrii metodelor, etc. O fereastră de stiva (stack frame) Java este echivalentul unei ferestre de stivă pentru un limbaj de programare convențional. O fereastră de stivă are trei componente:
• variabilele locale
• mediul de execuție
• stiva de operanzi

Spațiul alocat variabilelor locale și mediului de execuție este fixat la apelul metodei, în timp ce stiva de operanzi se modifică în timpul execuției metodei.

Variabilele locale sunt stocate într-un vector și sunt adresate de către registrul vars. Variabilele locale sunt stocate pe 32 biți. Tipul long și double sunt considerate ca fiind formate din două variabile locale.

Variabilele locale se încarcă în stiva de operanzi, iar instrucțiunile vor păstra valorile acestora în spațiul variabilelor locale.

Mediul de execuție este componenta din fereastra de stivă, care se folosește pentru a păstra operațiile stivei Java. Conține pointeri către fereastra de stivă precedentă, precum și către baza și vârful stivei de operanzi și variabile locale. Mai conține informații suplimentare aparținând mediului de execuție (pentru depanare).

Excepții. Fiecare metodă Java conține o serie de clauze catch. Fiecare clauză catch descrie domeniul pentru care este activă, tipul excepției care va fi tratată și are un bloc de tratare a excepției. Când se aruncă o excepție, aceasta este căutată în lista catch. O clauză cath este potrivită (pentru tratarea excepției) dacă instrucțiunea care a aruncat excepția se află în domeniul pentru care este activă clauza catch respectivă și excepția aruncată este derivată din cea pe care o tratează clauza catch. Dacă se găsește o clauză catch potrivită, sistemul va comuta la codul de tratare a excepției. Dacă nu se găsește nici o clauză potrivită, se părăsește fereastra de stivă actuală (metoda actuală) și excepția este aruncată mai departe, către un nivel superior celui în care a apărut.

Ordinea clauzelor catch este importantă, interpretorul executând codul de tratare a primei clauze care se potrivește.

Stiva de operanzi. Este o stivă FIFO pe 32 biți. Ea folosește pentru a stoca argumentele și rezultatele mai multor instrucțiuni ale mașinii virtuale Java. De exemplu operația iadd adună două numere întregi. Operația citește două numere întregi (32 biți) din vârful stivei, le adună, iar rezultatul este stocat tot în stiva de operanzi. Operația iadd presupune că o altă instrucțiune a mașinii virtuale a pus în vârful stivei cele două valori care trebuie să fie adunate și că rezultatul va fi preluat din stiva de operanzi. Pentru fiecare tip de date, există operații specializate pentru acel tip, este ilegal ca să se depună pe stivă două numere întregi și apoi să fie citite ca un întreg lung (64 biți).

Rezerva de constante (Constant pool)

Fiecărei clase Java i se asociază o rezervă de constante, care va păstra numele tuturor câmpurilor, metodelor și a tuturor informațiilor care pot fi utilizate de oricare metodă a clasei.

La încărcarea unei clase Java se va citi lungimea rezervei de constante (constant_pool_count), după care va urma efectiv spațiul alocat acesteia, începând cu constant_pool[0] și până la constant_pool[constant_pool_count-1]. Fiecare element (constant_pool[i]) are următoarea structură:

cp_info {
 	   u1 tag; 
 	   u1 info[]; 
 	   }

Înregistrările încep cu o etichetă de un octet, care indică tipul înregistrării. De exemplu, pentru descriptorul unei clase vom avea valoarea CONSTANT_Class = 7. Formatul informației care urmează după etichetă diferă în funcție de ceea ce reprezintă înregistrarea. De exemplu pentru o clasă avem:

CONSTANT_Class_info { 
 	   u1 tag; 
 	   u2 name_index; 
 	   }

unde tag= CONSTANT_Class (7) iar name _index reprezintă un index în constant_pool care va indica spre o înregistrare CONSTANT_Utf8_info, care va reprezenta numele clasei.

Colectorul de deșeuri

Mașina virtuală Java mai include un colector de deșeuri. Acesta este un proces care rulează în umbră și el este răspunzător de elibararea resurselor ocupate de programe. Există mai multe tehnici pentru realizarea acestuia, dar nu se impune implementarea uneia dintre acestea, cel care realizează o Mașină virtuală Java poate să implementeze propriile tehnici care să realizeze acest lucru. În Java nu se permite programatorului să aibă acces direct la resurse, de ocuparea și eliberarea propriu zisă a lor se ocupă mediul de execuție. De obicei, când o resursă nu mai este referită de nimeni, ea va fi eliberată de către colectorul de deșeuri.

Implementarea acestor elemente ale mașinii virtuale Java nu impune nici o restricție privitoare la tehnica care se folosește.

Diferitele implementări ale mașinii virtuale Java comunică prin intermediul fișierelor .class. Mașina virtuală recunoaște aceste fișiere și nu limbajul Java. Astfel, se pot realiza compilatoare care să genereze cod binar Java și pentru alte limbaje, singura condiție fiind să se genereze un fișier .class care are structura celui recunoscut de mașina virtuală.

Structura fișierului .class

Fișierele .class păstrează codul compilat pentru clasele și interfețele Java. Acest fișier are structura prezentată în figura „Structura fișierelor .class". În această figură u1,u2,u4 reprezintă valori fără semn, având lungimea de 1, 2 respectiv 4 octeți.

Elementele din structura fișierului .class sunt:
magic – reprezintă un număr care identifică formatul fișierului, are valoarea 0xCAFEBABE
minor_version, major_version – reprezintă numerele corespunzătoare versiunii compilatorului care a generat fișierul class.
constant_pool_count – reprezintă numărul înregistrărilor din rezerva de constante. constant_pool_count trebuie să fie o valoare mai mare decât zero.
constant_pool[] – este un vector care conține structuri cu lungime variabilă reprezentând constante string, nume de clase, nume de câmpuri, și alte constante care sunt referite din fișier. Prima înregistrare constant_pool[0] este rezervată pentru uzul intern a mașinii virtuale Java și nu este prezentă în fișierul class. În fișier se găsesc înregistrările constant_pool[1] … constant_pool[constant_pool_count-1]
access_flags – sunt o serie de flaguri folosite clase, metode, sau câmpuri pentru a descrie diferite proprietăți ale acestora, cum ar fi accesul metodelor din alte clase la elementele clasei.
this_class – este un index în constant_pool, care indică o înregistrare de tip CONSTANT_Class_info, care va reprezenta clasa sau interfața a cărei cod se află în fișierul class.
super_class – este un index în constat_pool și va indica spre o înregistrare CONSTANT_Class_info, care reprezintă superclasa din care s-a derivat clasa curentă. Dacă super_class are valoarea zero, atunci este vorba despre clasa java.lang.Object, aceasta fiind singura clasă, care nu se derivă dintr-o clasă.
interfaces_count – reprezintă numărul interfețelor implementate
interfaces[] – este o tabelă de indexuri către constant_pool, înregistrările respective din constant_pool reprezentând descrieri ale interfețelor care sunt superinterfețele directe
fields_count – reprezintă numărul structurilor field_info din tabela de câmpuri. Acest tabel include atât variabilele de clasă cât și cele de instanță.
fields[] – este un tabel conținând structuri care descriu complet variabilele din clasă (atât cele de clasă, cât și cele de instanță). Include doar variabilele declarate în această clasă și în interfețele acesteia, nu și cele care au fost declarate în clase superioare clasei.
methods_count – reprezintă numărul metodelor din tabela de metode
methods[] – este un tabel care conține structuri care descriu complet metodele împreună cu codul executabil al metodelor din clasă, sau interfață. În tabel se găsesc descrierile pentru toate metodele (atât de instanță cât și cele de clasă) care au fost declarate explicit în această clasă.
attributes_count – reprezintă numărul înregistrărilor din tabela de atribute a clasei
attributes[] – conține structuri de lungime variabilă, care caracterizează atributele asociate fișierului class.

Fișierul Hello.class

Pentru exemplificarea structurii unui fișier class am ales fișierul Hello.class, care se generează din următorul fișier sursă (Hello.java):

class Hello{ 
 	   public static void main(String args[]){ 
 	      System.out.println("Hello World"); 
 	      }
 	   }

Dacă vizualizăm conținutul fișierului Hello.class cu un editor, care este capabil să arate și în hexazecimal (wpwiev din NC), putem citi structura din figura „Hello.class".

Referințe:
1. PC Magazin DOS, 2/97, pag.166-1712. http://paro.etri.re.kr/java/doc/beta/vmspec/vmspec_2.html3. http://www.javasoft.com/docs/books/vmspec/html/VMSpecTOC.doc.html


BYTE România - septembrie 1997


(C) Copyright Computer Press Agora