Programovanie

Veľkosť pre Javu

26.12.2003

Otázka: Má Java operátor ako sizeof () v C?

A: Povrchnou odpoveďou je, že Java neposkytuje nič podobné ako C. veľkosť(). Zvážme však prečo programátor v jazyku Java to občas môže chcieť.

Programátor jazyka C spravuje väčšinu pridelení pamäte dátovej štruktúry sám a veľkosť() je nevyhnutný pre poznanie veľkosti alokačných blokov pamäte. Ďalej sú alokátory pamäte typu C podobné malloc () nerobte takmer nič, pokiaľ ide o inicializáciu objektov: programátor musí nastaviť všetky polia objektov, ktoré sú ukazovateľmi na ďalšie objekty. Ale keď je všetko povedané a zakódované, alokácia pamäte C / C ++ je dosť efektívna.

Na porovnanie, alokácia a konštrukcia objektov Java sú navzájom spojené (nie je možné použiť alokovanú, ale neinicializovanú inštanciu objektu). Ak trieda Java definuje polia, ktoré sú odkazmi na ďalšie objekty, je tiež bežné ich nastaviť v čase stavby. Alokácia objektu Java preto často prideľuje množstvo prepojených inštancií objektov: objektový graf. Spolu s automatickým zberom odpadu je to až príliš pohodlné a môžete mať pocit, že sa už nikdy nebudete musieť starať o podrobnosti alokácie pamäte Java.

To samozrejme funguje iba pre jednoduché Java aplikácie. V porovnaní s C / C ++ majú ekvivalentné dátové štruktúry Java tendenciu zaberať viac fyzickej pamäte. Pri vývoji podnikového softvéru je priblíženie sa k maximálnej dostupnej virtuálnej pamäti na dnešných 32-bitových procesoroch JVM bežným obmedzením škálovateľnosti. Programátor jazyka Java by z toho mohol mať úžitok veľkosť() alebo niečo podobné, aby ste dohliadali na to, či sú jeho dátové štruktúry príliš veľké alebo či obsahujú úzke miesta v pamäti. Našťastie Java reflexia umožňuje napísať takýto nástroj pomerne ľahko.

Predtým, ako budem pokračovať, sa zbavím častých, ale nesprávnych odpovedí na otázku tohto článku.

Klam: Sizeof () nie je potrebný, pretože veľkosti základných typov Java sú pevné

Áno, Java int je 32 bitov vo všetkých JVM a na všetkých platformách, ale toto je iba požiadavka na jazykovú špecifikáciu pre vnímateľný programátorom šírka tohto dátového typu. Takýto int je v podstate abstraktný údajový typ a môže byť zálohovaný povedzme 64-bitovým slovom fyzickej pamäte na 64-bitovom stroji. To isté platí pre typy, ktoré nie sú primitívne: špecifikácia jazyka Java nehovorí nič o tom, ako by mali byť polia tried zarovnané vo fyzickej pamäti, alebo že by nebolo možné implementovať pole booleans ako kompaktný bitvector vo vnútri JVM.

Klam: Môžete merať veľkosť objektu tak, že ho serializujete do bajtového toku a pozriete sa na výslednú dĺžku toku

Dôvod, prečo to nefunguje, je ten, že rozloženie serializácie je iba vzdialeným odrazom skutočného rozloženia v pamäti. Jeden ľahký spôsob, ako to vidieť, je pohľad na to, ako na to Stringje serializovaný: v pamäti každý char je najmenej 2 bajty, ale v serializovanej podobe Strings sú kódované UTF-8, a preto akýkoľvek obsah ASCII zaberá o polovicu menej miesta.

Ďalší pracovný prístup

Môžete si spomenúť na „Tip Java 130: Poznáte svoju veľkosť údajov?“ , ktorý popísal techniku ​​založenú na vytvorení veľkého množstva inštancií identickej triedy a starostlivom meraní výsledného nárastu použitej veľkosti haldy JVM. Ak je to možné, tento nápad funguje veľmi dobre a v skutočnosti ho použijem na zavedenie alternatívneho prístupu v tomto článku.

