Atminties nuotėkio sukūrimas naudojant java

Aš tiesiog turėjau interviu ir buvo paprašyta sukurti atminties nuotėkį naudojant „Java“.
Nereikia nė sakyti, kad aš jaučiausi gana kvailas, neturėjęs menkiausios idėjos, kaip net ją pradėti kurti.

Kas bus pavyzdys?

2828
24 июня '11 в 19:11 2011-06-24 19:11 Mat B. nustatoma birželio 24 d. 11 val. 19:11 2011-06-24 19:11
@ 53 atsakymai
  • 1
  • 2

Štai geras būdas sukurti tikrą atminties nuotėkį (objektus, kurie yra nepasiekiami, kai veikia kodas, bet vis dar saugomi atmintyje) gryname „Java“:

  • Programa sukuria ilgą siūlą (arba naudoja srautų srautą dar greičiau).
  • Sriegis įkelia klasę per (pasirinktinai pritaikomą) ClassLoader.
  • Klasė priskiria didelę atminties dalį (pvz., new byte[1000000] ), jame įrašomas stiprus nuoroda statiniame lauke, o tada išsaugo nuorodą į save „ThreadLocal“. Papildomos atminties skyrimas nėra būtinas (pakanka klasės nuotėkio), tačiau tai greičiau nutekės.
  • Gija išvalo visas nuorodas į vartotojo klasę arba ClassLoader, iš kurios jis buvo įkeltas.
  • Pakartokite.

Tai veikia, nes „ThreadLocal“ išsaugo nuorodą į objektą, kuriame saugoma nuoroda į savo klasę, o tai savo ruožtu reiškia „ClassLoader“. „ClassLoader“ savo ruožtu išsaugo nuorodą į visas pakrautas klases.

(Tai buvo dar blogiau daugelyje JVM diegimų, ypač prieš „Java 7“, nes klasės ir „ClassLoaders“ buvo tiesiogiai priskirtos permgen ir visai nebuvo GC'd. Tačiau nesvarbu, kaip JVM tvarko klasės iškrovimą, „ThreadLocal“ vis tiek neleis objektui grįžti klasė.)

Šio modelio variantas yra tas, kodėl taikomieji indai (pvz., Tomcat) gali nutekėti atminties, pvz., Sietai, jei dažnai perkeliate programas, kurios bet kokiu būdu naudoja „ThreadLocals“. (Kadangi programinės įrangos talpykloje naudojami siūlai, kaip aprašyta, ir kiekvieną kartą perkeliant programą, naudojamas naujas „ClassLoader“.)

Atnaujinti . Kaip daugelis žmonių ir toliau prašo, čia yra pavyzdinis kodas, rodantis šį veiksmą .

2071 m
24 июня '11 в 21:05 2011-06-24 21:05 Atsakymą davė Daniel Prydenas birželio 24 d., 11 val. 21:05 2011-06-24 21:05

Statinis laukas, kuriame yra nuoroda į [esp final field] objektą.

 class MemorableClass { static final ArrayList list = new ArrayList(100); } 

Skambinkite „ String.intern() ilgai String.intern()

 String str=readString(); // read lengthy string any source db,textbox/jsp etc.. // This will place the string in memory pool from which you can't remove str.intern(); 

(Neuždaryti) atviri srautai (failas, tinklas ir tt)

 try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); } 

Uždaros jungtys

border=0
 try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); } 

Iš JVM šiukšlių kolektoriaus nepasiekiamos sritys , pvz., Atminties, skiriamos naudojant vietinius metodus

Žiniatinklio programose kai kurie objektai yra saugomi taikomojoje srityje tol, kol programa bus aiškiai sustabdyta arba ištrinta.

 getServletContext().setAttribute("SOME_MAP", map); 

Neteisingi arba netinkami JVM parametrai , pvz., „IBM JDK“ noclassgc parametras, kuris neleidžia naudoti nepanaudotų klasės šiukšlių surinkimo

Žr. „ IBM jdk“ nustatymus .

