Ohjelmointi

Java-koko

26. joulukuuta 2003

K: Onko Java: lla operaattoria kuten sizeof () C: ssä?

A: Pinnallinen vastaus on, että Java ei tarjoa mitään C: n kaltaista koko(). Harkitaan kuitenkin miksi Java-ohjelmoija saattaa toisinaan haluta sitä.

C-ohjelmoija hallitsee useimpia datarakenteen muistialueita itse ja koko() on välttämätön allokoitavien muistilohkokokojen tiedossa. Lisäksi C-muistin varaimet pitävät malloc () tee melkein mitään objektin alustamisen osalta: ohjelmoijan on asetettava kaikki objektikentät, jotka osoittavat muita objekteja. Mutta kun kaikki sanotaan ja koodataan, C / C ++ -muistin allokointi on melko tehokasta.

Vertailun vuoksi Java-objektien allokointi ja rakentaminen on sidottu toisiinsa (allokoitua, mutta alustamatonta objektin esiintymää on mahdotonta käyttää). Jos Java-luokka määrittää kentät, jotka viittaavat muihin objekteihin, on myös yleistä asettaa ne rakennushetkellä. Java-objektin allokointi allokoi siis usein useita toisiinsa yhdistettyjä objekti-ilmentymiä: objektikaavio. Yhdessä automaattisen roskienkeruun kanssa tämä on aivan liian kätevää ja voi saada sinut tuntemaan, että sinun ei tarvitse koskaan huolehtia Java-muistin jakamisen yksityiskohdista.

Tietenkin tämä toimii vain yksinkertaisissa Java-sovelluksissa. Verrattuna C / C ++: iin, vastaavat Java-tietorakenteet vievät enemmän fyysistä muistia. Yritysohjelmistokehityksessä lähestyminen nykyisen 32-bittisten JVM-laitteiden suurimpaan käytettävissä olevaan virtuaalimuistiin on yleinen skaalautuvuuden rajoitus. Joten Java-ohjelmoija voisi hyötyä koko() tai jotain vastaavaa pitää silmällä, ovatko hänen datarakenteet liian suuria vai sisältävätkö ne pullonkauloja. Onneksi Java-pohdinnan avulla voit kirjoittaa tällaisen työkalun melko helposti.

Ennen kuin jatkan, luopun useista mutta virheellisistä vastauksista tämän artikkelin kysymykseen.

Fallacy: Sizeof () -arvoa ei tarvita, koska Java-perustyyppien koot ovat kiinteät

Kyllä, Java int on 32 bittiä kaikissa JVM: issä ja kaikilla alustoilla, mutta tämä on vain kielen määrittelyvaatimus ohjelmoijan havaittavissa tämän tietotyypin leveys. Sellainen int on pohjimmiltaan abstrakti tietotyyppi ja se voidaan varmuuskopioida esimerkiksi 64-bittisellä fyysisellä muistisanalla 64-bittisellä koneella. Sama koskee ei-primitiivisiä tyyppejä: Java-kielimääritys ei kerro mitään siitä, kuinka luokkakentät tulisi kohdistaa fyysiseen muistiin tai että eräistä boolean-alueita ei voitu toteuttaa pienikokoisena bittivektorina JVM: n sisällä.

Fallacy: Voit mitata objektin kokoa sarjamalla se tavuvirraksi ja tarkastelemalla tuloksena olevaa virran pituutta

Syy, että tämä ei toimi, johtuu siitä, että sarjallisuusasettelu on vain etäisyys todellisesta muistin ulkoasusta. Yksi helppo tapa nähdä se on katsomalla miten Merkkijonos sarjoitetaan: muistiin joka hiiltyä on vähintään 2 tavua, mutta sarjamuodossa MerkkijonoNe ovat UTF-8-koodattuja, joten kaikki ASCII-sisällöt vievät puolet vähemmän tilaa.

Toinen toimiva lähestymistapa

Saatat muistaa "Java Tip 130: Tiedätkö tietosi koon?" siinä kuvattiin tekniikka, joka perustui suuren määrän identtisten luokan esiintymien luomiseen ja mitattiin huolellisesti tuloksena olevan JVM: n käytetyn kasan koon kasvu. Tarvittaessa tämä idea toimii erittäin hyvin, ja käytän sitä itse asiassa tämän artikkelin vaihtoehtoisen lähestymistavan käynnistämiseen.

