Memóriaszivárgás

A számítástechnikában a memóriaszivárgás olyan fajta erőforrás-szivárgás, amely bekövetkezésekor a számítógépes program hibásan kezeli a memóriafoglalásokat[1] olyan módon, hogy a már nem szükséges memória nem szabadul fel. Memóriaszivárgás akkor is bekövetkezhet, amikor egy objektum tárolva van a memóriában, viszont a futó kód nem érheti el.[2] A memóriaszivárgás tünetei több más problémáéhoz hasonlóak, és általában csak egy programozó tudja diagnosztizálni, ha hozzáfér a program forráskódjához.

Helyszivárgás akkor következik be, amikor egy számítógépes program több memóriát használ mint szükséges lenne. Szemben a memóriaszivárgással, ahol a kiszivárgott memória soha nem szabadul fel, a helyszivárgás által elfogyasztott memória felszabadul, de a vártnál később.[3]

A memóriaszivárgás gyakran okozója vagy hozzájárul a szoftver elöregedéséhez, mert kimeríti az elérhető rendszer memóriát, ahogy az alkalmazás fut.

Következmények

A memóriaszivárgás lecsökkenti a számítógép teljesítményét az elérhető memória lecsökkentése által. Végső soron, a legrosszabb esetben, túl sok elérhető memória kerül kiosztásra és az összes vagy egy része a rendszernek megáll, vagy a készülék abba hagyja a pontos működést,az alkalmazás megáll, vagy a rendszer jelentősen lelassul a "szemetelés" miatt.

Lehetséges hogy a memóriaszivárgás nem súlyos vagy nem is mutatható ki normál eszközökkel. Modern operációs rendszerekben, az alkalmazás által használt normál memória felszabadul mikor az alkalmazás leáll. Ez azt jelenti, hogy a csak rövid ideig futó programokban előforduló memóriaszivárgást lehet, hogy észre sem vesszük és az csak ritkán súlyos.

Sokkal súlyosabb szivárgások a következők:

  • ahol a program hosszabb ideig fut, és idővel további memóriát emészt fel, mint például háttér feladatok a szervereken, de különösen beágyazott eszközökben, amiket sokszor évekig is futni hagynak
  • ahol gyakran új memória van kiosztva egyszeri feladathoz, mint például számítógépes játék vagy animált videó képkockáinak megjelenítéséhez
  • ahol a program kérhet memóriát – például megosztott memóriát – amely akkor sem szabadul fel, ha a program leáll
  • ahol a memória nagyon korlátozott, mint például beágyazott rendszerben vagy hordozható eszközben, vagy ahol a program megkezdése nagyon nagy mennyiségű memóriát igényel, kevés tartalékot hagyva a szivárgásra
  • ahol a szivárgás az operációs rendszeren vagy a memóriakezelőn belül történik
  • amikor a rendszereszköz- illesztőprogram okozza a szivárgást
  • olyan operációs rendszerben lévő futtatáskor ami nem szabadítja fel automatikusan a memóriát a program leállításakor.

Egy példa a memóriaszivárgásra

A következő pszeudokóddal írt példa megmutatja, hogy miként jön létre egy memóriaszivárgás, és annak hatásait anélkül, hogy bármilyen programozási ismeretre szükségünk lenne. A program ebben az esetben egy nagyon egyszerű szoftver része, amelyet arra terveztek, hogy egy liftet irányítson.A program ezen része fut amikor bárki a lift belsejében megnyomja egy emelet gombját.

Amikor egy gomb megnyomódott:
 Szerezz memóriát, ami felhasználható az emelet számának tárolására
 Tedd az emelet számát a memóriába
 A megadott emeleten vagyunk már?
  Ha igen, akkor nincs más dolgunk: kész
  Különben:
   Várj amíg a lift tétlen
   Menj a megadott emeletre
   Engedd el a memóriát amit az emelet számának a tárolására használtunk

Memóriaszivárgás következne be akkor, ha az kért emelet száma megegyezik azzal amin a lift van; a memória felszabadításának feltétele átugrásra kerülne. Minden egyes alkalommal, amikor ez az eset bekövetkezik, több memória szivárog.

