funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

JVM: Epizoda V – Paměť vrací úder

11. 7. 2013 (před 6 lety) — k47

Minule jsem psal, jak se po­dí­vat ob­jek­tům pod sukně a přímo pře­číst bajty v jejich hla­vič­kách. Už to samo o sobě jde daleko za hra­nice vir­tu­ál­ního stroje a na­ru­šuje jeho pa­mě­ťo­vou bez­peč­nost. Takže komu se dělalo minule mdlo, by vůbec neměl číst tento článek, pro­tože jdu ještě o kus dál a po­kou­ším se zjis­tit, co by se dalo dělat, kdy­bych do hla­vi­ček ob­jektů mohl za­pi­so­vat.

Nejde o nic kom­pli­ko­va­ného, třída sun.misc.Unsafe má kromě metod get{Byte, Int, Long, …} i sadu metod put{Byte, Int, Long, …}, které mohou zapsat na li­bo­volné místo paměti (a když ta paměť ne­patří JVM, skončí se­g­faul­tem).

Na­padly mě čtyři pří­pady, kdy by se tento hrozný hack dal využít. Ale ne­ří­kám, že by se někdy měl použít, pro­tože je zá­vislý na kon­krét­ním JVM a kon­krétní ar­chi­tek­tuře a už z prin­cipu jde o ne­bez­pečný, ne­pře­no­si­telný a ne­před­ví­da­telný kód, který může způ­so­bit bi­zarní ne­vy­sto­po­va­telné chyby.

Kdyby se někdo ptal, za­pí­rejte, po­přete, že jste cokoli z ná­sle­du­jí­cích řádků kdy četli nebo že vás to vůbec na­padlo. Ne­smíte dát najevo, že jste se ne­chali omámit za­ká­za­ným ovocem a přešli na temnou stranu pro­gra­mo­vání.

## 1) Změna třídy ob­jektu

Pomocí unsafe můžu celkem jed­no­duše změnit třídu ob­jektu. Stačí vědět, že Ope­n­JDK ucho­vává poin­ter na třídu těsně za sekcí markOop (viz. minule), na 32bit plat­for­mách je na off­setu 4, na 64 bit plat­for­mách na off­setu 8. Odkaz na třídu je uložen nějak zvláštně a proto nemůžu použít li­bo­vol­nou re­fe­renci, ale musím ji zko­pí­ro­vat z jiné in­stance.

import sun.misc.Unsafe

val unsafe = {
  val f = classOf[Unsafe].getDeclaredField("theUnsafe")
  f.setAccessible(true)
  f.get().asInstanceOf[Unsafe]
}

class A {
  val l: Long = 0x0000000100000002L
}
class B {
  val a: Int = 0
  val b: Int = 0
  def aa = a
  def bb = b
}

val a = new A
val b = new B

// změna třídy proběhne zde
unsafe.putInt(a, 8, unsafe.getInt(b, 8))

// bez problémů přetypuji na novou třídu
val a_b = a.asInstanceOf[B]
a_b.getClass // B
// přidané metody fungují bez problémů
a_b.aa // 0 - padding
a_b.bb // 2 - první 4 bajty longu

## 2) Zkra­co­vání polí

Délka pole je ulo­žena hned po uka­za­teli na třídu ob­jektu, tedy na dobře známém a sta­bil­ním místě (které se ale po­cho­pi­telně liší podle ar­chi­tek­tury). Není pro­blém na tohle místo zapsat jinou, menší hod­notu a tím způ­so­bem zkrá­tit exis­tu­jící pole. Gar­bage collec­tor v příš­tím cyklu uvidí kratší pole a zrecykluje ne­po­třeb­nou paměť.

Tento hack by byl sku­tečně uži­tečný a zjed­no­du­šil by ně­které al­go­ritmy. Na­pří­klad když chci získat pole ob­sa­hu­jící všechny spo­lečné prvky dvou se­řa­ze­ných polí, mám v sou­čas­nosti dvě mož­nosti:

Ani jeden z těchto pří­padů není ide­ální. Mnohem efek­tiv­nější by bylo, kdy­bych alo­ko­val pole ma­xi­mální možné délky (v tomhle pří­padě dlouhé jako menší z obou ko­lekcí), na­pl­nil ho a pak za­ho­dil zbytek pole. Po­u­ží­vám přece ma­naged run­time, sakra.

A o tom, že to je sku­tečně uži­tečné a nejde jenom o blouz­nění po­ma­te­ných, svědčí fakt, že se přesně tohle plá­nuje do ně­které z dal­ších verzí Javy.

Hack zkra­co­vání pole by přesně tohle vy­ře­šil. Bo­hu­žel ne­fun­guje. Narazí na GC a „card mar­king“ strá­nek v paměti. Narazí tak tvrdě, že JVM se­g­faultne.

## 3) Ne­vy­u­žité bajty v hla­vičce

Ve hla­vičce kaž­dého ob­jektu na 64 bi­to­vých plat­for­mách je do­hro­mady 24 ne­vy­u­ži­tých bitů, které leží ladem a do­slova čekají, až do nich něco zapíšu. Tahle data pře­žijí GC cyklus (lo­gicky, GC ko­pí­ruje celý blok paměti ob­jektu v jenom tažení). Si­tu­ace s hla­vič­kami je celkem kom­pli­ko­vaná, ale stačí vědět, že jakmile přečtu iden­ti­ty­Ha­shCode ob­jektu, je hla­vička za­fi­xo­vána a volné bity zů­sta­nou volné na věky věků.

Tento hack fun­guje jenom na 64 bit plat­for­mách (i s kom­pri­mo­va­nými poin­tery) a jenom s ně­kte­rými gar­bage collec­tory.

## 4) Vlastní iden­tity ha­shCode

Ně­které ob­jekty (jako třeba String) si uklá­dají vlastní ha­shCode do sou­kro­mého atri­butu, aby ho ne­mu­sely ne­u­stále znovu a znovu po­čí­tat. Každý objekt má pak ještě ne­zá­vislý iden­ti­ty­Ha­shCode v hla­vičce. Proč ne­vy­u­žít pro­stor iden­ti­ty­Ha­shCode (který se stejně nikdy ne­po­u­žije) pro můj vlastní ha­shCode. V ně­kte­rých pří­pa­dech te může ušet­řit až 8 bajtů na in­stanci (jeden int + pad­ding).

@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články