Obsah
1. Pohled pod kapotu JVM – přednosti a zápory využití JNI při optimalizacích (2)
2. Instrukce bajtkódu JVM určené pro práci s poli
3. Základní JNI funkce pro práci s poli primitivních datových typů
4. První demonstrační příklad: předání pole nativní funkci a zjištění délky pole
5. Překlad a spuštění prvního demonstračního příkladu
6. Získání ukazatele na prvky pole vytvořeného v javovské části aplikace
7. Druhý demonstrační příklad: získání ukazatele na prvky pole a výpočet průměrné hodnoty
8. Problematika práce s poli v nativních funkcích s ohledem na automatickou správu paměti
9. JNI funkce GetPrimitiveArrayCritical() a ReleasePrimitiveArrayCritical()
10. Obsah následující části seriálu
11. Repositář se zdrojovými soubory i se skripty pro překlad a spuštění
1. Pohled pod kapotu JVM – přednosti a zápory využití JNI při optimalizacích (2)
V předchozí části seriálu o jazyce Java i o virtuálním stroji Javy jsme si ukázali základní způsob využití rozhraní JNI (Java Native Interface), díky jehož existenci je možné přímo z Javy relativně jednoduše volat nativní funkce a metody, tj. programový kód, který byl ještě před spuštěním JVM přeložen přímo do strojového kódu cílového mikroprocesoru. Víme již, že volání nativních funkcí z virtuálního stroje Javy je časově poměrně náročné, což znamená, že v mnoha případech ani nemá smysl se snažit o reimplementaci některých částí algoritmů v jazyce C či C++, protože dokonce může nastat situace, kdy bude volání nativního kódu pomalejší než ekvivalentní část algoritmu naprogramovaná přímo v Javě a přeložená JIT překladačem (ať již typu client či častěji typu server). Ovšem samotná zvýšená režie volání nativních funkcí není jediný problém, s nímž se můžeme při použití JNI v praxi setkat.
Další problémy nastávají zejména při předávání parametrů nativním funkcím a metodám. Je totiž nutné zajistit buď kopii těchto parametrů, nebo je nutné příslušné Javovské objekty označit takovým způsobem, aby s nimi nebylo v průběhu spuštění nativní funkce manipulováno například při správě paměti. Tento problém se stává palčivým především ve chvíli, kdy nativní funkce musí manipulovat s polem vytvořeným v Javě. I přes podobnou syntaxi, která je při práci s poli použita v Javě na jedné straně a v jazycích C/C++ na straně druhé, je práce s poli v těchto jazycích odlišná, což si ostatně ukážeme na demonstračních příkladech. V Javě se k prvkům polí nepřistupuje přes ukazatel, dokonce se ani v bajtkódu nemusí složitě počítat adresy jednotlivých prvků. Namísto toho bajtkód JVM obsahuje několik instrukcí, které práci s poli zjednodušují (což je mimochodem jeden z důvodů, proč je práce s poli v mnoha případech velmi efektivní).
2. Instrukce bajtkódu JVM určené pro práci s poli
Pro zajímavost si ukažme, jaké instrukce v bajtkódu JVM jsou určeny pro práci s poli. U instrukcí bez operandů je důležité si uvědomit, že příslušná data (hodnoty, indexy) musí být před zavoláním těchto instrukcí uloženy na zásobníku operandů:
# | Instrukce | Opkód | Operandy | Prováděná operace |
---|---|---|---|---|
1 | newarray | 0×BC | arraytype | vytvoří nové pole s prvky primitivního datového typu |
2 | anewarray | 0×BD | highbyte, lowbyte | vytvoří nové pole objektů |
3 | multianewarray | 0×C5 | highbyte, lowbyte, dimensions | vytvoří vícedimenzionální pole o dimensions dimenzích |
4 | iaload | 0×2E | × | přečtení prvku z pole typu int[] |
5 | laload | 0×2F | × | přečtení prvku z pole typu long[] |
6 | faload | 0×30 | × | přečtení prvku z pole typu float[] |
7 | daload | 0×31 | × | přečtení prvku z pole typu double[] |
8 | aaload | 0×32 | × | přečtení prvku z pole typu reference[] |
9 | baload | 0×33 | × | přečtení prvku z pole typu byte[] nebo boolean[] |
10 | caload | 0×34 | × | přečtení prvku z pole typu char[] |
11 | saload | 0×35 | × | přečtení prvku z pole typu short[] |
12 | iastore | 0×4F | × | zápis nové hodnoty prvku do pole typu int[] |
13 | lastore | 0×50 | × | zápis nové hodnoty prvku do pole typu long[] |
14 | fastore | 0×51 | × | zápis nové hodnoty prvku do pole typu float[] |
15 | dastore | 0×52 | × | zápis nové hodnoty prvku do pole typu double[] |
16 | aastore | 0×53 | × | zápis nové hodnoty prvku do pole typu reference[] |
17 | bastore | 0×54 | × | zápis nové hodnoty prvku do pole typu byte[] nebo boolean[] |
18 | castore | 0×55 | × | zápis nové hodnoty prvku do pole typu char[] |
19 | sastore | 0×56 | × | zápis nové hodnoty prvku do pole typu short[] |
3. Základní JNI funkce pro práci s poli primitivních datových typů
Rozhraní JNI obsahuje několik funkcí určených pro práci s poli primitivních datových typů, které byly vytvořeny (zkonstruovány) v javovské části aplikace. Před popisem těchto funkcí si však musíme říci, jak se mapují javovské primitivní datové typy (ty jsou nezávislé na platformě) s datovými typy deklarovanými v jni.h. I tyto typy jsou nezávislé na platformě, na rozdíl od základních céčkových datových typů (int, short int, long int…):
# | Typ v Javě | Typ v JNI | Velikost a formát |
---|---|---|---|
1 | boolean | jboolean | unsigned 8 bits |
2 | byte | jbyte | signed 8 bits |
3 | char | jchar | unsigned 16 bits |
4 | short | jshort | signed 16 bits |
5 | int | jint | signed 32 bits |
6 | long | jlong | signed 64 bits |
7 | float | jfloat | 32 bits |
8 | double | jdouble | 64 bits |
Pole jsou do nativních funkcí předávány ve formě speciálního referenčního typu odvozeného od obecného typu pole jarray:
# | Typ v Javě | Typ v JNI |
---|---|---|
1 | boolean[] | jbooleanArray |
2 | byte[] | jbyteArray |
3 | char[] | jcharArray |
4 | short[] | jshortArray |
5 | int[] | jintArray |
6 | long[] | jlongArray |
7 | float[] | jfloatArray |
8 | double[] | jdoubleArray |
9 | Object[] | jobjectArray |
Nyní již následuje slíbený seznam základních funkcí použitých v rozhraní JNI pro práci s poli:
# | Funkce | Význam |
---|---|---|
1 | jsize GetArrayLength(JNIEnv *env, jarray array) | vrátí délku pole |
2 | jobjectArray NewObjectArray(JNIEnv *env, jsize len, jclass clazz, jobject init) | vytvoří nové pole libovolných objektů |
3 | jbooleanArray NewBooleanArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu boolean |
4 | jbyteArray NewByteArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu byte |
5 | jcharArray NewCharArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu char |
6 | jshortArray NewShortArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu short |
7 | jintArray NewIntArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu int |
8 | jlongArray NewLongArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu long |
9 | jfloatArray NewFloatArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu float |
10 | jdoubleArray NewDoubleArray(JNIEnv *env, jsize len) | vytvoří nové pole s prvky typu double |
11 | jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) | přečtení objektu z pole |
12 | void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject val) | zápis do pole objektů |
Další důležité funkce (pracující s poli prvků primitivních datových typů) si popíšeme v navazujících kapitolách.
4. První demonstrační příklad: předání pole nativní funkci a zjištění délky pole
V dnešním prvním demonstračním příkladu si ukážeme, jakým způsobem je možné do nativní funkce předat javovské pole a jak lze využít JNI pro získání počtu prvků v tomto poli. Příklad je skutečně velmi jednoduchý, protože (prozatím) nepotřebujeme číst ani zapisovat jednotlivé prvky uložené v poli.
JNITest4.java:
/** * Jednoduchy test na predavani poli do nativni funkce/metody. */ public class JNITest4 { // hlavicka ntivni funkce native public static int arrayLength(int[] array); public static void main(String[] args) { // nacist sdilenou knihovnu s nativni funkci arrayLength System.loadLibrary("JNITest4"); int[] array = new int[10]; // zavolat nativni funkci int length = arrayLength(array); System.out.println("Array length = " + length); } }
JNITest4.h (generovaný přes nástroj javah):
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JNITest4 */ #ifndef _Included_JNITest4 #define _Included_JNITest4 #ifdef __cplusplus extern "C" { #endif /* * Class: JNITest4 * Method: arrayLength * Signature: ([I)I */ JNIEXPORT jint JNICALL Java_JNITest4_arrayLength (JNIEnv *, jclass, jintArray); #ifdef __cplusplus } #endif #endif
JNITest4.c:
#include "JNITest4.h" JNIEXPORT jint JNICALL Java_JNITest4_arrayLength(JNIEnv *jni_env, jclass klass, jintArray array) { /* ziskat delku pole */ jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array); return length; }
Povšimněte si, že všechny JNI funkce je nutné volat přes ukazatel typu JNIEnv*.
5. Překlad a spuštění prvního demonstračního příkladu
Zdrojový kód dnešního prvního demonstračního příkladu se, jak je to běžné, přeloží překladačem javac:
javac JNITest4.java
Následně je nutné vygenerovat hlavičkový soubor JNITest4.h:
javah JNITest4
Následuje překlad souboru JNITest4.c uvedeného v předchozí kapitole, což může v případě Linuxu vypadat následovně (cestu k jni.h a dalším potřebným souborům bude samozřejmě nutné upravit na základě konfigurace konkrétní distribuce Linuxu):
gcc -shared -I/usr/lib/jvm/java-1.7.0-openjdk/include/ -o libJNITest4.so JNITest4.c
Popř. na platformách vyžadujících PIC (position independent code) v knihovnách:
gcc -shared -fPIC -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \ -I/usr/lib/jvm/java-1.7.0-openjdk/include/linux \ -o libJNITest4.so JNITest4.c
Na systému Windows s nainstalovanou JDK 7 by mohl překlad s využitím mingw vypadat následovně:
gcc -shared -I"c:\Program Files\Java\jdk1.7.0_25\include" -o JNITest4.dll JNITest4.c
V obou případech by měla vzniknout sdílená dynamicky linkovaná knihovna pojmenovaná v závislosti na použitém operačním systému buď libJNITest4.so či JNITest4.dll. Tyto názvy je zapotřebí zkontrolovat a dodržet, protože v případě jejich změny by došlo k chybě při volání System.loadLibrary(„JNITest4“)!
Spuštění testu na Linuxu je snadné:
export LD_LIBRARY_PATH=. java JNITest4
Na Windows taktéž:
java JNITest4
Pokud překlad i spuštění proběhlo v pořádku, měl by se na standardní výstup vypsat následující řádek:
Array length = 10
6. Získání ukazatele na prvky pole vytvořeného v javovské části aplikace
Když se podíváme na hlavičku nativní funkce JNITest4.arrayLength(), uvidíme, že se pole celých čísel do této funkce předává ve formě parametru typu jintArray:
JNIEXPORT jint JNICALL Java_JNITest4_arrayLength(JNIEnv *jni_env, jclass klass, jintArray array)
My ovšem v programovacím jazyku C či C++ potřebujeme k prvkům pole přistupovat jako k nativním polím, tj. přes ukazatel na první prvek pole. K tomuto účelu lze v rozhraní JNI použít následující osmici funkcí, které kýžený ukazatel vrátí:
# | Návratový typ | Funkce | Popis |
---|---|---|---|
1 | jboolean * | GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu boolean[] |
2 | jbyte * | GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu byte[] |
3 | jchar * | GetCharArrayElements(JNIEnv *env, jcharArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu char[] |
4 | jshort * | GetShortArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu short[] |
5 | jint * | GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu int[] |
6 | jlong * | GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu long[] |
7 | jfloat * | GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu float[] |
8 | jdouble * | GetDoubleArrayElements(JNIEnv *env, jdoubleArray array, jboolean *isCopy) | vrátí ukazatel na první prvek v poli typu double[] |
Zajímavý je poslední parametr těchto funkcí, jenž je typu jboolean *. Přes tento parametr se budeme moci dozvědět, zda se provedla kopie celého pole či nikoli. O této velmi důležité problematice se ještě zmíním v navazujících kapitolách. V případě, že nás informace o provedené či neprovedené kopii nezajímá, lze do posledního parametru předat hodnotu NULL (potom informaci o kopii nezískáme, což však někdy nemusí vadit).
Důležité je nezapomenout získané pole uvolnit, a to opět s využitím jedné z osmi funkcí deklarovaných v rozhraní JNI. Pokud se totiž při volání Get*ArrayElements celé pole zkopíruje, je nutné je ručně z paměti uvolnit, jinak by došlo k memory leaku (ten většina JVM nedokáže správně detekovat):
# | Návratový typ | Funkce | Popis |
---|---|---|---|
1 | void | ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode) | uvolnění pole získaného funkcí GetBooleanArrayElements |
2 | void | ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) | uvolnění pole získaného funkcí GetByteArrayElements |
3 | void | ReleaseCharArrayElements(JNIEnv *env, jcharArray array, jchar *elems, jint mode) | uvolnění pole získaného funkcí GetCharArrayElements |
4 | void | ReleaseShortArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) | uvolnění pole získaného funkcí GetShortArrayElements |
5 | void | ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode) | uvolnění pole získaného funkcí GetIntArrayElements |
6 | void | ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) | uvolnění pole získaného funkcí GetLongArrayElements |
7 | void | ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) | uvolnění pole získaného funkcí GetFloatArrayElements |
8 | void | ReleaseDoubleArrayElements(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode) | uvolnění pole získaného funkcí GetDoubleArrayElements |
V posledním parametru těchto funkcí lze určit, zda se má provést zpětná kopie změněných prvků céčkového (nativního) pole do pole viditelného v javovské části aplikace. Pokud se žádný prvek nezměnil (pole se jen četlo), lze právě tímto parametrem provést optimalizaci a nezatěžovat mikroprocesor zbytečným přesunem dat.
7. Druhý demonstrační příklad: získání ukazatele na prvky pole a výpočet průměrné hodnoty
V dnešním druhém demonstračním příkladu si ukážeme, jak je možné s využitím JNI funkcí popsaných v předchozí kapitole přistupovat k prvkům javovských polí. Úkolem nativní funkce bude vypočítat průměrnou hodnotu všech prvků pole typu float[].
JNITest5.java:
Javovská část aplikace je jednoduchá – pouze vytvoří pole, předá ho do nativní funkce a vytiskne výsledek výpočtu:
/** * Test s nativni metodou, ktera vrati prumer hodnot ulozenych * v poli. */ public class JNITest5 { // hlavicka ntivni funkce native public static float average(float[] array); public static void main(String[] args) { // nacist sdilenou knihovnu s nativni funkci average() System.loadLibrary("JNITest5"); float[] array = {1f, 2f, 3f, 4f, 5f}; // zavolat nativni funkci float average = average(array); System.out.println("Average = " + average); } }
JNITest5.h (generovaný přes nástroj javah):
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JNITest5 */ #ifndef _Included_JNITest5 #define _Included_JNITest5 #ifdef __cplusplus extern "C" { #endif /* * Class: JNITest5 * Method: average * Signature: ([F)F */ JNIEXPORT jfloat JNICALL Java_JNITest5_average (JNIEnv *, jclass, jfloatArray); #ifdef __cplusplus } #endif #endif
JNITest5.c:
V nativní části aplikace je nutné nejprve přečíst délku pole, následně získat ukazatel na první prvek, provést výpočet a posléze celé pole uvolnit, aby nedošlo k memory leaku:
#include "JNITest5.h" JNIEXPORT jfloat JNICALL Java_JNITest5_average(JNIEnv *jni_env, jclass klass, jfloatArray array) { jint release_mode = 0; /* JNI_ABORT */ jboolean is_copy; jfloat average = 0; /* ziskat delku pole */ jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array); /* prevod na "ceckove" pole prvku typu float */ jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, &is_copy); /* vypsat informaci o tom, zda muselo byt pole zkopirovano */ puts(is_copy ? "copy array" : "no copy"); /* pruchod polem a vypocet sumy prvku */ jfloat *item = f; int i; for (i=0; i<length; i++, item++) { average += *item; } /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */ (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode); /* vypocet prumeru */ return average/length; }
8. Problematika práce s poli v nativních funkcích s ohledem na automatickou správu paměti
Pokud předchozí demonstrační příklad vypsal na standardní výstup následující text:
Average = 3.0 copy array
znamená to, že se při volání JNI funkce GetFloatArrayElements() provedla kopie prvků pole do nově naalokované paměti. Mnoho JVM tuto kopii provádí, a to z toho důvodu, aby se při automatické správě paměti (GC) nemuselo čekat na dokončení nativní funkce. Připomeňme si, že javovské programy nemohou získat ukazatel na žádný objekt, tj. ani na pole, takže si případného přesunu celého pole na jiné místo na haldě „nevšimnou“, což však v žádném případě neplatí pro céčkový program, který již získal ukazatel na pole. Při volání funkce ReleaseFloatArrayElements() se naopak může (ale nemusí) provést zpětná kopie prvků z céčkového pole do pole javovského – vše záleží na posledním parametru této funkce, kde se většinou ponechává nula (provést zpětnou kopii) či konstanta JNI_ABORT (neprovádět kopii).
Poznámka: to, že se provádí kopie prvků pole znamená, že obsah pole viditelného z Javy a z nativní funkce se může lišit a navíc může při uvolňování pole dojít k přepisu pole viditelného z Javy. To se sice může zdát poněkud problematické, ve skutečnosti je však toto chování zcela korektní a případné kolize je nutné ošetřit například synchronizačními mechanismy.
9. JNI funkce GetPrimitiveArrayCritical() a ReleasePrimitiveArrayCritical()
Vzhledem k tomu, že se kopie a zpětné kopie pole mohou stát kritickým místem aplikací, v nichž bude docházet k velkému zpomalení, byly do rozhraní JNI přidány další dvě funkce, které mohou (ale nutně nemusí!!!) tento problém řešit. Tyto dvě funkce se jmenují GetPrimitiveArrayCritical() a ReleasePrimitiveArrayCritical(), viz též následující tabulku:
# | Návratový typ | Funkce |
---|---|---|
1 | void * | GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy) |
2 | void | ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode) |
Základní sémantika těchto funkcí je shodná se skupinou Get*ArrayElements() a Release*ArrayElements(), ovšem s tím rozdílem, že se předpokládá, že kód mezi voláním obou funkcí bude velmi krátký a nebudou se zde volat další JNI funkce, které by například mohly způsobit výjimku atd. Pokud virtuální stroj Javy předpokládá jen (relativně) krátkou dobu běhu nativního kódu mezi GetPrimitiveArrayCritical() a ReleasePrimitiveArrayCritical(), může upustit od provedení kopie prvků pole (a popř. navíc pozdržet běh GC). Toto chování ovšem není zaručeno pro všechny implementace JVM, takže je nutné otestovat hodnotu vrácenou přes poslední parametr isCopy, zda ke kopii prvků dochází či nikoli.
Poznámka: povšimněte si, původních funkcí pro získání pole je celkem osm (pro každý primitivní datový typ jedna funkce), zatímco funkce GetPrimitiveArrayCritical() je pouze jediná. To je dosti nesystematické řešení, které pravděpodobně vychází z toho, že funkce GetPrimitiveArrayCritical() a ReleasePrimitiveArrayCritical() byly do rozhraní JNI přidány až později.
10. Obsah následující části seriálu
V následující části tohoto seriálu si na benchmarku vyzkoušíme, jak se v praxi bude odlišovat chování nativních funkcí v případě využití dvojice Get*ArrayElements()+Release*ArrayElements() v porovnání s funkcemi GetPrimitiveArrayCritical()+ReleasePrimitiveArrayCritical(). Taktéž si, podobně jako tomu bylo i v předchozí části tohoto seriálu, porovnáme rychlost nativních funkcí volaných přes JNI s obdobnou metodou implementovanou v Javě. Výsledky budou velmi zajímavé (a nutno říci, že pro nativní funkce nepříliš povzbudivé :-).
Taktéž nezapomeneme ani na problematiku předávání řetězců do nativních funkcí přes rozhraní JNI, protože i zde může docházet k procesům, které mohou celé využití JNI do značné míry zpomalit. Navíc je nutné si dát pozor na způsob kódování znaků v řetězci i na případné memory leaky, které mohou při použití JNI nastat (tyto memory leaky jsou o to zákeřnější, že řetězce jsou většinou krátké, takže se na případný problém přijde například až poté, co aplikace běží několik týdnů zdánlivě bez chyby).
11. Repositář se zdrojovými soubory i se skripty pro překlad a spuštění
Následuje – v tomto seriálu již tradiční – kapitola s odkazy na zdrojové kódy uložené do Mercurial repositáře. V následující tabulce najdete odkazy na prozatím nejnovější verzi dnes použitých demonstračních příkladů:
12. Odkazy na Internetu
- Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html