Az ehhez hasonló eseteknek általában nem lenne közvetlen hatása. Az emberek nem gyakran nyomják meg azt a gombot amelyik emeleten már eleve vannak, és más esetekben is, a liftnek lehetséges hogy van elegendő tartalék memóriája hogy ez megtörténhet több száz vagy több ezer alkalommal. A lift azonban egyszer kifogy a memóriából. Ez eltarthat hónapokig vagy évekig is, szóval lehetséges hogy nem fedezik fel ezt a problémát az alapos tesztek ellenére sem.

A következmények kellemetlenek lennének; legjobb esetben a lift nem reagálna arra hogy menjen át más emeletre (például amikor megpróbálja valaki hívni a liftet vagy amikor valaki bent van, és megnyomja egy emelet gombját). Ha a program más részének szüksége van memóriára (például az ajtó kinyitásához és bezárásához tartozó résznek), akkor senki sem léphet be, és ha valaki véletlenül bent van, csapdába esik (feltéve ha az ajtókat nem lehet manuálisan kinyitni).

A memóriaszivárgás a rendszer visszaállításáig tart. Például: ha a lift áramforrását kikapcsolják vagy áramszünet lenne, a program leállna. Az áramellátás újbóli bekapcsolásakor a program újraindulna, és az összes memória ismét elérhető lenne, de a memória szivárgás lassú folyamata a programmal együtt újraindulna, és végül ismét a rendszer helyes működését sértené.

A fenti példában lévő memóriaszivárgás kijavítható, ha a „felszabadítás” műveletet a feltételen kívülre hozzuk:

Amikor egy gomb megnyomódott:
 Szerezz memóriát, ami felhasználható az emelet számának tárolására
 Tedd az emelet számát a memóriába
 A megadott emeleten vagyunk már?
  Ha nem:
   Várj amíg a lift tétlen
   Menj a megadott emeletre
 Engedd el a memóriát amit az emelet számának a tárolására használtunk

Programozási hibák

A memóriaszivárgás gyakori hiba a programozásban, különösen olyan nyelvek használatakor, amiknek nincsen beépített automatikus szemétgyűjtése, mint például a C és a C ++ . Jellemzően memóriaszivárgás akkor következik be, amikor egy dinamikusan kiosztott memória elérhetetlen. A memóriaszivárgási hiba túlnyomó jelenléte számos, az elérhetetlen memória felismerésére szolgáló hibakareső eszköz fejlesztéséhez vezetett. ABoundsChecker, a Deleaker, az IBM Rational Purify, a Valgrind, a Parasoft Insure ++, a Dr. Memory és a memwatch néhánya a népszerűbb memória hibakeresők közül C és C++ programokhoz. A "konzervatív" szemétgyűjtő képesség hozzáadható bármely programozási nyelvhez ami hiányol hasonló beépített funkciókat, és hasonló céllal rendelkező könyvtárak elérhetők C és C++ programokhoz is. A konzervatív gyűjtők megtalálják és visszakövetelik a legtöbb, de nem az összes, elérhetetlen memóriát.

Habár a memóriakezelő képes visszaállítani az elérhetetlen memóriát, nem képes felszabadítani a még elérhető és ezért potenciálisan még mindig hasznos memóriát. A modern memóriakezelők ezért technikákat kínálnak a programozók számára, hogy a memóriát különböző szintű hasznossággal szemantikailag jelöljék meg, amelyek megfelelnek az elérhetőség különböző szintjeinek. A memóriakezelő nem szabadít fel olyan objektumot, amely erősen elérhető. Egy objektum erősen elérhető, ha elérhető közvetlenül erős referencia által, vagy közvetve egy erős referenciák láncolata által. (Az erős referencia olyan referencia, amely a gyenge referenciával ellentétben megakadályozza hogy a szemétgyűjtő az objektumot feldolgozza.). Ennek megelőzése érdekében a fejlesztő felelős a referenciák használat utáni megtisztításáért, jellemzően azzal, hogy null-ra állítja a referenciát, ha már nincs rá igény, és ha szükséges, törli az esetleges eseményfigyelőket amelyek erős hivatkozásokat tartanak fenn az objektumra.

