Roskakokoelma

Roskien kerääminen [ 1] ohjelmoinnissa on automaattisen  muistinhallinnan muoto . Erityinen prosessi , jota kutsutaan roskienkerääjäksi , vapauttaa ajoittain muistia poistamalla siitä tarpeettomiksi tulleita esineitä . 

Automaattinen roskienkeruu parantaa muistin käytön turvallisuutta .

Historia

John McCarthy käytti roskakeräystä ensimmäisen kerran vuonna 1959 ohjelmointiympäristössä kehittämällään toiminnallisella ohjelmointikielellä Lisp . Myöhemmin sitä käytettiin muissa ohjelmointijärjestelmissä ja kielissä, pääasiassa toiminnallisissa ja loogisissa . Roskienkeruun tarve tämäntyyppisillä kielillä johtuu siitä, että tällaisten kielten rakenne tekee äärimmäisen hankalaksi seurata muistissa olevien objektien elinikää ja hallita sitä manuaalisesti. Näillä kielillä laajalti käytettyjä luetteloita ja niihin perustuvia monimutkaisia ​​tietorakenteita luodaan jatkuvasti, lisätään, laajennetaan, kopioidaan ohjelmien toiminnan aikana, ja objektin poiston hetkiä on vaikea määrittää oikein.

Teolliset proseduuri- ja objektikielet eivät käyttäneet roskien keräämistä pitkään aikaan. Etusijalle annettiin manuaalinen muistinhallinta, koska se on tehokkaampi ja ennakoitavampi. Mutta 1980-luvun toiselta puoliskolta lähtien roskienkeräystekniikkaa on käytetty sekä ohjelmointikielissä ( pakottava ) että objektiohjelmointikielissä, ja 1990-luvun jälkipuoliskosta lähtien yhä useammat luodut sovellusohjelmointiin keskittyneet kielet ja ympäristöt ovat olleet mm. keräysmekanismin roska joko ainoana tai yhtenä käytettävissä olevista dynaamisista muistinhallintamekanismeista. Sitä käytetään tällä hetkellä Oberon , Java , Python , Ruby , C# , D , F# , Go ja muilla kielillä.

Manuaalinen muistinhallinta

Perinteinen tapa ohjekielille hallita muistia on manuaalinen. Sen olemus on seuraava:

Jokaisella kielellä, joka sallii objektien luomisen dynaamiseen muistiin, on kaksi mahdollista ongelmaa: roikkuvat viittaukset ja muistivuotoja .

Roikkuvat linkit

Riippuva  osoitin on viittaus objektiin, joka on jo poistettu muistista. Objektin poistamisen jälkeen kaikki ohjelmaan tallennetut viittaukset siihen muuttuvat "roikkuviksi". Objektin aiemmin käyttämä muisti voidaan luovuttaa käyttöjärjestelmälle ja tulla käyttökelvottomaksi, tai sitä voidaan käyttää uuden objektin varaamiseen samassa ohjelmassa. Ensimmäisessä tapauksessa yritys päästä "riippuvaan" linkkiin laukaisee muistin suojausmekanismin ja kaataa ohjelman, ja toisessa tapauksessa se johtaa arvaamattomiin seurauksiin.

Roikkuvien viitteiden esiintyminen johtuu yleensä kohteen käyttöiän virheellisestä arvioinnista: ohjelmoija kutsuu komennon poistaa objekti ennen sen käytön lopettamista.

Muisti vuotaa

Luomalla objektin dynaamiseen muistiin ohjelmoija ei saa poistaa sitä käytön päätyttyä. Jos objektiin viittaavalle muuttujalle annetaan uusi arvo eikä objektiin ole muita viittauksia, siitä tulee ohjelmallisesti käyttökelvoton, mutta se vie edelleen muistia, koska poisto-komentoa ei kutsuttu. Tätä tilannetta kutsutaan muistivuotoksi . 

Jos ohjelmassa luodaan jatkuvasti objekteja, joihin viittaukset katoavat, muistivuoto ilmenee käytetyn muistin määrän asteittaisena kasvuna; jos ohjelma on käynnissä pitkään, sen käyttämän muistin määrä kasvaa jatkuvasti, ja jonkin ajan kuluttua järjestelmä hidastuu huomattavasti (johtuen tarpeesta käyttää swap -toimintoa mihin tahansa muistin varaukseen ) tai ohjelma käyttää loppuun käytettävissä olevan osoitetilan ja päättyy virheeseen.

Roskien keräysmekanismi

Jos tietokoneen muisti olisi ääretön , olisi mahdollista yksinkertaisesti jättää tarpeettomia esineitä muistiin. Automaattinen muistin hallinta roskienkeräyksellä - tällaisen äärettömän tietokoneen emulointi äärellisessä muistissa [2] . Monet roskakeräilijöiden rajoituksista (ei ole takeita siitä, että viimeistelyohjelma suoritetaan; se hallitsee vain muistia, ei muita resursseja) johtuvat tästä metaforasta.

Perusperiaatteet

