Vânătoarea scurgerilor de memorie Java

Publicat: 2022-03-11

Programatorii neexperimentați cred adesea că colectarea automată a gunoiului Java îi eliberează complet de grijile legate de gestionarea memoriei. Aceasta este o percepție greșită obișnuită: în timp ce colectorul de gunoi face tot posibilul, este absolut posibil ca chiar și cel mai bun programator să cadă pradă pierderilor de memorie paralizante. Lasă-mă să explic.

O scurgere de memorie apare atunci când referințele la obiecte care nu mai sunt necesare sunt menținute în mod inutil. Aceste scurgeri sunt rele. În primul rând, pun presiune inutilă asupra mașinii dvs., deoarece programele dumneavoastră consumă din ce în ce mai multe resurse. Pentru a înrăutăți lucrurile, detectarea acestor scurgeri poate fi dificilă: analiza statică se luptă adesea să identifice cu precizie aceste referințe redundante, iar instrumentele existente de detectare a scurgerilor urmăresc și raportează informații detaliate despre obiecte individuale, producând rezultate greu de interpretat și lipsite de precizie.

Cu alte cuvinte, scurgerile sunt fie prea greu de identificat, fie identificate în termeni prea specifici pentru a fi folositori.

Există de fapt patru categorii de probleme de memorie cu simptome similare și suprapuse, dar cauze și soluții variate:

  • Performanță : de obicei asociată cu crearea și ștergerea excesivă a obiectelor, întârzieri mari în colectarea gunoiului, schimbare excesivă a paginilor sistemului de operare și multe altele.

  • Constrângeri de resurse : apare atunci când există prea puțină memorie disponibilă sau memoria dvs. este prea fragmentată pentru a aloca un obiect mare - acesta poate fi nativ sau, mai frecvent, legat de heap-ul Java.

  • Scurgeri de memorie Java: scurgerile clasice de memorie, în care obiectele Java sunt create continuu fără a fi eliberate. Acest lucru este de obicei cauzat de referințe la obiecte latente.

  • Scurgeri de memorie nativă : asociate cu orice utilizare a memoriei în continuă creștere care se află în afara heap-ului Java, cum ar fi alocările făcute prin cod JNI, drivere sau chiar alocări JVM.

În acest tutorial de gestionare a memoriei, mă voi concentra pe scurgerile din grămezi Java și voi schița o abordare pentru a detecta astfel de scurgeri pe baza rapoartelor Java VisualVM și folosind o interfață vizuală pentru analiza aplicațiilor bazate pe tehnologia Java în timp ce rulează.

Dar înainte de a putea preveni și găsi scurgeri de memorie, ar trebui să înțelegeți cum și de ce apar. ( Notă: dacă vă pricepeți bine la complexitățile scurgerilor de memorie, puteți sări mai departe. )

Scurgeri de memorie: un primer

Pentru început, gândiți-vă la scurgerea memoriei ca la o boală și la OutOfMemoryError (OOM, pentru concizie) de la Java ca la un simptom. Dar, ca și în cazul oricărei boli, nu toate MOO implică neapărat scurgeri de memorie : un MOO poate apărea din cauza generării unui număr mare de variabile locale sau a altor astfel de evenimente. Pe de altă parte, nu toate scurgerile de memorie se manifestă neapărat ca OOM-uri , mai ales în cazul aplicațiilor desktop sau al aplicațiilor client (care nu sunt rulate foarte mult timp fără reporniri).

Gândiți-vă la scurgerea memoriei ca la o boală și la OutOfMemoryError ca la un simptom. Dar nu toate OutOfMemoryErrors implică scurgeri de memorie și nu toate scurgerile de memorie se manifestă ca OutOfMemoryErrors.

De ce sunt aceste scurgeri atât de rele? Printre altele, scurgerile de blocuri de memorie în timpul execuției programului degradează adesea performanța sistemului în timp, deoarece blocurile de memorie alocate, dar neutilizate, vor trebui schimbate odată ce sistemul rămâne fără memoria fizică liberă. În cele din urmă, un program poate chiar să-și epuizeze spațiul de adrese virtuale disponibil, ceea ce duce la OOM.

Descifrarea OutOfMemoryError