1113
01 июля '11 в 16:33 2011-07-01 16:33 atsakymą pateikė „ Prashant Bhate“ liepos 1 d. 11 d. 16:33 2011-07-01 16:33

Paprastas uždavinys yra naudoti „HashSet“ su neteisingu (arba neegzistuojančiu) hashCode() arba lygiu equals() ir tada toliau pridėti „dublikatus“. Vietoj to, kad būtų ignoruojami dublikatai, kaip ir turėtų būti, rinkinys augs tik ir jūs negalėsite jų pašalinti.

Jei norite, kad šie blogi raktai / elementai būtų pakabinti, galite naudoti statinį lauką

 class BadKey { // no hashCode or equals(); public final String key; public BadKey(String key) { this.key = key; } } Map map = System.getProperties(); map.put(new BadKey("key"), "value"); // Memory leak even if your threads die. 
416
24 июня '11 в 19:16 2011-06-24 19:16 atsakymą pateikė Peter Lawrey birželio 24 d. 11 val. 16:16 2011-06-24 19:16

Žemiau bus nepastebimas atvejis, kai „Java“ nutekės, be standartinio užmiršusių klausytojų atvejo, statinių nuorodų, suklastotų / modifikuojamų klavišų, turinčių klaidų, arba tiesiog srautai, kurie yra įstrigę be jokių galimybių baigti gyvavimo ciklą.

  • File.deleteOnExit() - visada praranda eilutę, jei eilutė yra pagrindinė dalis, nuotėkis yra dar blogesnis (slėpti char []) - „Java 7“ kopijose char[] , taigi vėlesnė versija netaikoma; @Danielis, tačiau nereikia jokių balsų.

Aš sutelksiu dėmesį į srautus, iš esmės parodyti nekontroliuojamų srautų pavojų, net nenorėdamas paliesti sūpuoklių.

  • Runtime.addShutdownHook ir ne ištrinkite ... ir tada net su removeShutdownHook dėl ThreadGroup klasės klaidos, susijusios su nepakeistomis siūlelėmis, jos gali būti nerenkamos, todėl efektyviai nuteka „ThreadGroup“. „JGroup“ turi „GossipRouter“ nuotėkį.

  • Kuriant, bet nepradedant, Thread eina į tą pačią kategoriją kaip ir anksčiau.

  • ContextClassLoader kūrimas paveldi „ ContextClassLoader ir „ AccessControlContext , taip pat „ ThreadGroup ir bet kokias „ ThreadGroup , visos šios nuorodos yra potencialūs nutekėjimai, taip pat visos klasės, kurias pakrauna klasė-krautuvas ir visos statinės nuorodos, ir ja-ja. Poveikis ypač pastebimas su visa „jucExecutor“ infrastruktūra, kuri turi itin paprastą „ ThreadFactory sąsają, tačiau dauguma kūrėjų neturi jokios informacijos apie paslėptą pavojų. Be to, daug bibliotekų veikia pagal temas (per daug pramonei būdingų populiarių bibliotekų).

  • ThreadLocal talpyklos; daugeliu atvejų tai blogis. Esu tikras, kad visi matė nemažai paprastų „ThreadLocal“ talpyklų, taip pat blogų naujienų: jei srautas tęsiasi ilgiau, nei tikėtasi, gyvenimas yra „ClassLoader“ kontekste, tai yra puikus švarus mažas nuotėkis. Nenaudokite „ThreadLocal“ talpyklų, jei tai tikrai būtina.

  • Skambinimas į „ ThreadGroup.destroy() kai „ThreadGroup“ neturi gijų, tačiau vis dar išlaiko vaikų siūlų grupes. Blogas nuotėkis neleis „ThreadGroup“ išimti iš tėvų, bet visi vaikai tampa neįskaitomi.

  • Naudojant „WeakHashMap“ ir reikšmė (in) tiesiogiai nurodo raktą. Sunku rasti be krūvų. Tai taikoma visoms išplėstoms Weak/SoftReference , kurios galėtų išlaikyti galiojančią nuorodą į saugomą objektą.

  • Naudojant java.net.URL su HTTP (S) protokolu ir parsisiunčiant šaltinį iš (!). Šis „ KeepAliveCache sukuria naują temą „ThreadGroup“ sistemoje, kuri nutekina dabartinį siūlų konteksto srauto krautuvą. Srautas sukuriamas pirmuoju prašymu, kai nėra vieno tiesioginio srauto, kad galėtumėte pasisekti arba tiesiog nutekėti. Srautas jau nustatytas „Java 7“, o kodas, kuris sukuria srautą, teisingai pašalina klasės krautuvą. Yra keletas atvejų ( kaip „ImageFetcher“ , taip pat fiksuoti), kuriant panašius siūlus.

  • Naudokite „ InflaterInputStream new java.util.zip.Inflater() perkėlimui konstruktoriuje (pvz., PNGImageDecoder ) ir PNGImageDecoder end() . Na, jei perduosite konstruktorių tik su new , nėra jokios galimybės ... Ir taip, close() skambutis sraute neuždaro inflatoriaus, jei jis perduodamas rankiniu būdu kaip konstruktoriaus parametras. Tai nėra tikrasis nuotėkis, nes jis bus išleistas kaip užbaigėjas ... kai jis mano, kad tai būtina. Iki to momento, kai jis labai blogai valgo savo gimtąją atmintį, jis gali padaryti Linux oom_killer nužudyti procesą nebaudžiamai. Pagrindinė problema yra ta, kad „Java“ užbaigimas yra labai nepatikimas, o G1 pablogėjo iki 7.0.2. Istorijos moralė: kuo greičiau paleiskite savo vietinius išteklius; užbaigėjas yra per blogas.

  • Tuo pačiu atveju su java.util.zip.Deflater . Tai dar blogiau, nes Deflater yra alkanas „Java“ atmintis, t.y. Visada naudoja 15 ir nbsp; bitų (maks.) ir 8 atminties lygiai (9 - maks.), paryškinant keletą šimtų KB vidinės atminties. Laimei, Deflater nėra plačiai naudojamas, ir, kiek žinau, JDK yra be piktnaudžiavimo. Visada skambinkite Deflater end() jei rankiniu būdu sukuriate Deflater arba Inflater . Geriausia paskutiniųjų dviejų dalių: jų nerandate įprastais profiliavimo įrankiais.

(Galiu pridėti dar kelis fragmentus, su kuriais susidūriau paprašius.)

Sėkmės ir saugus; nutekėjimas yra blogis!

248
30 июня '11 в 22:45 2011-06-30 22:45 atsakymas pateikiamas bestsss'u birželio 30 d., 11 val. 10.45 val. 2011-06-30 22:45

Dauguma čia pateiktų pavyzdžių yra „pernelyg sudėtingi“. Tai yra ekstremalūs atvejai. Šiuose pavyzdžiuose programuotojas padarė klaidą (pavyzdžiui, nepaiso lygiaverčių / hashcode) arba buvo įkandytas JVM / JAVA kampiniu atveju (įkeliant klasę su statiniu ...). Manau, kad tai nėra pavyzdys, kurį nori apklaustasis, ar net labiausiai paplitęs atvejis.

Tačiau yra daug paprastesnių atminties nutekėjimo atvejų. Šiukšlių surinkėjas išlaisvina tik tą, kur nėra daugiau nuorodų. Mes, kaip „Java“ kūrėjai, nerūpi atmintis. Mes ją platiname ir prireikus leidžiame juos automatiškai paleisti. Geras

Bet bet koks ilgai trunkantis taikymas paprastai turi bendrą sąlygą. Tai gali būti bet kas, statika, atskiri ... Dažnai ne trivialus taikymas yra linkęs sudaryti sudėtingus objektų grafikus. Tiesiog pamirškite nustatyti, kad nuoroda į nulį arba dažniau pamiršta pašalinti vieną objektą iš kolekcijos, kad sukeltumėte atminties nutekėjimą.

Žinoma, visų rūšių klausytojai (pvz., UI klausytojai), talpyklos, ar bet kokia ilgalaikė bendroji būsena paprastai sukelia atminties nutekėjimą, jei jie nėra tinkamai tvarkomi. Turėtų būti suprantama, kad tai nėra „Java“ atvejis ar problema su šiukšlių surinkėju. Tai yra dizaino problema. Mes projektuojame, kad mes įtraukiame klausytoją į ilgą gyvenimą trunkantį objektą, tačiau mes nepašalinsime klausytojo, kai jo nereikia. Mes talpiname objektus, bet neturime strategijos juos pašalinti iš talpyklos.

Galbūt mes turime sudėtingą grafiką, kuriame saugoma ankstesnė būsena, reikalinga skaičiavimams. Tačiau ankstesnė būsena yra susijusi su valstybe prieš ir pan.

Kaip mes turėtume uždaryti prisijungimus ar SQL failus. Turime nustatyti teisingą nulinę atskaitą ir pašalinti elementus iš kolekcijos. Mes turėsime tinkamas talpyklos strategijas (maksimalią atmintį, elementų skaičių arba laikmačius). Visi klausytoją informuojantys objektai turi pateikti „addListener“ ir „removeListener“ metodus. Ir kai šie pranešėjai nebenaudojami, jie turi išvalyti klausytojų sąrašą.

Atminties nutekėjimas yra tikrai įmanoma ir gana nuspėjamas. Nereikia specialių kalbos funkcijų ar kampinių atvejų. Atminties nutekėjimas - tai požymis, kad kažkas trūksta, arba netgi projektavimo problemos.

174
01 июля '11 в 10:54 2011-07-01 10:54 atsakymą pateikė Nicolas Bousquet liepos 1 d. 11 val. 10:54 2011-07-01 10:54

Atsakymas visiškai priklauso nuo to, ką apklausėjas mano, kad jie paklausė.

Ar galima praktiškai nutekėti „Java“? Žinoma, tai tiesa, ir yra daug kitų atsakymų pavyzdžių.

Tačiau yra keletas meta klausimų, kuriuos galima paklausti?

  • Ar teoriškai „tobula“ java įgyvendinimas yra pažeidžiamas nutekėjimams?
  • Ar kandidatas gali suprasti skirtumą tarp teorijos ir realybės?
  • Ar kandidatas padeda suprasti, kaip šiukšlių surinkimas veikia?
  • Arba, kaip šiukšlių surinkimas turėtų būti tinkamas?
  • Ar jie žino, kad jie gali skambinti kitomis kalbomis per savo sąsajas?
  • Ar jie žino, kad atmintis nuteka šiose kitose kalbose?
  • Ar kandidatas gali žinoti, kas yra atminties valdymas ir kas vyksta už „Java“ scenų?

Aš perskaičiau jūsų meta-klausimą, pvz., „Kokį atsakymą galėčiau naudoti šioje interviu situacijoje?“ Todėl aš norėčiau sutelkti dėmesį į darbo interviu įgūdžius vietoj „Java“. Manau, kad labiau tikėtina, kad pakartosite situaciją, kai nežinote atsakymo į klausimą interviu metu, nei jūs turite būti tinkamoje vietoje, kad žinotumėte, kaip padaryti Java nutekėjimą. Tikiuosi, kad tai padės.

Vienas iš svarbiausių įgūdžių, kuriuos galite sukurti pokalbiui, yra išmokti aktyviai klausytis klausimų ir dirbti su interviu, kad būtų išgauti jų ketinimai. Tai ne tik leidžia atsakyti į jų klausimą, kaip jie nori, bet ir rodo, kad turite svarbių bendravimo įgūdžių. Ir kai kalbama apie daugelio talentingų kūrėjų pasirinkimą, aš samdyti kažką, kuris klauso, galvoja ir supranta, kol jie reaguoja kiekvieną kartą.

142
01 июля '11 в 23:06 2011-07-01 23:06 Atsakymą pateikia „ PlayTank“ liepos 1 d. 11 d. 23:06 2011-07-01 23:06

Žemiau yra gana beprasmiškas pavyzdys, jei nesuprantate JDBC . Arba, bent jau, kadangi JDBC tikisi, kad kūrėjas uždarys „ Connection , „ Statement ir „ ResultSet atvejus prieš juos atsisakydamas arba praradęs nuorodas į juos, o ne pasikliauti finalize įgyvendinimu.

 void doWork() { try { Connection conn = ConnectionFactory.getConnection(); PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query ResultSet rs = stmt.executeQuery(); while(rs.hasNext()) { ... process the result set } } catch(SQLException sqlEx) { log(sqlEx); } } 

Pirmiau minėta problema yra ta, kad Connection objektas nėra uždarytas, todėl fizinis ryšys bus atidarytas, kol bus rodomas šiukšlių surinkėjas, ir mato, kad jis nepasiekiamas. GC paskambins finalize metodu, tačiau yra JDBC tvarkyklės, kurios neįgyvendina finalize , bent jau tokiu pat būdu kaip ir Connection.close . Rezultatas yra tas, kad, nors atmintis bus atkurta dėl neprieinamų objektų gedimo, iš „ Connection objekto susiję ištekliai (įskaitant atmintį) tiesiog negali būti fiksuoti.

Tokiu atveju, kai „ Connection finalize metodas neišvalo visko, iš tikrųjų galite pastebėti, kad fizinis ryšys su duomenų bazės serveriu užtruks kelis šiukšlių surinkimo ciklus, kol duomenų bazės serveris galiausiai sužinos, kad ryšys nėra gyvas (jei yra) ir turėtų būti uždarytas.

Net jei JDBC tvarkyklė turėjo būti finalize , išimtis gali būti pašalinta užbaigimo metu. Gautas elgesys yra tas, kad bet kokia atmintis, susijusi su šiuo metu „neaktyviu“ objektu, nebus fiksuota, nes finalize kad bus vadinama tik vieną kartą.

Pirmiau minėtas išimties aptikimo scenarijus objekto užbaigimo metu yra susijęs su kitu kitu scenarijumi, kuris gali sukelti atnaujintą atminties nutekėjimą. Objekto prisikėlimas dažnai daromas tyčia, sukuriant tvirtą ryšį su objektu, kuris turi būti užpildytas iš kito objekto. Kai prisikėlimo objektas naudojamas netinkamai, jis sukels atminties nuotėkį kartu su kitais atminties šaltinių šaltiniais.

Yra daug daugiau pavyzdžių, kuriuos galite skambinti, pavyzdžiui,

  • List pavyzdžio tvarkymas, kuriame pridedate tik šį sąrašą ir neištrinkite (nors jums reikia atsikratyti nereikalingų elementų) arba
  • Atidarykite Socket arba File s, bet neuždarykite, kai jie nebėra reikalingi (panašus į aukščiau pateiktą pavyzdį su klasės Connection ).
  • Kai įkeliate „Java EE“ programą, neperkelkite singletų. Akivaizdu, kad „Classloader“, kuris įkėlė vienintelę klasę, išsaugos nuorodą į klasę, todėl vienintelis atvejis niekada nebus sudarytas. Įdiegus naują programos egzempliorių, paprastai sukuriamas naujas klasės krautuvas, o senasis klasikinis krautuvas ir toliau išliks dėl singlo.
121
24 июня '11 в 19:21 2011-06-24 19:21 atsakymą pateikė Vineet Reynolds , birželio 24 d., 11 val., 19:21, 2011-06-24 19:21

Tikriausiai vienas iš paprasčiausių galimo atminties nutekėjimo pavyzdžių ir kaip tai išvengti yra ArrayList.remove (int) įgyvendinimas:

 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); elementData[--size] = null; // (!) Let gc do its work return oldValue; } 