Roskiin kerätyssä järjestelmässä ohjelman suoritusympäristön vastuulla on vapauttaa muisti. Ohjelmoija luo vain dynaamisia objekteja ja käyttää niitä, hän ei välttämättä välitä objektien poistamisesta, koska ympäristö tekee sen hänen puolestaan. Tätä varten ajonaikaiseen ympäristöön sisältyy erityinen ohjelmistomoduuli, jota kutsutaan "jätteenkeräjäksi". Tämä moduuli toimii säännöllisesti, määrittää, mitkä dynaamiseen muistiin luoduista objekteista eivät ole enää käytössä, ja vapauttaa niiden käyttämän muistin.

Jätteenkeräimen käyttötaajuus määräytyy järjestelmän ominaisuuksien mukaan. Keräilijä voi toimia taustalla alkaen, kun ohjelma ei ole aktiivinen (esimerkiksi kun ohjelma on käyttämättömänä odottamassa käyttäjän syöttöä). Roskankerääjä toimii ehdoitta pysäyttäen ohjelman suorittamisen ( Stop -the-  world ), kun seuraavaa muistinvaraustoimintoa ei voida suorittaa, koska kaikki käytettävissä oleva muisti on käytetty loppuun. Kun muisti on vapautettu, keskeytynyttä muistinvaraustoimintoa jatketaan ja ohjelman suorittamista jatketaan. Jos käy ilmi, että muistia ei voida vapauttaa, ajonaika päättää ohjelman "Muisti loppu" -virheilmoituksella.

Objektin saavutettavuus

Olisi parasta poistaa muistista objektit, joihin ei päästä käsiksi ohjelman jatkotoiminnan aikana. Tällaisten objektien tunnistaminen on kuitenkin mahdotonta, koska se pelkistyy algoritmisesti ratkaisemattomaksi pysäytysongelmaksi (tätä varten riittää oletus, että jotain objektia X käytetään silloin ja vain, jos ohjelma P suorittaa onnistuneesti ). Siksi jätteenkeräilijät käyttävät varovaisia ​​arvioita varmistaakseen, että esinettä ei käytetä tulevaisuudessa.

Yleensä kriteeri, että objekti on edelleen käytössä, on viittausten olemassaolo siihen: jos järjestelmässä ei ole enää viittauksia tähän objektiin, ohjelma ei tietenkään voi enää käyttää sitä, ja siksi se voidaan käyttää. poistettu. Tätä kriteeriä käyttävät useimmat nykyaikaiset roskankerääjät, ja sitä kutsutaan myös objektien saavutettavuudeksi . Se ei ole teoreettisesti paras, sillä sen mukaan tavoitettavissa oleviin objekteihin kuuluvat myös ne kohteet, joita ei koskaan käytetä, mutta joihin on vielä viittauksia, mutta se takaa suojan "roikkuvien" viitteiden ilmaantumista vastaan ​​ja voidaan toteuttaa varsin tehokkaasti. .

Epävirallisesti saavutettavalle objektille voidaan antaa seuraava rekursiivinen määritelmä:

Lippualgoritmi

Yksinkertainen algoritmi saavutettavien kohteiden määrittämiseksi, Mark and Sweep -algoritmi, on seuraava:

  • jokaiselle objektille tallennetaan bitti, joka osoittaa, onko tämä objekti tavoitettavissa ohjelmasta vai ei;
  • aluksi kaikki objektit, paitsi juuriobjektit, merkitään tavoittamattomiksi;
  • ovat rekursiivisesti skannatut ja merkitty saavutettaviksi kohteiksi, joita ei ole vielä merkitty ja joihin pääsee viitteillä juuriobjekteista;
  • ne kohteet, joille saavutettavuusbittiä ei ole asetettu, katsotaan saavuttamattomiksi.

Jos kaksi tai useampi objekti viittaa toisiinsa, mutta yhteenkään näistä objekteista ei viitata ulkopuolelta, koko ryhmää pidetään saavuttamattomana. Tämän algoritmin avulla voit taata sellaisten objektiryhmien poistamisen, joiden käyttö on päättynyt, mutta joissa on linkkejä toisiinsa. Tällaisia ​​ryhmiä kutsutaan usein "eristyssaariksi".

Viitelaskenta-algoritmi

Toinen tavoitettavuusalgoritmin muunnelma on tavallinen viitteiden laskenta . Sen käyttö hidastaa viitteiden määritystoimintoja, mutta tavoitettavien kohteiden määrittely on triviaali - nämä ovat kaikki objekteja, joiden viitelukuarvo ylittää nollan. Ilman lisäselvennyksiä tämä algoritmi, toisin kuin edellinen, ei poista vanhentuneiden objektien syklisesti suljettuja ketjuja, joilla on linkkejä toisiinsa.

Roskien keräysstrategiat

Kun joukko tavoittamattomia kohteita on määritetty, roskankerääjä voi purkaa niiden varaaman muistin ja jättää loput ennalleen. On myös mahdollista siirtää kaikki jäljellä olevat kohteet tai osa niistä toisille muistialueille muistin vapauttamisen jälkeen, jolloin kaikki viittaukset niihin päivitetään. Näitä kahta toteutusta kutsutaan ei -relocating ja relocating vastaavasti .

Molemmilla strategioilla on sekä etuja että haittoja.