După cum sa menționat mai sus, OOM este un indiciu comun al unei scurgeri de memorie. În esență, eroarea este aruncată atunci când nu există spațiu suficient pentru a aloca un nou obiect. Oricât de încercat s-ar putea, colectorul de gunoi nu poate găsi spațiul necesar, iar grămada nu poate fi extinsă în continuare. Astfel, apare o eroare, împreună cu o urmă de stivă.

Primul pas în diagnosticarea OOM este să determinați ce înseamnă de fapt eroarea. Acest lucru sună evident, dar răspunsul nu este întotdeauna atât de clar. De exemplu: OOM apare deoarece heap-ul Java este plin sau pentru că heap-ul nativ este plin? Pentru a vă ajuta să răspundeți la această întrebare, să analizăm câteva dintre posibilele mesaje de eroare:

  • java.lang.OutOfMemoryError: Java heap space

  • java.lang.OutOfMemoryError: PermGen space

  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit

  • java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

  • java.lang.OutOfMemoryError: <reason> <stack trace> (Native method)

„Spațiu heap Java”

Acest mesaj de eroare nu implică neapărat o scurgere de memorie. De fapt, problema poate fi la fel de simplă ca o problemă de configurare.

De exemplu, am fost responsabil pentru analiza unei aplicații care producea în mod constant acest tip de OutOfMemoryError . După câteva investigații, mi-am dat seama că vinovatul era o instanțiere de matrice care solicita prea multă memorie; în acest caz, nu a fost vina aplicației, ci mai degrabă, serverul de aplicații se baza pe dimensiunea heap implicită, care era prea mică. Am rezolvat problema ajustând parametrii de memorie ai JVM-ului.

În alte cazuri, și în special pentru aplicațiile cu durată lungă de viață, mesajul poate fi un indiciu că reținem neintenționat referințe la obiecte , împiedicând colectorul de gunoi să le curețe. Acesta este echivalentul în limbajul Java al unei scurgeri de memorie . ( Notă: API-urile apelate de o aplicație pot deține, de asemenea, neintenționat referințe la obiecte. )

O altă sursă potențială a acestor MOO „spațiu heap Java” apare odată cu utilizarea finalizatoarelor . Dacă o clasă are o metodă finalize , atunci obiectele de acest tip nu au spațiul lor recuperat la momentul colectării gunoiului. În schimb, după colectarea gunoiului, obiectele sunt puse în coadă pentru finalizare, ceea ce are loc mai târziu. În implementarea Sun, finalizatoarele sunt executate de un fir de demon. Dacă firul de finalizare nu poate ține pasul cu coada de finalizare, atunci heap-ul Java s-ar putea umple și ar putea fi lansat un OOM.

„Spațiu PermGen”

Acest mesaj de eroare indică faptul că generația permanentă este plină. Generația permanentă este zona heap-ului care stochează obiecte de clasă și metodă. Dacă o aplicație încarcă un număr mare de clase, atunci dimensiunea generației permanente ar putea fi nevoie să fie mărită folosind opțiunea -XX:MaxPermSize .

Obiectele interne java.lang.String sunt, de asemenea, stocate în generația permanentă. Clasa java.lang.String menține un grup de șiruri. Când este invocată metoda intern, metoda verifică pool-ul pentru a vedea dacă este prezent un șir echivalent. Dacă da, este returnat prin metoda intern; dacă nu, șirul este adăugat la pool. În termeni mai precisi, metoda java.lang.String.intern returnează reprezentarea canonică a unui șir; rezultatul este o referință la aceeași instanță de clasă care ar fi returnată dacă acel șir ar apărea ca un literal. Dacă o aplicație internează un număr mare de șiruri, poate fi necesar să măriți dimensiunea generației permanente.

Notă: puteți utiliza comanda jmap -permgen pentru a imprima statistici legate de generarea permanentă, inclusiv informații despre instanțele String internalizate.

„Dimensiunea matricei solicitată depășește limita VM”

Această eroare indică faptul că aplicația (sau API-urile utilizate de acea aplicație) a încercat să aloce o matrice care este mai mare decât dimensiunea heap-ului. De exemplu, dacă o aplicație încearcă să aloce o matrice de 512 MB, dar dimensiunea maximă a heap-ului este de 256 MB, atunci va fi lansat un OOM cu acest mesaj de eroare. În majoritatea cazurilor, problema este fie o problemă de configurare, fie o eroare care apare atunci când o aplicație încearcă să aloce o matrice masivă.

„Solicitați <dimensiune> octeți pentru <motiv>. Nu mai există spațiu de schimb?”

Acest mesaj pare a fi un OOM. Cu toate acestea, VM HotSpot aruncă această excepție aparentă atunci când o alocare din heap-ul nativ a eșuat și heap-ul nativ ar putea fi aproape de epuizare. În mesaj sunt incluse dimensiunea (în octeți) a cererii care a eșuat și motivul solicitării de memorie. În cele mai multe cazuri, <motivul> este numele modulului sursă care raportează o eroare de alocare.

Dacă acest tip de MOO este aruncat, ar putea fi necesar să utilizați utilitare de depanare pe sistemul dvs. de operare pentru a diagnostica problema în continuare. În unele cazuri, problema poate să nu fie nici măcar legată de aplicație. De exemplu, este posibil să vedeți această eroare dacă:

  • Sistemul de operare este configurat cu spațiu de swap insuficient.

  • Un alt proces din sistem consumă toate resursele de memorie disponibile.

Este, de asemenea, posibil ca aplicația să eșueze din cauza unei scurgeri native (de exemplu, dacă un fragment din aplicație sau cod de bibliotecă alocă continuu memorie, dar nu reușește să o elibereze în sistemul de operare).

<motiv> <urma stivă> (metoda nativă)

Dacă vedeți acest mesaj de eroare și cadrul superior al urmăririi stivei este o metodă nativă, atunci acea metodă nativă a întâmpinat o eroare de alocare. Diferența dintre acest mesaj și cel precedent este că eșecul de alocare a memoriei Java a fost detectat într-o metodă JNI sau nativă, mai degrabă decât în ​​codul Java VM.

Dacă acest tip de MOO este aruncat, este posibil să fie necesar să utilizați utilitare de pe sistemul de operare pentru a diagnostica în continuare problema.

Blocarea aplicației fără OOM

Ocazional, o aplicație se poate bloca la scurt timp după o eroare de alocare din heap-ul nativ. Acest lucru se întâmplă dacă rulați cod nativ care nu verifică erorile returnate de funcțiile de alocare a memoriei.

De exemplu, apelul de sistem malloc returnează NULL dacă nu există memorie disponibilă. Dacă revenirea de la malloc nu este verificată, atunci aplicația se poate bloca atunci când încearcă să acceseze o locație de memorie invalidă. În funcție de circumstanțe, acest tip de problemă poate fi dificil de localizat.

În unele cazuri, informațiile din jurnalul de erori fatale sau din depozitul de blocare vor fi suficiente. Dacă se determină că cauza unui accident este o lipsă de gestionare a erorilor în unele alocări de memorie, atunci trebuie să găsiți motivul eșecului de alocare menționat. Ca și în cazul oricărei alte probleme native de heap, sistemul poate fi configurat cu spațiu de swap insuficient, un alt proces ar putea consuma toate resursele de memorie disponibile etc.

Diagnosticarea scurgerilor

În cele mai multe cazuri, diagnosticarea scurgerilor de memorie necesită cunoștințe foarte detaliate despre aplicația în cauză. Atenție: procesul poate fi lung și iterativ.

Strategia noastră pentru a urmări scurgerile de memorie va fi relativ simplă:

  1. Identificați simptomele

  2. Activați colectarea detaliate a gunoiului

  3. Activați profilarea

  4. Analizați urma

1. Identificați simptomele

După cum sa discutat, în multe cazuri, procesul Java va arunca în cele din urmă o excepție de rulare OOM, un indicator clar că resursele de memorie au fost epuizate. În acest caz, trebuie să distingeți între o epuizare normală a memoriei și o scurgere. Analizând mesajul OOM și încercați să găsiți vinovatul pe baza discuțiilor prezentate mai sus.

Adesea, dacă o aplicație Java solicită mai mult spațiu de stocare decât oferă heap-ul de rulare, aceasta se poate datora unui design slab. De exemplu, dacă o aplicație creează mai multe copii ale unei imagini sau încarcă un fișier într-o matrice, va epuiza spațiul de stocare atunci când imaginea sau fișierul este foarte mare. Aceasta este o epuizare normală a resurselor. Aplicația funcționează așa cum a fost proiectată (deși acest design este în mod clar neînțeles).