Jei ją įgyvendinsite, ar manote, kad išvalyti masyvo elementą, kuris nebėra naudojamas ( elementData[--size] = null )? Ši nuoroda gali išsaugoti tiesioginį objektą ...

107
25 июня '11 в 0:28 2011-06-25 00:28 atsakymas pateikiamas meritonui birželio 25 d. 11 val. 0:28 2011-06-25 00:28

Kiekvieną kartą, kai pasiekiate objektų, kurių jums nereikia, turite atminties nutekėjimą. Žr. „ Atminties nutekėjimo„ Java “programose pavyzdžiai, kaip atminties nutekėjimas pasireiškia„ Java “ir ką su juo galite padaryti.

64
24 июня '11 в 19:18 2011-06-24 19:18 atsakymą Bill Bill Lizard pateikė birželio 24 d. 11 val. 19:18 2011-06-24 19:18

Galite nutekėti atmintį naudodami sun.misc.Unsafe klasę. Tiesą sakant, ši paslaugų klasė naudojama įvairiose standartinėse klasėse (pavyzdžiui, java.nio klasėse). Negalite tiesiogiai sukurti šios klasės egzemplioriaus , tačiau tai galite naudoti refleksija .

Kodas nekompiliuojamas į „Eclipse IDE“ - kompiliuojamas naudojant „ javac komandą (kompiliavimo metu gausite įspėjimų)

 import java.> 
46
11 июля '11 в 15:55 2011-07-11 15:55 atsakymas pateikiamas liepos 11 d. 11 val. 15:55 2011-07-11 15:55

Aš galiu nukopijuoti savo atsakymą iš čia: Lengviausias būdas sukelti atminties nutekėjimą java?

„Atminties nutekėjimas kompiuterių moksle (arba nutekėjimas šiame kontekste) atsiranda, kai kompiuterio programa naudoja atmintį, bet negali ją grąžinti į operacinę sistemą.“ (Vikipedija)

Paprastas atsakymas: jūs negalite. „Java“ atlieka automatinį atminties valdymą ir išlaisvina išteklius, kurių jums nereikia. Jūs negalite jo sustabdyti. Jis visada išlaisvins išteklius. Programose su rankiniu atminties valdymu tai skiriasi. C atminties negalite gauti naudojant malloc (). Norint atlaisvinti atmintį, jums reikia „malloc“ grąžinamo žymeklio ir nemokamo skambučio (). Bet jei jūs nebeturi rodyklės (jis perrašomas arba tarnavimo laikas viršijamas), tada, deja, negalite atlaisvinti šios atminties, todėl jūs turite atminties nutekėjimą.

Visi kiti atsakymai iki šiol mano apibrėžime nėra atminties nutekėjimas. Jie visi stengiasi užpildyti atmintį beprasmių dalykų labai greitai. Bet kuriuo metu jūs vis tiek galite atkurti sukurtus objektus ir atlaisvinti atmintį → NO LEAK. „Acconrad“ atsakymas yra gana glaudus, nors, kaip man reikia pripažinti, nes jos sprendimas yra tiesiog „išmušimas“ šiukšlių surinkėjas, verčia jį į begalinę kilpą.