Általában az automatikus memóriakezelés robusztusabb és kényelmesebb a fejlesztőknek, mivel nincsen szükségük felszabadító rutinok végrehajtására, vagy nem kell a tisztítás sorrendje miatt aggódniuk, vagy amiatt hogy egy objektumra továbbra is hivatkoznak-e vagy már nem. A programozóknak könnyebb felismerni, ha egy referenciára nincs már szükség, mint azt, hogy ha egy objektumra már nincs hivatkozás. Az automatikus memóriakezelés azonban terhelheti a teljesítményt, és nem küszöböli ki az összes programozási hibát ami memóriaszivárgáshoz vezet.

RAII

Az RAII, az erőforrás-beszerzés inicializálásának a rövidítése, a probléma általánosan alkalmazott megközelítése C ++, D és Ada programozási nyelvekben. Ez magába foglalja a hatókörű objektumok társítását a megszerzett erőforrásokkal, és az erőforrások automatikus felszabadítását, ha az objektumok már nem tartoznak a hatókörbe. A szemétgyűjtővel ellentétben, a RAII-nak megvan az az előnye, hogy tudja, egy objektum mikor létezik és mikor nem. Hasonlítsa össze a következő C és C ++ példákat:

/* C version */
#include <stdlib.h>

void f(int n)
{

 int* array = calloc(n, sizeof(int));
 do_some_work(array);
 free(array);
 
}
// C++ version
#include <vector>

void f(int n)
{

 std::vector<int> array (n);
 do_some_work(array);
 
}

A C verzió, amint a példában megvalósítottuk, határozott kiosztást igényel; a lista dinamikusan van lefoglalva (legtöbbször halmazból van végrehajtva C-ben), és továbbra is létezik, amíg nincsen kifejezetten felszabadítva.

A C ++ verzió nem igényel határozott kiosztást; mindig automatikusan fog bekövetkezni, amint az objektum lista kikerül a hatókörből, beleértve azt is ha kivételt dobunk. Ezzel elkerülve a szemétgyűjtő rendszerek néhány gyengeségét. Mivel az objektumrombolók fel tudnak szabadítani a memórián kívül más erőforrásokat, az RAII segít megelőzni a bemeneti és kimeneti erőforrások szivárgását a kezelés során, amelyet a szemétgyűjtés nem kezel kecsesen. Ezek tartalmaznak nyitott fájlokat, nyitott ablakokat, felhasználói értesítéseket, objektumokat egy grafikus rajzkönyvtárban, szál szinkronizációs primitíveket mint például kritikus szakaszokat, hálózati kapcsolatokat, valamint a Windows rendszerleíró adatbázishoz vagy egy másik adatbázishoz való kapcsolatokat.

Azonban, a RAII helyes használata nem minden esetben könnyű és megvannak a maga buktatói. Például, ha valaki nem elég óvatos, akkor lehet, hogy létrehoz függő mutatókat (vagy referenciákat) az adatok hivatkozással történő visszaadásával csak akkor törölhetők, ha az adatokat tartalmazó objektum kikerül az alkalmazási hatókörből.

A D az RAII és a szemétgyűjtés kombinációját használja, automatikus megsemmisítést alkalmazva, amikor egyértelmű, hogy egy objektumhoz nem lehet hozzáférni az eredeti hatókörén kívül, és szemétgyűjtést egyéb esetekben.

Referenciaszámlálás és ciklikus referenciák

A modernebb szemétgyűjtő sémákat gyakran az elérhetőség fogalmára alapozzák – ha nincs használható referencia a kérdéses memóriához, akkor gyűjtésre kerülhet. Más szemétgyűjtő sémák referenciaszámításon alapulhatnak, ahol az objektum felelős annak nyomon követéséről, hogy hány referencia mutat rá. Ha a szám nullára csökken, akkor az objektum várhatóan felszabadítja magát, és lehetővé teszi a memória visszanyerését. A hiba ezzel a modellel hogy képtelen megbirkózni a ciklikus referenciákkal, és ezért manapság programozók nagy része felkészült arra, hogy vállalja a költségesebb jelölési és söprési típusú rendszerek terheit.