Muistin varaus ja purkunopeus Siirtymätön roskankerääjä vapauttaa muistia nopeammin (koska se vain merkitsee sopivat muistilohkot vapaiksi), mutta käyttää enemmän aikaa sen varaamiseen (koska muisti pirstoutuu ja varauksen täytyy löytää oikea määrä sopivan kokoisia lohkoja muistista ). Siirtokeräimellä roskien kerääminen kestää suhteellisen kauemmin (muistin eheyttäminen ja kaikkien viittausten muuttaminen siirrettäviin objekteihin vie ylimääräistä aikaa ), mutta siirto mahdollistaa erittäin yksinkertaisen ja nopean ( O(1) ) muistin allokointialgoritmin. Eheytyksen aikana objektit siirretään siten, että kaikki muisti jaetaan kahdelle suurelle alueelle - varattuun ja vapaaseen, ja osoitin niiden reunaan tallennetaan. Uuden muistin varaamiseen riittää vain tämän rajan siirtäminen palauttamalla pala vapaan muistin alusta. Nopeus päästä käsiksi dynaamisessa muistissa oleviin objekteihin Kohteet, joiden kentät ovat jaetut, voidaan sijoittaa lähelle toisiaan muistissa siirtokeräimellä. Silloin ne ovat todennäköisemmin prosessorin välimuistissa samaan aikaan, mikä vähentää pääsyn määrää suhteellisen hitaalle RAM -muistille . Yhteensopivuus ulkomaisen koodin kanssa Siirrettävä roskankeräys aiheuttaa ongelmia käytettäessä koodia, jota ei hallita automaattisen muistinhallinnan avulla ( tällaista koodia kutsutaan perinteisessä terminologiassa vieraaksi tai Microsoftin terminologiassa hallitsemattomaksi ) .  Osoitin muistiin, joka on varattu järjestelmälle, jossa on ei-uudelleensijoittuva keräilijä, voidaan yksinkertaisesti siirtää vieraaseen koodiin käytettäväksi pitäen samalla vähintään yhtä säännöllistä viittausta objektiin, jotta kerääjä ei poista sitä. Liikkuva keräilijä muuttaa objektien paikkaa muistissa, muuttaen synkronisesti kaikkia viittauksia niihin, mutta se ei voi muuttaa viittauksia vieraassa koodissa, minkä seurauksena vieraaseen koodiin välitetyt viittaukset kohteen siirron jälkeen muuttuvat virheellisiksi. Vieraan koodin kanssa työskentelemiseen käytetään erilaisia ​​​​erikoistekniikoita, esimerkiksi kiinnitys  on kohteen nimenomainen estäminen, joka estää sen liikkumisen roskien keräämisen aikana. 

Objektien sukupolvia

Kuten käytäntö osoittaa, äskettäin luoduista objekteista tulee useammin saavuttamattomia kuin pitkään olemassa olevista objekteista. Tämän mallin mukaisesti monet nykyaikaiset roskankerääjät jakavat kaikki esineet useisiin sukupolviin  - sarjaan esineitä, joilla on lyhyt käyttöikä. Heti kun jollekin sukupolvelle varattu muisti loppuu, tässä sukupolvessa ja kaikissa "nuoremmissa" sukupolvissa etsitään tavoittamattomia esineitä. Kaikki ne poistetaan, ja loput siirretään "vanhemmalle" sukupolvelle.

Sukupolvien käyttö lyhentää roskien keräysjakson aikaa vähentämällä keräyksen aikana tarkistettavien objektien määrää, mutta tämä menetelmä vaatii ajonaikaa, jotta voidaan pitää kirjaa eri sukupolvien välisistä viitteistä.

Muut mekanismit

muuttumattomat esineet _ _  Ohjelmointikielen säännöt voivat sanoa, että erityisellä tavalla tai tietyntyyppiset objektit ovat pohjimmiltaan muuttumattomia. Nämä ovat esimerkiksi merkkijonoja Javassa ja useissa muissa kielissä. Muuttumattomuustietojen ansiosta muistinhallintajärjestelmä voi säästää tilaa. Esimerkiksi kun merkkijonomuuttujalle annetaan arvo "Hello", merkkijono sijoitetaan muistiin ja muuttuja saa viitteen siihen. Mutta jos toinen muuttuja alustetaan myöhemmin samalla merkkijonolla, järjestelmä löytää aiemmin luodun merkkijonon "Hello"muistista ja antaa sille viitteen toiselle muuttujalle sen sijaan, että se varaisi merkkijonon uudelleen muistissa. Koska merkkijono on pohjimmiltaan muuttumaton, tällainen päätös ei vaikuta ohjelman logiikkaan millään tavalla, mutta merkkijono ei kopioidu muistiin, vaikka sitä käytetään kuinka monta kertaa. Ja vasta kun kaikki viittaukset siihen poistetaan, roskankerääjä tuhoaa linjan. Pääsääntöisesti tällaiset vakioobjektit tallennetaan erityisesti varatuille muistialueille, joita kutsutaan "pooliksi" (muuttumattomien merkkijonojen tallennusalue on "merkkijonovarasto"), jotta voidaan käyttää tehokasta työtä, jonka kanssa voidaan käyttää melko erityisiä algoritmeja. Finalisoijat Viimeistely on koodi, joka suoritetaan automaattisesti juuri ennen kuin roskankerääjä poistaa objektin muistista. Viimeistelijöitä käytetään tarkistamaan, onko objekti siivottu, ja vapauttamaan ylimääräistä muistia, jos se on varattu objektin luomisen tai käytön aikana, ohittaen muistinhallintajärjestelmän. Ammattitaitoiset ohjelmoijat yrittävät usein käyttää viimeistelijöitä vapauttaakseen tiedostoja , verkkopistokkeita ja muita objektien käyttämiä järjestelmäresursseja . Tämä on erittäin huono käytäntö: koska objektin roskien kerääminen riippuu käytettävissä olevan muistin määrästä ja ohjelman käyttämästä muistista, on mahdotonta ennustaa, milloin viimeistelyä kutsutaan ja kutsutaanko sitä ollenkaan. Viimeistelyt eivät sovellu muiden järjestelmäresurssien vapauttamiseen kuin RAM-muistiin. ohjelmoijan on suljettava tiedostot tai pistokkeet manuaalisesti komennolla, kuten close(), kun objekti ei ole enää käytössä.

