tifyty

pure Java, what else ?

Ez már tényleg WTF!!!

Ha valakinek sikerült megtartani a lelki békéét a korábbi A string immutable vagy nem elolvasása után, és nem kezdte el mondogatni, hogy a “Java egy szar” vagy valami hasonlót, akkor azt ajánlom, hogy olvasd el ezt a szösszenetet, és utána rohanj végig a folyosón üvöltve, hogy elmégy Nepálba kecskepásztornak.

import java.lang.reflect.Field;

public class IntHack {
	public static void main(String[] args) throws Exception {
		Integer a1 = 1;
		Integer a2 = 2;
		Integer a3 = 3;
		Integer a4 = 4;
		Integer a5 = 5;
		Integer a128 = 128;
		Field f = Integer.class.getDeclaredField("value");
		f.setAccessible(true);
		f.setInt(a1, 666);
		f.setInt(a2, 666);
		f.setInt(a3, 666);
		f.setInt(a4, 666);
		f.setInt(a5, 666);
		f.setInt(a128, 666);
		Integer b1 = 1;
		Integer b2 = 2;
		Integer b3 = 3;
		Integer b4 = 4;
		Integer b5 = 5;
		Integer b128 = 128;
		System.out.println(b1);
		System.out.println(b2);
		System.out.println(b3);
		System.out.println(b4);
		System.out.println(b5);
		System.out.println(b128);
	}
}

És mit ír ez ki?

666
666
666
666
666
128

Nagyon gonosz! Nagyon, nagyon gonosz!

No akkor, ha kifutottad magad a folyosókon, és rájöttél, hogy a nepáli kecskepásztorok élete sem olyan egyszerű, mert bár ott nincs reflection, de még tükör sem, viszont ott van … Mi van ott? Nincs ott semmi. Na hagyjuk. Szóval, akkor nézzük meg, hogy mi a búbánatos ecske történt itt.

Létrehoztunk néhány Integer változót, és értéket adtunk nekik. Majd elővettük a gonosz kelléktárát, és megerőszakolva a Java lelkivilágát, átírtuk az alapvetően immutable Integer objektumok értékeit. Azért igaza van annak, hogy aki ilyent tesz, az meg is érdemli, de ifjonti hévvel akár az is gondolhatjuk, hogy ha lehet, akkor miért ne?

Hogy miért? Azért, mert ha olyasmit csinálunk, ami nincs benne a nyelvi kontraktusban, csak éppen meg lehet csinálni, akkor bármi egyéb olyan dolog is történhet, amire nem számítunk. Mert ha egyszer a Java runtime azt mondja, hogy az Integer immutable, tehát nem változtatható meg a szokásos nyelvi elemekkel, akkor bizony a futtató környezet erre számít is. Az, hogy éppen van egy olyan eszköz, ami alkalmas arra, hogy megszegjük ezt a kontraktust, és mégis megváltoztassuk a megváltoztathatatlant nem kell, hogy érdekelje a Java-t: ez a mi felelősségünk. A villamosművek sem felelős azért, ha valaki bele \pisil a konnektorba.

Amit a JVM pedig tesz, bízva abban, hogy az Integer immutable, az az, hogy létrehoz előre 256 darab Integer objektumot -128 és 127 közötti értékekkel, beleértve a határokat is. Amikor pedig ebbe az intervallumba eső int értéket talál a Java kódban, akkor nem hoz létre új objektumot az autoboxinghoz, hanem felhasználja ezeket a már létrehozott objektumokat. Ennek pedig az is a következménye, amit a mellékelt kód is demonstrál, hogy ha átírjuk reflection-nel ezeknek az objektumoknak az értékeit, akkor az egész JVM-ben felrúghatjuk a -128 és 127 közötti Integer konstans objektumok értékeit.

VIGYÁZAT: azért nem az int literál konstansok értéket írjuk felül, csak az autoboxinghoz használt “final” Integer objektumok értékeit. Az igazi, genuine int változóink továbbra is megmaradnak, és a helyes értéket adják. És ha nem autoboxinggal hozunk létre Integer objektumot, akkor az is korrekt lesz. Ezt demonstrálja a következő kis kódrészlet:

	public static void main(final String[] args) throws Exception {
		Integer a;
		Integer b;
		a = 1;
		final Field f = Integer.class.getDeclaredField("value");
		f.setAccessible(true);
		f.setInt(a, 2);
		b = 1;
		System.out.println(a == b);
		System.out.println(b);
		a = new Integer(1);
		b = new Integer(1);
		System.out.println(a == b);
		System.out.println(b);
	}

Ennek eredménye:

true
2
false
1

De miért csinálja ezt a JVM? Azért, mert ezek azok az értékek, amelyek int konstansként a leggyakrabban előfordulnak a kódjaink futása során, és ha nem kell minden egyes alkalommal új Integer objektumot létrehoznia, akkor az valamennyit gyorsítani fog a kódon. Hogy mennyit is?

public class IntSpeed {

	private final static int N = 1000_000_000;

	private static void autoboxing() {
		for (int i = 0; i < N; i++) {
			Integer a = i % 128;
		}
	}

	private static void noboxing() {
		for (int i = 0; i < N; i++) {
			Integer a = new Integer(i % 128);
		}

	}

	public static void main(String[] args) {

		long tStart = System.currentTimeMillis();
		autoboxing();
		long tEnd = System.currentTimeMillis();
		System.out.println(tEnd - tStart);
		tStart = System.currentTimeMillis();
		noboxing();
		tEnd = System.currentTimeMillis();
		System.out.println(tEnd - tStart);
	}
}

Ezt Eclipse-ben debug módban futtatva (hogy a JIT ne zavarjon, és ne optimalizálja ki a nem használt változóinkat) 12ms és 7000ms futási időt produkált. Ha nem debug módban futtatjuk, akkor azért az sokat segít, de így is lényeges a különbség: 10ms és 40ms a futási idő.

És miért érdekes ez az egész történet? Miért kell ezzel foglalkozni? Egyszerűen nem csinálunk ilyent, és nem fáj a fejünk, nem futunk bele ilyen szituációba.

És az innen onnan összeszedett köcsögök (JAR-ok) ?

4 responses to “Ez már tényleg WTF!!!

  1. Visszajelzés:Integer cache konfigurálható. De miért billeg? | tifyty

  2. Visszajelzés:Google ImmutableMap | tifyty

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: