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 face posibil ca aplicaţiile java
să poată fi rulate pe diferite platforme (Sun, MacOS,
Win32, Linus) fără a fi nevoie să se recompileze aceste aplicaţii pentru fiecare dintre
acestea în parte. Astfel aplicaţiile java sunt independente de
platformă.
Implementarea practică a conceptului independenţei de
platformă sa realizat prin folosirea unui calculator virtual pe care
rulează de fapt aplicaţiile compilate java. În urma compilării
fişierelor sursă java nu se va genera cod executabil, pentru o
platformă anume, ci se va genera un cod intermediar numit cod de octeţi (eng.
byte code). Acest cod de octeţi este asemănător
cu limbajul de asamblare dar nu va putea
fi rulat direct pe nici un sistem de operare.
Pentru a rula un cod executabil
java (aşa numitul cod de octeţi) este nevoie de un program
special care va interpreta acest cod de octeţi şi va executa instrucţiuni
specifice sistemului de operare pe care se află. Acest program care
interpretează codul de octeţi se numeşte Maşina
Virtuală Java. Maşina Virtuală Java reprezintă un
calculator abstract. Ca şi calculatoarele reale, aceasta dispune de un set
de instrucţiuni, un set de regiştri şi
utilizează diferite zone de memorie.
Figura 1
prezintă principalele componente ale Maşinii Virtuale Java (JVM).
Figura 1. Arhitectura Maşinii Virtuale Java
Principalele
componente ale JVM sunt:
-
Class Loader Subsystem – aces modul este responsabil cu încărcarea claselor în
memorie.
-
Execution Engine – mecanismul
responsabil cu executarea instrucţiunilor din cadrul claselor
încărcate în cadrul JVM.
-
Runtime Data Area – zona în care sunt memorate
componente necesare pe parcursul rulării unui program. În această
zonă sunt stocate clasele încărcate, obiectele construite,
informaţii despre apelul metodelor, parametrii variabilelor, valorile returnate de metode, date despre firele de execuţie,
etc.
Toate obiectele
create în cadrul maşinii virtuale pe parcursul rulării unui program
sunt plasate în zona de memorie numită Heap.
Ştergerea acestora din memorie este realizată automat de către o
componentă a JVM numita Garbage Collector. Acest proces şterge din zona Heap toate obiectele care nu mai sunt referite în cadrul
programului aflat în execuţie.
Mecanismul Garbage Collector
de eliberare a memorie de obiecte care nu mai sun folositoare oferă două
avantaje importante. În primul rând programatorul nu trebuie sa se
îngrijească în mod explicit de eliberarea obiectelor nefolositoare. Datorită
unor erori de implementare care duc la situaţii de supraîncărcare a
memorie programatorul poate să îşi piardă multe ore pentru a
găsi şi repara defectul. Un al doilea avantaj este acela că GC
asigură integritatea programelor – nu permite ştergerea
accidentală a unor obiecte sau zone de memorie care ar duce pierderea
integrităţii programului sau a maşinii virtuale.
Încărcare suplimentară a procesorului dată de componenta
GC este un dezavantaj al acestei abordări de eliberare automate a
memoriei. Această problemă este rezolvată într-o
măsură destul de mare prin dezvoltarea unor algoritmi
performanţi implementaţi la nivelul GC.
Este important de reţinut faptul că, nu poate fi prezis in
avans momentul în care un obiect urmează să fie eliberat din memorie.
Programatorul nu trebuie să îşi bazeze arhitectura programului
plecând de la anumite presupuneri asupra momentului în care un anumit obiect va
fi eliberat din memorie.
IMPORTANT: Deşi mecanismul GC asigură
eliberarea memorie de obiectele care nu mai sunt referite, există
pericolul ca un program prost gândit şi implementat să aibă
probleme de alocare a memorie, şi să se ajungă la situaţia
de depăşire a memorie – fapt care duce la întreruperea programului.
Acest lucru se poate întâmpla deoarece GC şterge din zona Heap doar acele obiecte care nu sunt referite de către
nici o variabila.
Deşi nu se poate şti în avans exact momentul în care un obiect
este eliberat din memorie, obiectele care urmează să fie şterse
sunt anunţate prin apelarea metodei finalize(). Metoda finalize() se regăseşte în cadrul
oricărui obiect java (fiind moştenită din cadrul clasei de
bază Object) şi este apelată de
către GC în momentul în care obiectul urmează să fie şters.
public class GarbageCollectorTest {
static
int removedObjects;
public
static class Flower{
String name;
Flower(String name){
this.name = name;
}
public
void finalize(){
removedObjects++;
System.err.println("The flower "+name+" is removed. Number
of removed flowers is "+removedObjects);
}
}
public
static void main(String[] args) {
Flower myF = null;
for(int i=0;i<10000;i++)
myF = new Flower("
Flower "+i);
}
}
În listingul anterior este prezentat un
program care demonstrează faptul că la eliberarea fiecărui
obiect din memorie este apelată metoda finalize()
a acestuia.
IMPORTANT: Intrarea în acţiune a GC şi
eliberarea memoriei de obiecte (deci implicit apelarea metodei finalize()) nu este garantată de către
maşina virtuală java. Ca urmare a acestui fapt, toate operaţiile
de eliberarea de resurse care trebuiesc realizate în cadrul unei aplicaţii
(de exemplu închiderea unor conexiuni de reţea, unor fişiere,
închiderea unor conexiuni la baze de date, etc.) trebuie să fie realizate
de programator în mod manual prin construirea unor metode care sunt apelate în
mod explicit la momentul potrivit.