Dar dacă o aplicație își crește în mod constant utilizarea memoriei în timp ce procesează același tip de date, este posibil să aveți o scurgere de memorie.

2. Activați Verbose Garbage Collection

Una dintre cele mai rapide moduri de a afirma că aveți într-adevăr o scurgere de memorie este să activați colectarea de gunoi. Problemele de constrângere a memoriei pot fi identificate de obicei prin examinarea tiparelor din ieșirea verbosegc .

Mai exact, argumentul -verbosegc vă permite să generați o urmă de fiecare dată când procesul de colectare a gunoiului (GC) este început. Adică, pe măsură ce memoria este colectată de gunoi, rapoartele rezumative sunt tipărite la o eroare standard, oferindu-vă o idee despre cum este gestionată memoria dvs.

Iată câteva rezultate tipice generate cu opțiunea –verbosegc :

ieșire de colectare a gunoiului

Fiecare bloc (sau strofă) din acest fișier de urmărire GC este numerotat în ordine crescătoare. Pentru a înțelege această urmă, ar trebui să vă uitați la strofele succesive de eșec al alocării și să căutați memoria eliberată (octeți și procentaj) care scad în timp în timp ce memoria totală (aici, 19725304) crește. Acestea sunt semne tipice de epuizare a memoriei.

3. Activați Profiling

Diferite JVM-uri oferă diferite moduri de a genera fișiere de urmărire pentru a reflecta activitatea heap, care de obicei includ informații detaliate despre tipul și dimensiunea obiectelor. Aceasta se numește profilarea heap .

4. Analizați Urma

Acest post se concentrează pe urma generată de Java VisualVM. Urmele pot veni în diferite formate, deoarece pot fi generate de diferite instrumente Java de detectare a scurgerilor de memorie, dar ideea din spatele lor este întotdeauna aceeași: găsiți un bloc de obiecte în grămada care nu ar trebui să fie acolo și determinați dacă aceste obiecte se acumulează. în loc să elibereze. De un interes deosebit sunt obiectele tranzitorii despre care se știe că sunt alocate de fiecare dată când un anumit eveniment este declanșat în aplicația Java. Prezența multor instanțe de obiect care ar trebui să existe doar în cantități mici indică, în general, o eroare a aplicației.

În cele din urmă, rezolvarea scurgerilor de memorie necesită să vă revizuiți codul în detaliu. Aflarea despre tipul de scurgere a obiectelor poate fi foarte utilă și poate accelera considerabil depanarea.

Cum funcționează colectarea gunoiului în JVM?

Înainte de a începe analiza unei aplicații cu o problemă de scurgere a memoriei, să vedem mai întâi cum funcționează colectarea gunoiului în JVM.

JVM folosește o formă de colector de gunoi numită tracing collector , care funcționează în esență prin întreruperea lumii din jurul său, marcarea tuturor obiectelor rădăcină (obiecte la care se face referire direct prin rularea firelor de execuție) și urmărirea referințelor acestora, marcând fiecare obiect pe care îl vede pe parcurs.

Java implementează ceva numit un colector de gunoi generațional pe baza ipotezei generaționale, care afirmă că majoritatea obiectelor care sunt create sunt aruncate rapid , iar obiectele care nu sunt colectate rapid sunt probabil să existe pentru o perioadă .

Pe baza acestei presupuneri, Java partiţionează obiectele în mai multe generaţii. Iată o interpretare vizuală:

Java împarte în mai multe generații

  • Generația tânără - Aici pornesc obiectele. Are două subgenerații:

    • Eden Space - Obiectele încep de aici. Cele mai multe obiecte sunt create și distruse în Spațiul Eden. Aici, GC face GC minore , care sunt colectări optimizate de gunoi. Când se efectuează un GC Minor, orice referințe la obiecte care sunt încă necesare sunt migrate într-unul dintre spațiile supraviețuitorilor (S0 sau S1).

    • Survivor Space (S0 și S1) - Obiectele care supraviețuiesc Edenului ajung aici. Există două dintre acestea și doar unul este în uz la un moment dat (cu excepția cazului în care avem o scurgere serioasă de memorie). Unul este desemnat ca gol , iar celălalt ca viu , alternând cu fiecare ciclu GC.

  • Generație titulară - Cunoscută și ca vechea generație (vechiul spațiu din Fig. 2), acest spațiu deține obiecte mai vechi cu durate de viață mai lungi (mutate din spațiile supraviețuitorilor, dacă trăiesc suficient de mult). Când acest spațiu este umplut, GC face un GC complet , care costă mai mult din punct de vedere al performanței. Dacă acest spațiu crește fără limită, JVM-ul va arunca un OutOfMemoryError - Java heap space .

  • Generație permanentă - O a treia generație strâns legată de generația titulară, generația permanentă este specială deoarece deține date necesare mașinii virtuale pentru a descrie obiecte care nu au o echivalență la nivel de limbaj Java. De exemplu, obiectele care descriu clase și metode sunt stocate în generația permanentă.

Java este suficient de inteligent pentru a aplica diferite metode de colectare a gunoiului fiecărei generații. Generația tânără este gestionată folosind un colector de urmărire și copiere numit Parallel New Collector . Acest colecționar oprește lumea, dar pentru că generația tânără este în general mică, pauza este scurtă.

Pentru mai multe informații despre generațiile JVM și cum funcționează acestea în detaliu, vizitați Gestionarea memoriei din documentația Java HotSpot Virtual Machine.

Detectarea unei scurgeri de memorie

Pentru a găsi scurgeri de memorie și pentru a le elimina, aveți nevoie de instrumentele adecvate pentru scurgeri de memorie. Este timpul să detectați și să eliminați o astfel de scurgere folosind Java VisualVM.

Profilarea de la distanță a heap-ului cu Java VisualVM

VisualVM este un instrument care oferă o interfață vizuală pentru vizualizarea informațiilor detaliate despre aplicațiile bazate pe tehnologia Java în timp ce acestea rulează.

Cu VisualVM, puteți vizualiza date referitoare la aplicațiile locale și cele care rulează pe gazde la distanță. De asemenea, puteți captura date despre instanțele software JVM și puteți salva datele în sistemul dvs. local.

Pentru a beneficia de toate caracteristicile Java VisualVM, ar trebui să rulați Java Platform, Standard Edition (Java SE) versiunea 6 sau mai recentă.

Înrudit: De ce trebuie să faceți deja upgrade la Java 8

Activarea conexiunii la distanță pentru JVM

Într-un mediu de producție, este adesea dificil să accesați mașina reală pe care va rula codul nostru. Din fericire, putem profila aplicația noastră Java de la distanță.

În primul rând, trebuie să ne acordăm acces JVM pe mașina țintă. Pentru a face acest lucru, creați un fișier numit jstatd.all.policy cu următorul conținut:

 grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };

Odată ce fișierul a fost creat, trebuie să activăm conexiunile de la distanță la VM-ul țintă folosind instrumentul jstatd - Virtual Machine jstat Daemon, după cum urmează:

 jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>

De exemplu:

 jstatd -p 1234 -J-Djava.security.policy=D:\jstatd.all.policy

Odată cu jstatd pornit în VM-ul țintă, ne putem conecta la mașina țintă și ne putem profila de la distanță aplicația cu probleme de pierderi de memorie.

Conectarea la o gazdă de la distanță

În mașina client, deschideți un prompt și tastați jvisualvm pentru a deschide instrumentul VisualVM.

Apoi, trebuie să adăugăm o gazdă la distanță în VisualVM. Deoarece JVM-ul țintă este activat pentru a permite conexiuni la distanță de pe o altă mașină cu J2SE 6 sau mai mare, pornim instrumentul Java VisualVM și ne conectăm la gazda la distanță. Dacă conexiunea cu gazda la distanță a avut succes, vom vedea aplicațiile Java care rulează în JVM-ul țintă, așa cum se vede aici:

rulează în jvm țintă

Pentru a rula un profiler de memorie pe aplicație, facem doar dublu clic pe numele acestuia în panoul lateral.

Acum că suntem cu toții configurați cu un analizor de memorie, să investigăm o aplicație cu o problemă de scurgere a memoriei, pe care o vom numi MemLeak .