A következő Visual Basic kód a kronologikus referenciaszámolásos memóriaszivárgást illusztrálja:

Dim A, B
Set A = CreateObject("Some.Thing")
Set B = CreateObject("Some.Thing")
' Ezen a ponton, mindkét objektumnak van egy referenciája,

Set A.member = B
Set B.member = A
' Most már mindkettőnek van két referenciája.

Set A = Nothing ' Mág mindig kijuthatunk belőle...

Set B = Nothing ' És most már memóriaszivárgásod van!

End

A gyakorlatban ezt az egyszerű példát azonnal felismernék és kijavítanák. A legtöbb valódi esetben, a referenciák ciklusa át terjed több mint két objektumra és nehezebb a felfedezése.

Az ilyen típusú szivárgások jól ismert példája az AJAX programozási technikák megjelenésével a webböngészőkben a lejárt hallgatói problémában került előtérbe. Az a JavaScript-kód, amely egy DOM elemet társított egy eseménykezelőhöz, és a kilépés előtt nem sikerült eltávolítania a hivatkozást, memóriát szivárogtat (az AJAX weboldalak sokkal tovább tartanak egy DOM-ot életben mint egy tradicionális weboldal, úgyhogy a szivárgás sokkal nyilvánvalóbb volt.) .

Hatások

Ha egy programnak memóriaszivárgása van és a memória használata fokozatosan emelkedik, akkor általában nincsen azonnali tünete. Minden fizikai rendszer véges mennyiségű memóriával rendelkezik, és ha a memóriaszivárgás nincs feltartóztatva (például a szivárgó program újraindításával), akkor az idővel problémákat okoz.

A legtöbb modern személyi számítógép rendelkezik mind fő memóriával, ami fizikailag a RAM mikrochip-ben van elhelyezve, mind a másodlagos tárhellyel, mint például merevlemez vagy ssd. A memória kiosztás dinamikus – minden folyamat annyi memóriát kap amennyit kér. Az aktív oldalak átkerülnek a fő memóriába a gyors elérés érdekében; az inaktív oldalak átkerülnek a másodlagos tárolóba, hogy szükség esetén helyet kapjanak. Amikor egyetlen folyamat elkezd nagy mennyiségű memóriát fogyasztani, általában egyre több és többet foglal el a fő memóriából, ezzel kiszorítva más programokat a másodlagos memóriába - általában jelentősen lassítva ezzel a rendszer teljesítményét. Még akkor is, ha a szivárgó program megszűnik, eltarthat egy ideig, mire más programok visszacserélődnek a fő memóriába, és a teljesítmény normalizálódik.

Amikor a rendszer összes memóriája kiürül (függetlenül attól, hogy van-e virtuális memória vagy csak fő memória, például beágyazott rendszer esetén), akkor minden további kísérlet a memória kiosztására sikertelen lesz. Ez általában azt okozza, hogy a memóriafoglalást megkísérlő program leállítja magát, vagy szegmentálási hibát generál. Néhány programot úgy terveztek hogy visszaálljon ebből a szituációból (lehetségesen az előre lefoglalt memória visszaállításával). Nem feltétlenül az a program szivárogtatja a memóriát ami az először megtapasztalja a memória kifogyását.

Néhány többfeladatos operációs rendszer rendelkezik speciális mechanizmusokkal hogy megbirkózzanak a kifogyott memória állapotokkal, mint például véletlenszerűen megszüntetni folyamatokat (ami érintheti az "ártatlan" folyamatokat), vagy a memória legnagyobb folyamatának megszüntetése (ami valószínűleg az amelyik a problémát okozza). Néhány operációs rendszernek van egy memória/folyamat korlátja, hogy megelőzzék, hogy bármelyik program felfalja a rendszer összes memóriáját. Ennek az felosztásnak az a hátránya, hogy az operációs rendszert néha át kell állítani, hogy lehetővé tegye a nagy memóriát igénylő programok megfelelő működését, mint például amik grafikával, videóval, vagy tudományos számításokkal foglalkoznak.