Ilgas atsakymas: galite gauti atminties nuotėkį rašydami „Java“ biblioteką, naudodami JNI, kuris gali turėti rankinį atminties valdymą ir todėl turi atminties. Jei skambinate į šią biblioteką, jūsų java procesas nuteka. Arba JVM gali turėti klaidų, todėl JVM praranda atmintį. JVM yra tikriausiai klaidų, galbūt net kai kurie iš jų yra žinomi, nes šiukšlių surinkimas nėra nereikšmingas, bet tai vis dar yra klaida. Pagal dizainą tai neįmanoma. Gali būti prašoma Java kodo, kurį sukelia tokia klaida. Atsiprašome, aš nežinau vieno dalyko, ir bet kuriuo atveju tai gali būti ne klaida kitoje „Java“ versijoje.

41
02 июля '11 в 13:23 2011-07-02 13:23 atsakymą pateikė „ yankee“ liepos 2 d. 11 val. 13:23 2011-07-02 13:23

Здесь простой/зловещий через http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .

 public class StringLeaker { private final String muchSmallerString; public StringLeaker() { // Imagine the whole Declaration of Independence here String veryLongString = "We hold these truths to be self-evident..."; // The substring here maintains a reference to the internal char[] // representation of the original string. this.muchSmallerString = veryLongString.substring(0, 1); } }