Kieli- ja järjestelmävaatimukset

Jotta ohjelma voisi käyttää roskienkeruuta, sen on täytyttävä useita ehtoja, jotka liittyvät kieleen, ajonaikaiseen ympäristöön ja itse tehtävään.

Kestoajan tarve roskienkerääjän kanssa Luonnollisesti roskien kerääminen vaatii dynaamisen ympäristön, joka tukee ohjelman suorittamista, ja roskankeräimen läsnäoloa tässä ympäristössä. Tulkituilla kielillä tai virtuaalikoneen tavukoodiin käännetyillä kielillä roskankerääjä voidaan sisällyttää kieli- tai tavukoodin tulkkikoodiin, mutta objektikoodiin käännetyissä kielissä roskankerääjä on pakotettu osaksi järjestelmää. kirjasto, joka linkitetään (staattisesti tai dynaamisesti) ohjelmakoodiin suoritettavaa tiedostoa luotaessa, mikä lisää ohjelman kokoa ja latausaikaa. Ohjelmointikielen tuki Roskankerääjä voi toimia kunnolla vain, jos se pystyy seuraamaan tarkasti kaikki viittaukset kaikkiin luotuihin objekteihin. Ilmeisesti, jos kieli sallii viitteiden (osoittimien) muuntamisen muihin tietotyyppeihin (kokonaisluvut, tavutaulukot jne.), kuten C / C++ , tällaisten muunnettujen viitteiden käyttöä on mahdotonta seurata ja roskien kerääminen muuttuu merkityksettömäksi. - se ei suojaa "riippuvilta" linkeiltä ja muistivuotoilta. Siksi roskienkeräyssuuntaiset kielet rajoittavat yleensä merkittävästi vapautta käyttää osoittimia, osoitearitmetiikkaa, osoitintyyppien muuntamista muihin tietotyyppeihin. Joillakin niistä ei ole lainkaan "osoitin"-tietotyyppiä, osalla on, mutta ne eivät salli tyyppimuunnoksia tai -muutoksia. Ohjelmien toiminnassa esiintyvien lyhytaikaisten viivästysten tekninen hyväksyttävyys Jätteitä kerätään säännöllisesti, yleensä tuntemattomina aikoina. Jos ohjelman keskeyttäminen jätteenkeruun aikaan verrattavaksi ajaksi voi johtaa kriittisiin virheisiin , on luonnollisesti mahdotonta käyttää roskien keräämistä tällaisessa tilanteessa. On jonkin verran vapaata muistia Mitä enemmän muistia ajon aikana on käytettävissä, sitä harvemmin roskankeräys toimii ja sitä tehokkaampi se on. Jätteenkeräimen käyttäminen järjestelmässä, jossa roskankerääjän käytettävissä olevan muistin määrä lähestyy ohjelman huipputarvetta, voi olla tehotonta ja tuhlaavaa. Mitä vähemmän muistia on liikaa, sitä useammin keräilijä toimii ja sitä enemmän sen suorittamiseen kuluu aikaa. Ohjelman suorituskyvyn heikkeneminen tässä tilassa voi olla liian merkittävä.

Käyttöongelmat

Toisin kuin usein sanotaan, roskien kerääminen ei vapauta ohjelmoijaa kaikista muistinhallintaongelmista.