A memória kihasználtság "fűrészfog" mintázata: a felhasznált memória hirtelen csökkenése a memória szivárgásának tünete.

Ha a memóriaszivárgás a rendszermagban van, akkor az valószínűleg magának az operációs rendszernek a meghibásodását fogja okozni. A kifinomult memóriakezelés nélküli számítógépek, mint például a beágyazott rendszerek, szintén meghibásodhatnak a hosszú ideig tartó memóriaszivárgás miatt.

Nyilvánosan elérhető rendszerek, mint például a webszerverek vagy az útválasztók fogékonyak a szolgáltatás megtagadására irányuló támadásokra, ha a támadó felfedez egy műveletsorozatot ami szivárgást vált ki. Az ilyen sorozatot kihasználásnak nevezik.

A memória kihasználásának "fűrészfog" mintája utalhat arra, hogy a memóriaszivárgás az alkalmazáson belül van, különösen ha az applikáció újraindulásával függőleges esés következik be. Óvatosan kell azonban eljárni, mert a szemétgyűjtő pontok is hasonló mintákat okoznak és a halmaz egészséges működését mutatnák.

Egyéb memóriafogyasztók

Vegyük figyelembe, hogy a folyamatosan növekvő memóriahasználat nem feltétlenül bizonyíték egy memóriaszivárgásra. Néhány applikáció egyre nagyobb mennyiségű információt fog tárolni (pl.: gyorsítótárat ). Ha a gyorsítótár képes annyira nagyra nőni hogy problémát okozzon, ez lehet programozási vagy tervezési hiba, de nem memóriaszivárgás mivel az információ névlegesen használatban marad. Más esetekben a programok indokolatlanul sok memóriát igényelhetnek, mivel a programozó feltételezte, hogy a memória mindig elegendő egy adott feladat elvégzéséhez; például egy grafikus fájlfeldolgozó egy képfájl teljes tartalmának elolvasásával indul és mindent a memóriában tárolva, nem működőképes amikor egy nagyon nagy fájl meghaladja a rendelkezésre álló memóriát.

Másképpen fogalmazva, a memóriaszivárgás egy bizonyos programozási hibából származik, és a program kód hozzáférése nélkül, a tünetek tapasztalata után csak gyanakodni lehet arra hogy memóriaszivárgás áll a háttérben. Ahol belső tudás nem létezik ott olyan kifejezés használata, mint a "folyamatosan növekszik a memóriahasználat" jobb lenne.

Egy egyszerű példa C-ben

A következő C eljárás szándékosan memóriát szivárogtat azzal hogy a lefoglalt memóriához elveszíti a mutatót. Azt mondhatjuk, hogy a szivárgás akkor következik be, mikor az "a" mutató kijön a hatókörből, azaz amikor a function_which_allocates () visszatér az "a" felszabadítása nélkül.

#include <stdlib.h>

void function_which_allocates(void) {
  /* hozzárendel egy 45 valós számból álló tömböt */
  float *a = malloc(sizeof(float) * 45);

  /* további kód 'a' használatával */

  /* visszatérés a main-be, elfelejtve felszabadítani azt a memóriát, amire 
  a malloc-ot használtuk */
  
}

int main(void) {
  function_which_allocates();

  /* az 'a'mutató nem létezik többé, ezért nem lehet felszabadítani a memóriáját,
   de a memória még mindig le van foglalva. Szivárgás következett be. */
}

Fordítás

Ez a szócikk részben vagy egészben a Memory leak című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

Jegyzetek

  1. Crockford: JScript Memory Leaks. [2012. december 7-i dátummal az eredetiből archiválva]. (Hozzáférés: 2012. november 6.)
  2. Creating a memory leak with Java. Stack Overflow. (Hozzáférés: 2013. június 14.)
  3. Mitchell: Leaking Space. (Hozzáférés: 2017. május 27.)

További információk

Kapcsolódó szócikk