Upozorňujeme, že Java Tip 130's Veľkosť trieda vyžaduje kľudový JVM (aby aktivita haldy bola spôsobená iba alokáciami objektov a zbierkami odpadkov požadovanými meracím vláknom) a vyžaduje veľké množstvo rovnakých inštancií objektov. To nefunguje, keď chcete zmeniť veľkosť jedného veľkého objektu (možno ako súčasť výstupu sledovania ladenia), a najmä keď chcete preskúmať, čo ho vlastne urobilo takým veľkým.

Aká je veľkosť objektu?

Diskusia vyššie zdôrazňuje filozofický bod: aká je definícia veľkosti objektu, ak sa zvyčajne zaoberáte grafmi objektov? Je to iba veľkosť inštancie objektu, ktorú skúmate, alebo veľkosť celého dátového grafu zakoreneného v inštancii objektu? To druhé je to, na čom v praxi zvyčajne záleží viac. Ako uvidíte, veci nie sú vždy také jednoznačné, ale pre začiatočníkov sa môžete riadiť týmto prístupom:

  • Inštancia objektu môže byť (približne) veľká sčítaním všetkých jeho nestatických údajových polí (vrátane polí definovaných v nadtriedach)
  • Na rozdiel od, povedzme, C ++, nemajú triedne metódy a ich virtuálnosť žiadny vplyv na veľkosť objektu
  • Superrozhranie triedy nemá žiadny vplyv na veľkosť objektu (pozri poznámku na konci tohto zoznamu)
  • Plnú veľkosť objektu je možné získať ako záver celého grafu objektu zakoreneného na počiatočnom objekte
Poznámka: Implementácia ľubovoľného rozhrania Java iba označí príslušnú triedu a nepridá k jej definícii žiadne údaje. V skutočnosti JVM ani neoveruje, či implementácia rozhrania poskytuje všetky metódy vyžadované rozhraním: v súčasných špecifikáciách je to striktne zodpovednosť kompilátora.

Na zavedenie procesu pre primitívne dátové typy používam fyzické veľkosti merané pomocou Java Tip 130's Veľkosť trieda. Ako sa ukázalo, pre bežné 32-bitové JVM obyčajná java.lang.Objekt zaberá 8 bajtov a základné dátové typy majú zvyčajne najmenšiu fyzickú veľkosť, ktorá uspokojí jazykové požiadavky (okrem boolean zaberá celý bajt):

 // java.lang.Veľkosť shellu objektu v bajtoch: public static final int OBJECT_SHELL_SIZE = 8; verejny staticky final int OBJREF_SIZE = 4; public static final int LONG_FIELD_SIZE = 8; public static final int INT_FIELD_SIZE = 4; verejný statický konečný int SHORT_FIELD_SIZE = 2; verejný statický konečný int CHAR_FIELD_SIZE = 2; verejný statický konečný int BYTE_FIELD_SIZE = 1; public static final int BOOLEAN_FIELD_SIZE = 1; public static final int DOUBLE_FIELD_SIZE = 8; public static final int FLOAT_FIELD_SIZE = 4; 

(Je dôležité si uvedomiť, že tieto konštanty nie sú napevno zakódované a musia sa merať nezávisle pre daný JVM.) Naivné sčítanie veľkostí poľa objektu samozrejme zanedbáva problémy so zarovnaním pamäte v JVM. Na zarovnaní pamäte záleží (ako je to znázornené napríklad pre primitívne typy polí v Java Tip 130), ale myslím si, že je nerentabilné prenasledovať sa za takými podrobnosťami na nízkej úrovni. Nielen, že také podrobnosti závisia od dodávateľa JVM, nie sú ani pod kontrolou programátora. Naším cieľom je získať dobrý odhad veľkosti objektu a dúfajme, že dostaneme predstavu o tom, kedy môže byť pole triedy nadbytočné; alebo kedy by malo byť pole lenivo osídlené; alebo keď je potrebná kompaktnejšia vnorená dátová štruktúra atď. Pre absolútnu fyzickú presnosť sa môžete kedykoľvek vrátiť k Veľkosť triedy v jazyku Java Tip 130.

Náš nástroj, aby pomohol profilovať to, čo tvorí inštanciu objektu, nielen vypočíta veľkosť, ale vytvorí aj užitočnú štruktúru údajov ako vedľajší produkt: graf zložený z IObjectProfileNodes:

rozhranie IObjectProfileNode {Object object (); Názov reťazca (); int velkost (); int refcount (); IObjectProfileNode parent (); IObjectProfileNode [] deti (); IObjectProfileNode shell (); IObjectProfileNode [] cesta (); IObjectProfileNode root (); int pathlength (); boolovský traverz (filter INodeFilter, návštevník INodeVisitor); Výpis reťazcov (); } // Koniec rozhrania 

IObjectProfileNodes sú prepojené takmer úplne rovnako ako graf pôvodného objektu, s IObjectProfileNode.object () vrátenie skutočného objektu, ktorý každý uzol predstavuje. IObjectProfileNode.size () vráti celkovú veľkosť (v bajtoch) podstromu objektov zakoreneného v inštancii objektu daného uzla. Ak sa inštancia objektu spája s inými objektmi prostredníctvom nenulových polí inštancie alebo prostredníctvom odkazov obsiahnutých vo vnútri polí poľa, potom IObjectProfileNode.children () bude zodpovedajúci zoznam uzlov podradeného grafu zoradených podľa veľkosti. Naopak, pre každý iný ako počiatočný uzol, IObjectProfileNode.parent () vráti svojho rodiča. Celá zbierka IObjectProfileNodes teda krája a krája pôvodný objekt a ukazuje, ako je v ňom rozdelené úložisko dát. Ďalej sú názvy uzlov grafu odvodené z polí triedy a skúmania cesty uzla v grafe (IObjectProfileNode.path ()) umožňuje sledovať vlastnícke odkazy z pôvodnej inštancie objektu na akýkoľvek interný údaj.

Možno ste si pri čítaní predchádzajúceho odseku všimli, že táto myšlienka má stále určitú nejednoznačnosť. Ak sa pri prechádzaní grafom objektu stretnete s tou istou inštanciou objektu viackrát (t. J. Na ňu smeruje viac ako jedno pole niekde v grafe), ako pridelíte jeho vlastníctvo (nadradený ukazovateľ)? Zvážte tento útržok kódu:

 Objekt obj = new String [] {new String ("JavaWorld"), new String ("JavaWorld")}; 

Každý java.lang.String inštancia má vnútorné pole typu char [] to je skutočný obsah reťazca. Spôsob, akým String copy constructor funguje v prostredí Java 2 Platform, Standard Edition (J2SE) 1.4, obidve String inštancie vo vyššie uvedenom poli budú zdieľať to isté char [] pole obsahujúce znak {'J', 'a', 'v', 'a', 'W', 'o', 'r', 'l', 'd'} postupnosť znakov. Oba reťazce vlastnia toto pole rovnako, tak čo by ste mali robiť v takýchto prípadoch?

Ak chcem vždy priradiť jedného rodiča k uzlu grafu, potom tento problém nemá univerzálne dokonalú odpoveď. V praxi však možno veľa takýchto inštancií objektov vysledovať až k jedinému „prirodzenému“ rodičovi. Takáto prirodzená postupnosť odkazov je zvyčajne kratšie než iné, okružnejšie trasy. Pomyslite na to, že údaje, na ktoré inštančné polia poukazujú, patria skôr tejto inštancii ako čomukoľvek inému. Myslite na to, že položky v poli patria skôr do tohto poľa samotného. Ak je teda možné dosiahnuť inštanciu interného objektu cez niekoľko ciest, zvolíme najkratšiu cestu. Ak máme niekoľko rovnako dlhých ciest, dobre, stačí zvoliť prvú objavenú. V najhoršom prípade je to rovnako dobrá všeobecná stratégia ako ktorákoľvek iná.

V tomto okamihu by ste mali premýšľať o prechodoch grafov a najkratších cestách: hľadanie na šírku je algoritmus prechodu grafom, ktorý zaručuje nájdenie najkratšej cesty od východiskového uzla po akýkoľvek iný dosiahnuteľný uzol grafu.

Po všetkých týchto úvodných slovách je tu učebnicová implementácia takéhoto prechodu grafu. (Niektoré podrobnosti a pomocné metódy boli vynechané; ďalšie podrobnosti nájdete v stiahnutí tohto článku.):

$config[zx-auto] not found$config[zx-overlay] not found