Vapauta muut objektin käyttämät resurssit Dynaamisen muistin lisäksi objekti voi omistaa muita resursseja, joskus muistia arvokkaampia. Jos objekti avaa tiedoston luodessaan, sen on suljettava se käytön päätyttyä; jos se muodostaa yhteyden DBMS:ään, sen on katkaistava yhteys. Manuaalisen muistinhallinnan järjestelmissä tämä tehdään välittömästi ennen objektin poistamista muistista, useimmiten vastaavien objektien destruktoreissa . Roskienkeräysjärjestelmissä on yleensä mahdollista suorittaa jokin koodi juuri ennen objektin poistamista, ns. viimeistelijöitä , mutta ne eivät sovellu resurssien vapauttamiseen, koska poiston hetkeä ei tiedetä etukäteen ja se voi kääntyä. että resurssi vapautuu paljon myöhemmin kuin objektin käyttö lakkaa. Tällaisissa tapauksissa ohjelmoijan on silti seurattava kohteen käyttöä manuaalisesti ja suoritettava manuaalisesti toimenpiteitä kohteen varaamien resurssien vapauttamiseksi. C# : ssa on tähän tarkoitukseen tarkoitettu käyttöliittymä IDisposable, Java  - AutoCloseable. Muistivuoto Jätteenkeräysjärjestelmissä voi myös esiintyä muistivuotoja, vaikka niillä on hieman erilainen luonne. Viittaus käyttämättömään kohteeseen voidaan tallentaa toiseen käytössä olevaan objektiin, ja siitä tulee eräänlainen "ankkuri", joka pitää tarpeettoman kohteen muistissa. Luotu objekti esimerkiksi lisätään aputoimintoihin käytettävään kokoelmaan, jonka jälkeen sen käyttö lakkaa, mutta sitä ei poisteta kokoelmasta. Kokoelmassa on viite, esine pysyy tavoitettavissa eikä sitä kerätä roskaa. Tuloksena on sama muistivuoto. Tällaisten ongelmien poistamiseksi ajonaika voi tukea erityistä ominaisuutta - ns. heikkoja viittauksia . Heikot viittaukset eivät pidä objektia ja muuttuvat nullheti objektin katoamisen jälkeen - joten koodi on varauduttava siihen, että jonain päivänä viittaus osoittaa ei minnekään. Tehokkuuden menetys toistuvan muistin varauksen ja purkamisen yhteydessä Jotkut toiminnot, jotka ovat melko vaarattomia järjestelmissä, joissa on manuaalinen muistinhallinta, voivat aiheuttaa suhteettoman suuria ylimääräisiä kustannuksia järjestelmissä, joissa on roskienkeräys. Alla on klassinen esimerkki tällaisesta ongelmasta. String out = "" ; // Oletetaan, että merkkijonot sisältävät suuren määrän lyhyitä merkkijonoja, // joista sinun täytyy kerätä yksi suuri merkkijono out-muuttujaan. for ( String str : merkkijonot ) { out += str ; // Tämä koodi luo // uuden merkkijonomuuttujan joka iteraatiossa ja varaa sille muistia. } Tämä Java-koodi näyttää siltä, ​​että kerran luotu out-muuttuja "lisätään" uudella rivillä joka kerta silmukassa. Itse asiassa Java-merkkijonot ovat muuttumattomia, joten tässä koodissa tapahtuu jokaisessa silmukassa seuraavaa:
  1. Luo uusi riittävän pitkä merkkijonomuuttuja.
  2. Kopioi ulos vanhan sisällön uuteen muuttujaan.
  3. Kopioi uuteen sisältömuuttujaan str.
  4. Out-muuttujan viittaus uuteen merkkijonomuuttujaan.
Tässä tapauksessa joka kerta, kun muistilohko, joka aiemmin sisälsi out-muuttujan arvon, poistuu käytöstä ja odottaa, kunnes roskakeräys käynnistyy. Jos 100 100 merkin merkkijonoa yhdistetään tällä tavalla, tähän toimintoon varataan yhteensä yli 500 000 tavua muistia, eli 50 kertaa enemmän kuin lopullisen "pitkän" merkkijonon koko. Tällaiset toiminnot, kun usein luodaan riittävän suuria esineitä muistissa ja lopetetaan heti niiden käyttö, johtavat erittäin nopeaan tuottamattomaan kaiken käytettävissä olevan muistin täyttymiseen ja roskakeräimen toistuvaan käynnistämiseen, mikä voi tietyissä olosuhteissa hidastaa huomattavasti ohjelmaa tai ainakin vaatia sen varaamista riittämättömän suuren muistimäärän toimimiseen. Tällaisten ongelmien välttämiseksi ohjelmoijalla on oltava hyvä käsitys automaattisesta muistinhallintamekanismista. Myös erikoiskeinoja voidaan joskus käyttää vaarallisten toimintojen tehokkaaseen suorittamiseen. Joten yllä olevan esimerkin optimoimiseksi sinun on käytettävä erityistä StringBuilder-luokkaa, jonka avulla voit varata muistia välittömästi koko merkkijonolle yhdellä toiminnolla ja lisätä silmukassa vain seuraavan fragmentin tämän merkkijonon loppuun. Vuorovaikutusongelmat vieraan koodin kanssa ja suora työ fyysisen muistin kanssa Käytännön ohjelmoinnissa kielillä, joissa on jätekeräys, on melkein mahdotonta tehdä ilman vuorovaikutusta ns. vieraan koodin kanssa: käyttöjärjestelmän API:t, laiteajurit, muilla kielillä kirjoitetut ulkoiset ohjelmamoduulit eivät ole roskakeräimen hallinnassa. . Joskus on välttämätöntä työskennellä suoraan tietokoneen fyysisen muistin kanssa; myös muistinhallintajärjestelmä rajoittaa tätä, jos ollenkaan. Vuorovaikutus vieraan koodin kanssa on mahdollista kahdella tavalla: joko vieraan koodin kääre kirjoitetaan matalan tason kielellä (yleensä C-kielellä), piilotetaan matalan tason yksityiskohdat, tai syntaksi lisätään suoraan kieleen, joka tarjoaa kyky kirjoittaa "turvaton" (safe) koodi - erilliset fragmentit tai moduulit, joiden ohjelmoijalle annetaan enemmän hallintaa kaikista muistinhallinnan näkökohdista. Sekä ensimmäisessä että toisessa ratkaisussa on haittapuolensa. Kääreet ovat yleensä monimutkaisia, erittäin ammattitaitoisia kehittää, eivätkä ne välttämättä ole kannettavia. (Niiden luonti voidaan kuitenkin automatisoida. Esimerkiksi on olemassa monikielinen SWIG -generaattori , joka käyttämällä saatavilla olevia C/C++-otsikkotiedostoja luo automaattisesti kääreitä useille kielille, jotka tukevat roskien keräämistä.) Ne ovat vanhentuneita: yhdelle kielelle kirjoitettu kääre voi muuttua käyttökelvottomaksi toisessa, esimerkiksi kun vaihtuu ei-uudelleensijoittavasta roskakoriin siirrettäväksi. Epäturvallisen koodin erityinen syntaksi on "laillinen aukko" muistinhallintamekanismissa ja vaikeasti löydettävien virheiden lähde; Samalla se provosoi jo olemassaolollaan ohjelmoijan kiertämään kielirajoituksia. Lisäksi kaikki roskankeräimen työhön puuttuminen (ja se on väistämätöntä vuorovaikutuksessa vieraan koodin kanssa) saattaa heikentää sen työn tehokkuutta. Esimerkiksi tietyn alueen kiinnittäminen muistiin, mikä on välttämätöntä, jotta roskankerääjä ei poista ja siirrä vierasta koodia työskennellessään tämän muistin kanssa, voi rajoittaa kykyä eheyttää muisti ja siten vaikeuttaa myöhempää osien allokointia muistista. haluttu koko, vaikka kokonaistilaa olisi riittävästi vapaata muistia.