MemLeak

Desigur, există o serie de moduri de a crea scurgeri de memorie în Java. Pentru simplitate, vom defini o clasă să fie o cheie într-un HashMap , dar nu vom defini metodele equals() și hashcode().

Un HashMap este o implementare a tabelului hash pentru interfața Map și, ca atare, definește conceptele de bază de cheie și valoare: fiecare valoare este legată de o cheie unică, deci dacă cheia pentru o anumită pereche cheie-valoare este deja prezentă în HashMap, valoarea sa actuală este înlocuită.

Este obligatoriu ca clasa noastră cheie să ofere o implementare corectă a metodelor equals() și hashcode() . Fără ele, nu există nicio garanție că va fi generată o cheie bună.

Prin nedefinirea metodelor equals() și hashcode() , adăugăm aceeași cheie la HashMap iar și iar, în loc să înlocuim cheia așa cum ar trebui, HashMap crește continuu, nereușind să identifice aceste chei identice și aruncând un OutOfMemoryError .

Iată clasa MemLeak:

 package com.post.memory.leak; import java.util.Map; public class MemLeak { public final String key; public MemLeak(String key) { this.key =key; } public static void main(String args[]) { try { Map map = System.getProperties(); for(;;) { map.put(new MemLeak("key"), "value"); } } catch(Exception e) { e.printStackTrace(); } } }

Notă: scurgerea de memorie nu se datorează buclei infinite de pe linia 14: bucla infinită poate duce la o epuizare a resurselor, dar nu la o scurgere de memorie. Dacă am fi implementat corect metodele equals() și hashcode() , codul ar rula bine chiar și cu bucla infinită, deoarece am avea un singur element în HashMap.

(Pentru cei interesați, iată câteva mijloace alternative de a genera (intenționat) scurgeri.)

Folosind Java VisualVM

Cu Java VisualVM, putem monitoriza memoria Java Heap și putem identifica dacă comportamentul acestuia indică o scurgere de memorie.

Iată o reprezentare grafică a analizorului MemLeak Java Heap imediat după inițializare (amintim discuția noastră despre diferitele generații):

monitorizați scurgerile de memorie folosind java visualvm

După doar 30 de secunde, Vechea Generație este aproape plină, ceea ce indică faptul că, chiar și cu un Full GC, Vechea Generație este în continuă creștere, un semn clar al unei scurgeri de memorie.

Un mijloc de a detecta cauza acestei scurgeri este prezentat în următoarea imagine ( faceți clic pentru a mări ), generată folosind Java VisualVM cu un heapdump . Aici, vedem că 50% din obiectele Hashtable$Entry sunt în heap , în timp ce a doua linie ne indică clasa MemLeak . Astfel, scurgerea memoriei este cauzată de un tabel hash utilizat în cadrul clasei MemLeak .

scurgere de memorie a tabelului hash

În cele din urmă, observați Java Heap imediat după OutOfMemoryError , în care generațiile Young și Old sunt complet pline .

outofmemoryerror

Concluzie

Scurgerile de memorie sunt printre cele mai greu de rezolvat probleme ale aplicațiilor Java, deoarece simptomele sunt variate și greu de reprodus. Aici, am schițat o abordare pas la pas pentru a descoperi scurgerile de memorie și a identifica sursele acestora. Dar, mai presus de toate, citiți cu atenție mesajele de eroare și acordați atenție urmelor stivei - nu toate scurgerile sunt atât de simple pe cât par.

Apendice

Alături de Java VisualVM, există câteva alte instrumente care pot efectua detectarea scurgerilor de memorie. Mulți detectoare de scurgeri funcționează la nivel de bibliotecă prin interceptarea apelurilor către rutinele de gestionare a memoriei. De exemplu, HPROF , este un instrument simplu de linie de comandă, împreună cu Java 2 Platform Standard Edition (J2SE) pentru crearea de profile heap și CPU. Ieșirea HPROF poate fi analizată direct sau utilizată ca intrare pentru alte instrumente precum JHAT . Când lucrăm cu aplicații Java 2 Enterprise Edition (J2EE), există o serie de soluții de analiză heap dump care sunt mai prietenoase, cum ar fi IBM Heapdumps pentru serverele de aplicații Websphere.