Huomaa, että Java Tip 130: t Koko luokka vaatii lepotilassa olevan JVM: n (niin että kasa-aktiivisuus johtuu vain mittauslangan pyytämistä kohteiden allokoinnista ja roskakorista) ja vaatii suuren määrän identtisiä objektin esiintymiä. Tämä ei toimi, kun haluat koota yhden suuren objektin (ehkä osana virheenkorjauksen jäljitystulosta) ja varsinkin kun haluat tutkia, mikä sen todella teki niin suureksi.

Mikä on kohteen koko?

Yllä olevassa keskustelussa korostetaan filosofista näkökohtaa: mikä on objektikoon määritelmä, kun yleensä käsittelet objektikaavioita? Onko se vain tutkittavan objektin ilmentymän koko vai koko objektikaavion juurtunut koko datakaavio? Jälkimmäinen on yleensä tärkeämpää käytännössä. Kuten näette, asiat eivät aina ole niin selkeitä, mutta aloittelijoille voit noudattaa tätä lähestymistapaa:

  • Kohde-ilmentymä voi olla (suunnilleen) kokoinen summaamalla kaikki sen ei-staattiset tietokentät (mukaan lukien yliluokissa määritellyt kentät)
  • Toisin kuin esimerkiksi C ++, luokkamenetelmät ja niiden virtuaalisuus eivät vaikuta kohteen kokoon
  • Luokan superliitännöillä ei ole vaikutusta objektin kokoon (katso huomautus tämän luettelon lopussa)
  • Koko objektikoko voidaan saada sulkemisena koko objektikuvaajalle, joka on juurtunut alkukohteeseen
merkintä: Minkä tahansa Java-käyttöliittymän toteuttaminen vain merkitsee kyseessä olevan luokan eikä lisää mitään tietoja sen määritelmään. Itse asiassa JVM ei edes tarkista, että käyttöliittymän toteutus tarjoaa kaikki käyttöliittymän edellyttämät menetelmät: tämä on ehdottomasti kääntäjän vastuu nykyisissä määrityksissä.

Käynnistääksesi prosessin, primitiivisille tietotyypeille käytän fyysisiä kokoja Java Tip 130: n mittaamana Koko luokassa. Kuten käy ilmi, tavallisten 32-bittisten JVM: ien tavallinen java.lang.objekti vie 8 tavua, ja perustietotyypit ovat yleensä pienintä fyysistä kokoa, joka mahtuu kielivaatimuksiin (paitsi looginen vie kokonaisen tavun):

 // java.lang.Objektin kuoren koko tavuina: julkinen staattinen lopullinen int OBJECT_SHELL_SIZE = 8; julkinen staattinen lopullinen int OBJREF_SIZE = 4; julkinen staattinen lopullinen int LONG_FIELD_SIZE = 8; julkinen staattinen lopullinen int INT_FIELD_SIZE = 4; julkinen staattinen lopullinen int SHORT_FIELD_SIZE = 2; julkinen staattinen lopullinen int CHAR_FIELD_SIZE = 2; julkinen staattinen lopullinen int BYTE_FIELD_SIZE = 1; julkinen staattinen loppu int BOOLEAN_FIELD_SIZE = 1; julkinen staattinen lopullinen int DOUBLE_FIELD_SIZE = 8; julkinen staattinen lopullinen int FLOAT_FIELD_SIZE = 4; 

(On tärkeää ymmärtää, että näitä vakioita ei ole kovakoodattu ikuisesti, ja ne on mitattava itsenäisesti tietylle JVM: lle.) Tietysti objektien kenttäkokojen naiivi summaus jättää huomiotta muistin kohdistusongelmat JVM: ssä. Muistin tasaamisella on merkitystä (kuten on esitetty esimerkiksi Java Tip 130: n primitiivisille taulukotyypeille), mutta mielestäni on kannattamatonta ajaa tällaisten matalan tason yksityiskohtia. Tällaiset tiedot eivät ole vain riippuvaisia ​​JVM-toimittajista, vaan ne eivät ole ohjelmoijan hallinnassa. Tavoitteenamme on saada hyvä arva objektin koosta ja toivottavasti saada vihje, kun luokan kenttä saattaa olla tarpeeton. tai kun kentän tulisi olla laiskasti asuttua; tai kun tarvitaan kompaktampaa sisäkkäistä datarakennetta jne. Absoluuttisen fyysisen tarkkuuden saavuttamiseksi voit aina palata Koko luokka Java Tip 130: ssä.

Auttaakseen profiilia siitä, mistä objektikohde muodostuu, työkalumme ei vain laske kokoa, vaan rakentaa myös hyödyllisen tietorakenteen sivutuotteeksi: kaavio, joka koostuu IObjectProfileNodes:

käyttöliittymä IObjectProfileNode {Object object (); Merkkijonon nimi (); int-koko (); int uudelleenlaskenta (); IObjectProfileNode-vanhempi (); IObjectProfileNode [] lapset (); IObjectProfileNode-kuori (); IObjectProfileNode [] -polku (); IObjectProfileNode-juurihakemisto (); int polun pituus (); totuusarvoinen liikenne (INodeFilter-suodatin, INodeVisitor-vierailija); Merkkijono (); } // Käyttöliittymän loppu 

IObjectProfileNodes on kytketty toisiinsa lähes täsmälleen samalla tavalla kuin alkuperäinen objektikaavio IObjectProfileNode.object () palauttamalla todellisen objektin, jota kukin solmu edustaa. IObjectProfileNode.size () palauttaa solmun objektin ilmentymään juurtuneen objektin alipuun kokonaiskoon (tavuina). Jos objektin ilmentymä linkittää muihin objekteihin ei-nollan esiintymäkenttien tai taulukkokenttien sisällä olevien viitteiden kautta, niin IObjectProfileNode.children () on vastaava luettelo alikaavion solmuista, lajiteltu pienenevässä järjestyksessä. Vastaavasti jokaiselle muulle kuin aloitussolmulle, IObjectProfileNode.parent () palauttaa vanhemman. Koko kokoelma IObjectProfileNodes viipaloi ja pilkkoo alkuperäisen objektin ja näyttää kuinka tietojen tallennus on osioitu sen sisällä. Lisäksi kaavion solmunimet johdetaan luokan kentistä ja tutkitaan solmun polkua kaavion sisällä (IObjectProfileNode.path ()) avulla voit jäljittää omistajuuslinkit alkuperäisestä objektin ilmentymästä mihin tahansa sisäiseen dataan.

Olet ehkä huomannut edellistä kappaletta lukiessasi, että tähänastinen idea on edelleen epäselvä. Jos törmäät objektigrafiikkaa liikuttaessasi samaan objektiinstanssiin useammin kuin kerran (ts. Useampi kuin yksi kenttä jossain kaaviossa osoittaa sitä), miten määrität sen omistuksen (pääosoitin)? Harkitse tätä koodinpätkää:

 Object obj = uusi merkkijono [] {uusi merkkijono ("JavaWorld"), uusi merkkijono ("JavaWorld")}; 

Jokainen java.lang.String esiintymällä on sisäinen tyypin kenttä hiiltyä[] se on todellinen merkkijonon sisältö. Tapa Merkkijono kopiosuunnittelija toimii Java 2 Platform, Standard Edition (J2SE) 1.4 -versiossa, molemmissa Merkkijono Yllä olevan taulukon sisällä olevat esiintymät jakavat saman hiiltyä[] taulukko, joka sisältää {'J', 'a', 'v', 'a', 'W', 'o', 'r', 'l', 'd'} merkkijono. Molemmat merkkijonot omistavat tämän ryhmän yhtä lailla, joten mitä sinun pitäisi tehdä tällaisissa tapauksissa?

Jos haluan aina määrittää yhden vanhemman kaaviosolmulle, tälle ongelmalle ei ole yleisesti täydellistä vastausta. Käytännössä monet tällaiset objektitapaukset voidaan kuitenkin jäljittää yhteen "luonnolliseen" vanhempaan. Tällainen linkkien luonnollinen sekvenssi on yleensä lyhyempi kuin muut, kiertävämmät reitit. Ajattele tietoja, joihin esiintymäkentät osoittavat kuuluvan enemmän kyseiseen esiintymään kuin mihinkään muuhun. Ajattele, että taulukon merkinnät kuuluvat enemmän kyseiseen ryhmään. Jos siis sisäinen objektiesiintymä voidaan saavuttaa useiden polkujen kautta, valitsemme lyhimmän polun. Jos meillä on useita yhtä pitkiä polkuja, valitsemme vain ensimmäisen löydetyn. Pahimmassa tapauksessa tämä on yhtä hyvä yleinen strategia kuin mikään muu.

Kaavioiden ja lyhyimpien polkujen ajattelun pitäisi soida kelloa tässä vaiheessa: leveys-ensimmäinen haku on kaavion läpikulkualgoritmi, joka takaa lyhimmän polun löytämisen aloitussolmusta mihin tahansa muuhun saavutettavissa olevaan kaaviosolmuun.

Kaikkien näiden alkutöiden jälkeen tässä on oppikirjan toteutus tällaisen kuvaajan läpikäynnistä. (Joitakin yksityiskohtia ja apumenetelmiä on jätetty pois; lisätietoja on tämän artikkelin latauksessa.):