Edut ja haitat

Manuaaliseen muistinhallintaan verrattuna roskien kerääminen on turvallisempaa, koska se estää muistivuotoja ja roikkuvia linkkejä esineiden ennenaikaisesta hävittämisestä. Se yksinkertaistaa myös itse ohjelmointiprosessia .

Roskien keräämisen uskotaan vähentävän merkittävästi muistinhallinnan ylimääräisiä kustannuksia verrattuna kieliin, jotka eivät käytä sitä. Tutkimuksen [3] mukaan C-ohjelmoijat käyttävät 30–40 % kokonaiskehitysajastaan ​​(pois lukien virheenkorjaus) pelkästään muistin hallintaan. On kuitenkin olemassa tutkimuksia, joissa on päinvastaisia ​​johtopäätöksiä, esimerkiksi julkaisussa [4] todetaan, että todellinen ero ohjelmistokehityksen nopeudessa on C ++:ssa, jossa ei ole automaattista roskakeräystä, ja Javassa, jossa se on toteutettu. , on pieni.

Roskankerääjän läsnäolo kokemattomassa kehittäjässä voi luoda väärän uskomuksen, ettei hänen tarvitse kiinnittää huomiota muistinhallintaan ollenkaan. Vaikka roskankerääjä vähentää muistin huonon hallinnan ongelmia, se ei poista niitä kokonaan, ja ne, jotka jatkuvat, eivät näy ilmeisinä virheinä, kuten yleisenä suojausvirheenä , vaan hukkaantuneena muistina ohjelman suoritettaessa. Tyypillinen esimerkki: jos ohjelmoija on unohtanut sen tosiasian, että globaalissa laajuudessa objektissa on jäljellä ainakin yksi ei-nullattava osoitin, tällaista objektia ei koskaan poisteta; Tällaisen näennäisvuodon löytäminen voi olla erittäin vaikeaa.

Usein on kriittistä paitsi varmistaa, että resurssi vapautetaan, myös varmistaa, että se vapautetaan ennen kuin jotain muuta toimintoa kutsutaan - esimerkiksi avoimet tiedostot, merkinnät kriittisissä osissa. Yritykset antaa näiden resurssien hallinta roskankeräilijälle ( viimeistelijöiden kautta ) ovat tehottomia tai jopa virheellisiä, joten sinun on hallittava niitä manuaalisesti. Viime aikoina jopa kielissä, joissa on roskakeräys, on otettu käyttöön syntaksi, joka takaa "puhdistuskoodin" (esimerkiksi erityisen "destructor"-menetelmän) suorittamisen, kun objektiin viittaava muuttuja menee soveltamisalan ulkopuolelle.

Monissa tapauksissa järjestelmät, joissa on roskienkeräys, ovat tehottomampia sekä nopeuden että muistinkäytön suhteen (mikä on väistämätöntä, koska roskankerääjä itse kuluttaa resursseja ja tarvitsee ylimääräistä vapaata muistia toimiakseen kunnolla). Lisäksi järjestelmissä, joissa on roskakeräys, on vaikeampaa toteuttaa matalan tason algoritmeja, jotka vaativat suoran pääsyn tietokoneen RAM-muistiin, koska osoittimien vapaa käyttö on mahdotonta ja suora muistin käyttö vaatii erityisiä matalan tason kielillä kirjoitettuja rajapintoja. . Toisaalta nykyaikaiset roskakerätyt järjestelmät käyttävät erittäin tehokkaita muistinhallintaalgoritmeja minimaalisella ylikuormituksella. On myös mahdotonta olla ottamatta huomioon sitä tosiasiaa, että nyt RAM on suhteellisen halpaa ja saatavilla. Tällaisissa olosuhteissa tilanteet, joissa jätteenkeräyksen kustannukset tulevat kriittisiksi ohjelman tehokkuudelle, ovat erittäin harvinaisia.

Jätteenkeruun merkittävä etu on se, että dynaamisesti luodut objektit elävät pitkään, monistetaan monta kertaa ja viittaukset niihin siirtyvät ohjelman eri osien välillä. Tällaisissa olosuhteissa on melko vaikea määrittää paikkaa, jossa kohde on lopetettu, ja se voidaan poistaa. Koska juuri tämä on tilanne dynaamisesti muuttuvien tietorakenteiden (luettelot, puut, kaaviot) laajassa käytössä, roskien kerääminen on välttämätöntä toiminnallisissa ja loogisissa kielissä, jotka käyttävät laajalti tällaisia ​​rakenteita, kuten Haskell , Lisp tai Prolog . Jätteenkeräyksen käyttö perinteisillä pakollisilla kielillä (perustuu rakenteelliseen paradigmaan, mahdollisesti objektiominaisuuksilla täydennettynä) määräytyy halutusta tasapainosta ohjelmankehityksen yksinkertaisuuden ja nopeuden sekä sen suorituksen tehokkuuden välillä.

Vaihtoehdot

Tuki joissain pakollisissa kielissä tuhoajan automaattiselle kutsumiselle, kun objekti menee syntaktisen soveltamisalan ulkopuolelle ( C++ [5] , Ada , Delphi ) mahdollistaa muistin vapautuskoodin sijoittamisen destrutoriin ja varmistaa, että sitä kutsutaan joka tapauksessa. . Tämä mahdollistaa vaarallisten paikkojen keskittämisen luokan toteutukseen, eikä vaadi ylimääräisiä resursseja, vaikka se asettaakin korkeampia vaatimuksia ohjelmoijan pätevyydelle. Samalla on mahdollista vapauttaa turvallisesti muut kohteen käyttämät resurssit tuhoajassa.

Vaihtoehto roskien keräämiselle on " älykkäiden viittausten " käyttötekniikka, jolloin viittaus dynaamiseen objektiin itse pitää kirjaa käyttäjien määrästä ja poistaa objektin automaattisesti, kun tämä luku on nolla. "Älykkäiden viitteiden" tunnettu ongelma on, että olosuhteissa, joissa ohjelma luo jatkuvasti monia pieniä lyhytikäisiä objekteja muistiin (esimerkiksi luettelorakenteita käsiteltäessä), ne häviävät suorituskyvyssä roskien keräämiselle.

1960-luvulta lähtien on ollut aluepohjaista muistinhallintaa ,  tekniikkaa, jossa muisti on jaettu suhteellisen suuriin fragmentteihin, joita kutsutaan alueiksi , ja jo alueiden sisällä muistia varataan yksittäisille objekteille. Manuaalisella ohjauksella alueet luo ja poistaa ohjelmoija itse, automaattisessa ohjauksessa käytetään erilaisia ​​konservatiivisia arvioita sen määrittämiseen, milloin kaikki alueelle varatut objektit lakkaavat olemasta, minkä jälkeen muistinhallintajärjestelmä poistaa koko alueen. Esimerkiksi luodaan alue, jossa muisti varataan kaikille tietyn alueen sisällä luoduille objekteille, joita ei siirretä ulkopuolelle, ja tämä alue tuhotaan yhdellä komennolla, kun ohjelman suoritus poistuu tästä alueesta. Siirtyminen muistinhallinnassa (joko manuaalinen tai automaattinen) yksittäisistä objekteista suurempiin yksiköihin mahdollistaa monissa tapauksissa yksinkertaistamisen kohteiden käyttöiän osalta ja samalla vähentää yleiskustannuksia. Alueellisen muistinhallinnan toteutuksia (eritasoisia) on olemassa monille ohjelmointikielille, mukaan lukien ML , Prolog , C , Cyclone .

Rust - ohjelmointikieli tarjoaa "omistuksen" käsitteen, joka perustuu kääntäjän tiukkaan hallintaan objektien eliniän ja laajuuden suhteen. Ajatuksena on, että kun objekti luodaan, muuttujasta, jolle on annettu viittaus, tulee objektin "omistaja", ja omistajamuuttujan laajuus rajoittaa objektin käyttöikää. Kun poistut omistajan ulottuvuudesta, objekti poistetaan automaattisesti. Määrittämällä objektiviittauksen toiselle muuttujalle, se voidaan "lainata", mutta lainaus on aina väliaikaista ja se tulee suorittaa kohteen omistajan eliniän sisällä. "Omistajuus" voidaan siirtää toiselle muuttujalle (esimerkiksi objekti voidaan luoda funktion sisällä ja palauttaa sen seurauksena), mutta alkuperäinen omistaja menettää pääsyn kohteeseen. Yhdessä säännöt on suunniteltu varmistamaan, että objektia ei voida muuttaa hallitsemattomasti vieraiden viittausten avulla. Kääntäjä seuraa staattisesti objektien elinikää: mikä tahansa toiminto, joka saattaa jopa johtaa viittauksen tallentamiseen objektiin sen jälkeen, kun sen omistaja on poistunut, johtaa käännösvirheeseen, joka eliminoi "roikkuvien viitteiden" ja muistivuotojen esiintymisen. Tämä lähestymistapa monimutkaistaa ohjelmointitekniikkaa (vastaavasti vaikeuttaa kielen oppimista), mutta eliminoi sekä manuaalisen varauksen ja muistin purkamisen että roskien keräämisen tarpeen.

Muistinhallinta tietyillä kielillä ja järjestelmillä

Roskien keräämistä ohjelman suoritusympäristön välttämättömänä attribuuttina käytetään deklaratiiviseen paradigmaan perustuvissa kielissä , kuten LISP , ML , Prolog , Haskell . Sen välttämättömyys tässä tapauksessa johtuu näiden kielten luonteesta, sillä ne eivät sisällä työkaluja objektien eliniän manuaaliseen hallintaan ja joilla ei ole mahdollisuutta tällaisten työkalujen luonnolliseen integrointiin. Tällaisten kielten monimutkainen perustietorakenne on yleensä dynaaminen yksitellen linkitetty luettelo, joka koostuu dynaamisesti allokoiduista luettelosoluista. Listoja luodaan, kopioidaan, monistetaan, yhdistetään ja jaetaan jatkuvasti, mikä tekee lähes mahdottomaksi hallita manuaalisesti kunkin varatun luettelosolun käyttöikää.

Pakollisilla kielillä roskien kerääminen on yksi vaihtoehto manuaalisten ja joidenkin vaihtoehtoisten muistinhallintatekniikoiden ohella. Tässä sitä pidetään keinona yksinkertaistaa ohjelmointia ja estää virheitä . Yksi ensimmäisistä käännetyistä pakollisista roskienkeräyskielistä oli Oberon , joka osoitti tämän mekanismin soveltuvuuden ja melko korkean tehokkuuden tämäntyyppisille kielille, mutta Java -kieli toi tälle lähestymistavalle laajan suosion ja suosion . Myöhemmin Java-lähestymistapa toistettiin .NET -ympäristössä ja lähes kaikilla siinä toimivilla kielillä alkaen C # :stä ja Visual Basic .NET :stä . Samaan aikaan ilmestyi monia tulkittuja kieliä (JavaScript, Python, Ruby, Lua), joihin sisällytettiin roskien kerääminen muiden kuin ohjelmoijien kielen saavutettavuuden ja koodauksen yksinkertaistamisen vuoksi. Laitteistotehon kasvu, joka tapahtui samanaikaisesti itse keräilijöiden parantamisen kanssa, johti siihen, että ylimääräiset jätteenkeräyksen yleiskustannukset eivät enää olleet merkittäviä. Useimmilla nykyaikaisilla roskiin kerätyillä pakollisilla kielillä ei ole lainkaan mahdollisuutta poistaa objekteja (kuten poisto-operaattoria) suoraan manuaalisesti. Tulkkia käyttävissä tai tavukoodaukseen kääntävissä järjestelmissä roskienkerääjä on osa ajonaikaa; samoilla kielillä, joissa käännetään prosessorin objektikoodiksi, se on toteutettu vaadittavana järjestelmäkirjastona.

On myös pieni määrä kieliä ( nim , Modula-3 , D ), jotka tukevat sekä manuaalista että automaattista muistinhallintaa, joille sovellus käyttää kahta erillistä kasaa.

Muistiinpanot

  1. ↑ Vakiintunut termi, venäjän kielen näkökulmasta "roskakeräys" on oikeampi ( ote ABBYY Lingvo -sanakirjoista Arkistokopio 25.4.2017 Wayback Machinella , Ushakovin sanakirja : build Arkistokopio 25.4 ., 2017 Wayback Machinessa , kokoelma Arkistokopio päivätty 25. huhtikuuta 2017 Wayback Machinessa , kerätä Arkistoitu 25. huhtikuuta 2017 Wayback Machinessa ; Gramota.ru : keskustelu Arkistoitu 25. huhtikuuta 2017 Wayback Machinessa ). Kokoaminen on sanakirjan mukaan "yhdistämällä erillisiä osia, yksityiskohtia, tehdä, luoda jotain, muuttaa joksikin valmiiksi" ja se on "keräily", joka koskee sanan "kokoaminen" muita merkityksiä.
  2. Raymond Chen . Ajattelet varmasti roskien keräämistä väärin . Arkistoitu 19. heinäkuuta 2013 Wayback Machinessa
  3. Boehm H. Konservatiivisen roskakeräyksen edut ja haitat . Arkistoitu alkuperäisestä 24. heinäkuuta 2013.
    (linkki Raymond, Eric . Unix-ohjelmoinnin taito.. - 2005. - s. 357. - 544 s. - ISBN 5-8459-0791-8 . )
  4. Lutz Prechelt. C:n, C++:n, Javan, Perlin, Pythonin, Rexxin ja  Tcl :n empiirinen vertailu . Karlsruhen teknillinen korkeakoulu . Haettu 26. lokakuuta 2013. Arkistoitu alkuperäisestä 3. tammikuuta 2020.
  5. RAII, Dynamic Objects ja Factories in C++, Roland Pibinger, 3. toukokuuta 2005 . Käyttöpäivä: 14. helmikuuta 2016. Arkistoitu alkuperäisestä 5. maaliskuuta 2016.