Monitorování procesů a správa paměti v JDK6 a JDK7 (2)

13. 1. 2011
Doba čtení: 18 minut

Sdílet

V osmé části seriálu o vlastnostech JDK si ukážeme použití profileru, jenž je přímo součástí standardní instalace JRE. Také si řekneme, proč není vhodné, aby se v často volaném kódu spojovaly řetězce pomocí operátorů + a +=. V závěru se navíc seznámíme se základními způsoby implementace správců paměti.

Obsah

1. Spojování řetězců a další problémy, které je provázejí

2. Způsob překladu operátorů + a += při práci s řetězci

3. Opakované skryté provádění interních kopií polí znaků – AbstractStrin­gBuilder.appen­d(String)

4. Další skryté kopie pole znaků v metodách String.getChars() a StringBuilder­.toString()

5. Malá rekapitulace

6. Základní analýza běhu programu – profiler JDK

7. Implementace správců paměti: klasický případ – počítání referencí

8. Přednosti a zápory správce paměti založeného na počítání referencí

9. Odkazy na Internetu

1. Spojování řetězců a další problémy, které je provázejí

V předchozí části seriálu o běhovém prostředí JDK 6 a JDK 7 jsme si řekli základní informace o problematice vytváření a rušení objektů. V samotném závěru předchozí části byly ukázány tři demonstrační programy, které – každý příklad ovšem pomocí jiného postupu – ve své metodě nazvané createString() vytvořily řetězec, v němž byly uloženy textové reprezentace čísel od nuly do zadané mezní hodnoty (konkrétně se jednalo o hodnotu 10000), přičemž jednotlivá čísla byla od sebe oddělena jednou mezerou. První příklad pro vytvoření řetězce používal „řetězcové“ operátory + a +=, ve druhém příkladu se použila optimalizace s využitím StringBufferu a jeho přetížené metody append(), do něhož se ovšem ukládal lokálně vytvářený řetězec, a konečně v příkladu třetím se již žádný lokální řetězec uvnitř programové smyčky nevytvářel, protože se jak číselná hodnota, tak i mezera vkládaly přímo do StringBufferu (číselná hodnota se samozřejmě interně musela převést do řetězcové podoby).

Zdrojové kódy demonstračních příkladů už zde nebudu znovu uvádět, aby se článek zbytečně neprodlužoval. Pokud si však chcete všechny tři demonstrační programy prohlédnout i se zvýrazněním syntaxe, je možné je nalézt pod následujícími odkazy: ConcatTest1, ConcatTest2 a ConcatTest3. Časy běhů všech tří programů i celkové zatížení procesoru se ovšem při měření velmi lišily, a to i přesto, že výsledkem práce všech programů byl shodný textový řetězec. Již v předchozí části jsme si řekli, že jednou z příčin je neustálé vytváření objektů uvnitř programové smyčky. Ovšem to není příčina jediná, i když poměrně podstatná, protože si v případě prvního příkladu vyžádala hned několikeré volání správce paměti. Druhou příčinou, proč je první program o mnoho pomalejší, než další dva programy a taktéž proč je třetí program (nepatrně) rychlejší než program druhý, je to, že se při práci s řetězci i při převodu StringBufferu či StringBuilderu na řetězec provádí kopie všech znaků, které řetězec tvoří (na tuto příčinu správně upozornili někteří čtenáři v  diskusi pod předchozím článkem). Pojďme se tedy podívat, jakým způsobem se vlastně interně s řetězci i s ostatními dvěma třídami sloužícími primárně pro úschovu řetězců pracuje.

2. Způsob překladu operátorů + a += při práci s řetězci

Před popisem způsobu profilování programů s využitím nástrojů dostupných přímo v JRE si nejdříve ukážeme, v čem konkrétně spočívají další problémy skryté zejména v prvním, ale částečně ve i druhém demonstračním příkladu. Nejprve se podíváme na to, jakým způsobem se přeloží metoda createString() z prvního demonstračního příkladu. Pro připomenutí – zdrojový kód této metody má následující tvar:

public static String createString()
{
    String str = "";
    for (int i = 0; i < LOOP_COUNT; i++)
    {
        str += i + " ";
    }
    return str;
}

Po překladu demonstračního příkladu do bajtkódu (ideálně provedeném i s použitím modifikátoru -g) můžeme z bajtkódu poměrně snadno zjistit, jak vůbec překladač Javy dokázal přeložit příkaz str += i + " ". Bajtkód všech veřejných metod získáme pomocí příkazu javap -c ConcatTest1. Pod tímto odstavcem jsou vypsány pouze ty instrukce virtuálního stroje Javy (JVM), které reprezentují metodu createString(). Způsob vlastní implementace počítané smyčky nás v tento okamžik příliš nezajímá, protože mnohem důležitější je to, jak je přeložen výše uvedený příkaz str += i + " ". Instrukce, kterými je tento zdánlivě jednoduchý příkaz implementován, jsou označeny na začátku každého řádku hvězdičkou:

public static java.lang.String createString();
  Code:
   0:   ldc             #2; //String
   2:   astore_0
   3:   iconst_0
   4:   istore_1
   5:   iload_1
   6:   sipush  10000
   9:   if_icmpge 42        // na bajtu s indexem 42 je konec smyčky
*  12:  new             #3; //class java/lang/StringBuilder
*  15:  dup
*  16:  invokespecial   #4; //Method java/lang/StringBuilder."(init)":()V
*  19:  aload_0
*  20:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
*  23:  iload_1
*  24:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
*  27:  ldc             #7; //String
*  29:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
*  32:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
*  35:  astore_0
   36:  iinc    1, 1
   39:  goto    5           // skok zpět na začátek smyčky
   42:  aload_0
   43:  areturn

Vzhledem k tomu, že výše uvedený bajtkód je pro velkou část programátorů v Javě poměrně nepřehledný a špatně čitelný, je níže pro ilustraci vypsán programový kód, který je do co největší míry ekvivalentní s instrukcemi, jež se vyskytují ve vygenerovaném bajtkódu. Povšimněte si, že se v tomto zdrojovém kódu instance třídy StringBuilder ihned po svém vytvoření (konstrukci) naplní původním řetězcem, ke kterému se mají připisovat další znaky. Posléze se do tohoto objektu přidá textová reprezentace celého čísla následovaná řetězcem obsahujícím mezeru. Použití řetězce bylo v tomto případě způsobeno tím, že by zápis i + ' ' ve skutečnosti znamenal přičtení konstanty 32 k hodnotě proměnné i, což vůbec není chování, které očekáváme (zmíněná konstanta 32 samozřejmě odpovídá ASCII hodnotě mezery – space). Poslední operací, která se s instancí třídy StringBuffer provádí, je převod jejího atributu na řetězec:

public static String createString()
{
    String str = "";
    for (int i = 0; i < LOOP_COUNT; i++)
    {
        StringBuilder tmp = new StringBuilder();
        tmp.append(str);
        tmp.append(i);
        tmp.append(" ");
        str = tmp.toString();
    }
    return str;
}

3. Opakované skryté provádění interních kopií polí znaků – AbstractStrin­gBuilder.appen­d(String)

I při letmém pohledu na fragment zdrojového kódu vypsaného v závěru předchozí kapitoly můžeme objevit jeden problém – uvnitř programové smyčky se vytváří pomocná instance třídy StringBuilder, z níž je na konci každé iterace získána nová podoba řetězce str (navíc je ještě, jak uvidíme dále, vytvořena i nová instance třídy String). O tomto problému – konkrétně o častém vytváření objektů s velmi krátkou dobou života – jsme se dozvěděli základní informace již v předchozí části tohoto seriálu. Ovšem v oněch pěti řádcích uvnitř programové smyčky jsou skryta i další úskalí, spočívající v tom, že se (i když skrytě) v operační paměti neustále provádí několik kopií polí znaků. S těmito úskalími – které ovšem zdaleka neplatí pouze pro třídu String, ale i pro některé další často používané programové konstrukce – se podrobněji seznámíme v následujících odstavcích, kde se taktéž podíváme na způsob, jakým jsou jednotlivé metody implementovány v knihovnách JDK. Zaměříme se přitom na implementaci použitou v (Open)JDK 7, ovšem podobně je tomu i v (Open)JDK 6.

První z tohoto pohledu zajímavou metodou je metoda StringBuilder­.append(Strin­g), která je je v OpenJDK 7 implementována následujícím způsobem:

public AbstractStringBuilder append(String str)
{
    if (str == null) str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

V této metodě jsou potenciálně výpočetně náročné dva příkazy – volání metody ensureCapacity­Internal(), tj. zajištění, aby se do StringBuilderu mohl skutečně připojit celý řetězec a taktéž volání metody String.getChar­s(), v níž se provádí defenzivní kopie pole znaků z řetězce do atributu value. První ze zmíněných metod – privátní metoda ensureCapacity­Internal() – není sama o sobě příliš zajímavá; ostatně posuďte sami:

private void ensureCapacityInternal(int minimumCapacity)
{
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

Zajímavější je již metoda expandCapacity(), která je zavolaná v případě, kdy je zapotřebí zvětšit kapacitu StringBufferu nebo StringBuilderu. V našem demonstračním případu ke zvětšení kapacity určitě dojde již po několika málo iteracích (kdy se průběžně rozšiřovaný řetězec zvětší), protože standardní velikost pole znaků je v případě StringBufferu() a StringBuilderu() rovna pouze 16 znakům. Při rozšíření kapacity bufferu samozřejmě musí dojít ke kopii pole znaků na začátek většího pole. Zda je tomu skutečně tak, se dozvíme při pohledu na zdrojový kód metody expandCapacity():

void expandCapacity(int minimumCapacity)
{
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

Z výše uvedeného výpisu je patrné, že se kopie pole znaků při zvětšení kapacity StringBufferu nebo StringBuilderu skutečně provádí a původní pole uložené v atributu value musí být následně uvolněno správcem paměti.

4. Další skryté kopie pole znaků v metodách String.getChars() a StringBuilder­.toString()

To ovšem zdaleka není jediná kopie pole, která se při provádění onoho tak nenápadného, ale o to více problematického příkazu str += i + „ “ musí provést. Další kopie je skryta ve výše popsané metodě StringBuilder­.append(Strin­g), konkrétně na programovém řádku str.getChars(0, len, value, count);, protože metoda String.getChars() provádí defenzivní kopii celého pole znaků (toto pole představuje interní podobu řetězce v operační paměti). O tom, že se tato kopie skutečně provádí, se můžeme snadno přesvědčit ve zdrojových kódech OpenJDK:

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
{
    if (srcBegin < 0)
    {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > count)
    {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd)
    {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, offset + srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

Poslední (konečně :-) problematickou část kódu můžeme najít v pátém příkazu v programové smyčce, tj. ve volání metody StringBuilder­.toString(). V této metodě se totiž vytvoří nová instance třídy String, která se bude inicializovat ještě jednou zkopírovaným polem znaků, protože metoda StringBuilder­.toString() má následující tvar:

public String toString()
{
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

Přičemž se v konstruktoru String(char value[], int offset, int count) volá metoda:

Arrays.copyOfRange(value, offset, offset+count);

5. Malá rekapitulace

Proveďme si malé shrnutí poznatků získaných z předchozích kapitol. Původní příkaz:

str += i + " ";

kde proměnná str je typu String a proměnná i je typu int, se přeloží do následující sekvence příkazů:

StringBuilder tmp = new StringBuilder();
tmp.append(str);
tmp.append(i);
tmp.append(" ");
str = tmp.toString();

V nichž se kromě jiného provádí i následující kopie polí znaků:

tmp.append(str):
    Arrays.copyOf (pouze v případě, že se zvětšuje kapacita bufferu)
    System.arraycopy
 
tmp.toString():
    Arrays.copyOfRange

Kromě výše zmíněného neustálého kopírování v podstatě stále těch samých znaků mezi několika regiony operační paměti se navíc při každé iteraci vytvoří tyto objekty:

StringBuilder
String

Přičemž se původní instance třídy String stane neaktivní, stejně jako lokálně (v programové smyčce) vytvořený objekt StringBuilder.

Ještě do této kapitoly doplním malou statistiku – délka vytvořeného řetězce je pro 10000 iterací rovna pouhým 48890 znakům a – pokud zanedbáme kopie polí prováděné v případě zvětšování kapacity bufferu – vyjde nám, že se pro získání takto krátkého řetězce musí v paměti přesunout celkem 478858990 znaků, tj. cca 912 MB! Počet kopírovaných znaků totiž roste se zvětšujícím se počtem iterací poměrně rychle:

Počet iterací Zkopírovaných bajtů
1 4
2 12
3 24
4 40
5 60
6 84
7 112
8 144
9 180
10 220
11 266
12 318
13 376
14 440
15 510
16 586
17 668
18 756
19 850
20 950

Mimochodem – ani druhý demonstrační příklad není zcela bez chybičky, protože se jeho programová smyčka ve tvaru:

StringBuffer str = new StringBuffer();
for (int i = 0; i < LOOP_COUNT; i++)
{
    str.append(i + " ");
}
return str.toString();

Přeloží takto:

StringBuffer str = new StringBuffer();
for (int i = 0; i < LOOP_COUNT; i++)
{
    StringBuilder tmp = new StringBuilder();
    tmp.append(i);
    tmp.append(" ");
    String s = tmp.toString();
    str.append(s);
}
return str.toString();

Což znamená, že se opět vytváří při každé iteraci několik dočasných objektů a taktéž se provádí kopie polí znaků. Ovšem v tomto případě se jedná pouze o velmi malá pole, navíc je zde časová složitost prakticky lineární, na rozdíl od problematického příkladu prvního.

6. Základní analýza běhu programu – profiler JDK

„Statické“ analýzy programového kódu, které jsme si ukázali v předchozích kapitolách, sice mohou vést k základnímu přehledu o tom, co se v běžícím procesu děje a kde mohou být jeho slabá místa, ovšem v mnoha případech nám nedají přesnou informaci o skutečně kritických částech kódu, tj. o částech, které se volají velmi často a které mnohdy tvoří nejdůležitější místo v programu, na něž by se měli programátoři zaměřit při optimalizacích. Pro tento účel se – kromě dalších nástrojů – používají i takzvané profilery. V případě použití Oracle JDK nebo OpenJDK mají vývojáři výhodu v tom, že mohou použít profiler zabudovaný přímo do JRE, tj. do běhového prostředí Javy. Je to ostatně to nejlepší místo, kam profiler umístit, protože právě JRE má všechny informace jak o načtených knihovnách, tak i o nativních metodách, které jsou interně volány z Javovského kódu.

Použití interního profileru JRE je jednoduché – při startu Javovské aplikace postačí použít parametr -Xprof s přesměrováním profilovacích informací ze standardního výstupu do souboru:

java -Xprof ConcatTest1 > ConcatTest1.prof

Výsledky běhu profileru mohou být v některých případech zajímavé, jak se ostatně ukazuje při spuštění profileru pro první demonstrační příklad, tj. pro příklad, ve kterém se provádí konkatenace řetězců v příkazu str += i + " ";. Z výstupu profileru je patrné, že nejvíce času proces ztratí při volání metod AbstractStrin­gBuilder.expan­dCapacity() (tato metoda obsahuje volání Arrays.copyOf(), jak jsme si již ostatně řekli ve třetí kapitole), dále pak v metodě AbstractStrin­gBuilder.appen­d() (s parametrem String, což zde není patrné) a taktéž při inicializaci řetězců:

Flat profile of 5.53 secs (297 total ticks): main
 
  Interpreted + native   Method
  1.0%     3  +     0    java.lang.AbstractStringBuilder.append
  0.7%     2  +     0    java.util.Arrays.copyOf
  0.3%     1  +     0    java.util.zip.ZipEntry.<init>
  0.3%     1  +     0    java.util.Arrays.copyOfRange
  0.3%     1  +     0    sun.misc.URLClassPath$3.run
  2.7%     8  +     0    Total interpreted
 
     Compiled + native   Method
 29.6%     0  +    88    java.lang.AbstractStringBuilder.expandCapacity
 19.9%    59  +     0    java.lang.AbstractStringBuilder.append
 18.9%    56  +     0    java.lang.String.<init>
 17.2%    51  +     0    java.lang.AbstractStringBuilder.append
 11.4%     0  +    34    java.util.Arrays.copyOfRange
 97.0%   166  +   122    Total compiled
 
         Stub + native   Method
  0.3%     0  +     1    java.lang.System.arraycopy
  0.3%     0  +     1    Total stub
 
 
Flat profile of 6.51 secs (1 total ticks): DestroyJavaVM
 
  Thread-local ticks:
100.0%     1             Unknown: thread_state
 
 
Global summary of 12.04 seconds:
100.0%   399             Received ticks
 23.1%    92             Received GC ticks
  2.0%     8             Other VM operations
  0.3%     1             Unknown code

Při profilování druhého demonstračního příkladu se ukazuje, že se náhrada spojování řetězců za připojování řetězce na konec StringBuilderu či StringBufferu, v každém případě vyplácí, neboť program je mnohem rychlejší (první řádek). Současně se zcela změnilo pořadí metod, v nichž běžící proces tráví nejvíce strojového času – ze 40% se jedná o nativní metodu System.arraycopy volanou (interně) v programové smyčce:

Flat profile of 0.00 secs (1 total ticks): Secondary finalizer
 
  Thread-local ticks:
100.0%     1             Unknown: no last frame
 
Flat profile of 0.09 secs (6 total ticks): main
 
  Interpreted + native   Method
 20.0%     1  +     0    java.util.Arrays.copyOfRange
 20.0%     1  +     0    java.lang.String.indexOf
 20.0%     1  +     0    java.lang.AbstractStringBuilder.stringSizeOfInt
 60.0%     3  +     0    Total interpreted
 
         Stub + native   Method
 40.0%     0  +     2    java.lang.System.arraycopy
 40.0%     0  +     2    Total stub
 
  Thread-local ticks:
 16.7%     1             Blocked (of total)
 
 
Flat profile of 6.51 secs (1 total ticks): DestroyJavaVM
 
  Thread-local ticks:
100.0%     1             Unknown: thread_state
 
 
Global summary of 6.61 seconds:
100.0%     7             Received ticks
 28.6%     2             Unknown code

Ve třetím demonstračním příkladu program strávil většinu svého přiděleného strojového času mimo vlastní metodu createString(), protože jediná metoda, která ovšem zabrala zhruba pouze čtvrtinu času, byla metoda AbstractStrin­gBuilder.appen­d(). Zde je ovšem nutné poznamenat, že pokud by se počet iterací například řádově zvýšil, byly by výsledky jiné (na druhou stranu by však první demonstrační příklad běžel i několik minut, protože u něj je časová složitost zhruba exponenciální):

Flat profile of 0.06 secs (4 total ticks): main
 
  Interpreted + native   Method
 25.0%     0  +     1    java.util.zip.ZipFile.open
 25.0%     1  +     0    java.util.LinkedHashMap.createEntry
 25.0%     1  +     0    java.lang.AbstractStringBuilder.append
 25.0%     0  +     1    java.lang.Runtime.gc
100.0%     2  +     2    Total interpreted
 
 
Flat profile of 6.52 secs (1 total ticks): DestroyJavaVM
 
  Thread-local ticks:
100.0%     1             Unknown: thread_state
 
 
Global summary of 6.57 seconds:
100.0%     5             Received ticks
 20.0%     1             Unknown code

7. Implementace správců paměti: klasický případ – počítání referencí

Po malé, ale poměrně důležité odbočce, v níž jsme se zabývali problémy s výpočetním výkonem a systémovou náročností některých operací s řetězci v Javě, se již konečně začneme věnovat popisu funkcí správců paměti, které jsou v posledních verzích JVM k dispozici. Správci paměti (garbage collectors – GC) tvoří nezbytnou a integrální součást virtuálního stroje jazyka Java, protože tento programovací jazyk je přímo navržen takovým způsobem, aby se vývojáři nemuseli ve většině případů starat o to, v jakém okamžiku a jakým způsobem mají odstraňovat objekty z operační paměti. Jak jsme si již řekli v předchozích částech tohoto seriálu, jsou všechny objekty, včetně objektů lokálních v dané funkci nebo programovém bloku (například v těle programové smyčky), vytvářeny dynamicky na haldě (heapu), nikoli na zásobníku. To je poměrně velký rozdíl při porovnání Javy a některých dalších programovacích jazyků, které dokážou lokální objekty alokovat v takzvaném zásobníkovém rámci (stack frame).

Programátoři navíc v Javě nemají přístup přímo k fyzické podobě objektů, tak jak jsou uloženy v operační paměti, ale získají pouze referenci na objekt, přes kterou přistupují k atributům a metodám objektů (to mimo jiné znamená, že objekty jako své atributy obsahují pouze hodnoty primitivních datových typů nebo reference na jiné objekty). Jedním z nejjednodušších a velmi pravděpodobně i jedním z prvních typů správců paměti, které byly vytvořeny, je správce paměti založený na počítání referencí (reference counting). Tento správce paměti sice není v JVM běžně používán, nicméně je dobré se s jeho funkcí seznámit a zjistit jeho přednosti a zápory. Správce paměti založený na počítání referencí přiřazuje každému objektu počitadlo (celočíselnou hodnotu), v němž je uložené, kolikrát je daný objekt referencován, jinými slovy, kolik k tomuto objektu v běžícím procesu existuje platných referencí či ukazatelů.

Správce paměti v tomto případě může odstranit pouze ty objekty, jejichž počitadlo dosáhlo nuly. Důležité přitom je, aby se při každém vzniku reference počitadlo zvýšilo o jedničku a navíc při každém zániku reference opět o jedničku snížilo. To například znamená, že příkaz (zde nikoli výraz!):

new Color(0x000000);

sice vytvoří objekt, ovšem počitadlo referencí bude rovno nule, zatímco příkazy:

Color c = new Color(0x000000);

popř:

callMethod(new Color(0x000000));

nebo:

private Color color = new Color(0x000000);

vytvoří objekt, jehož počitadlo referencí je nastaveno na jedničku.

bitcoin_skoleni

8. Přednosti a zápory správce paměti založeného na počítání referencí

Tento typ správce paměti je sice implementačně poměrně jednoduchý a současně i rychlý, protože není zapotřebí složitě zjišťovat, které objekty je možné odstranit, ovšem má jednu poměrně závažnou nevýhodu – nedokáže z paměti odstranit ty objekty, které tvoří cyklus, tj. například objekt A obsahující referenci na objekt B, který naopak obsahuje referenci na objekt A. Cyklus samozřejmě může být i delší, může se například jednat i o složitou grafovou strukturu s alespoň jedním cyklem. Druhou nevýhodou – i když méně závažnou – je to, že v mnoha případech může být neustálá změna hodnoty počitadla referencí výpočetně náročná, zejména kvůli nutnosti jeho synchronizace mezi všemi vlákny, které úloha používá (a navíc s vláknem či více vlákny samotného správce paměti), protože zvýšení počitadla o jedničku, snížení počitadla o jedničku a současný test na jeho nulovou hodnotu nebývají na současných procesorech atomické operace. Ovšem již první uvedený důvod (neodstranění objektů tvořících cyklus) vedl k tomu, že se s tímto správcem paměti v JDK prakticky nesetkáme, i když by se tento správce paměti hodil především na realtimeové úlohy.

V moderních implementacích JDK se namísto toho setkáme se správci paměti, které jsou založené na zjišťování, které objekty jsou v daném okamžiku dostupné z každého vlákna aplikace. Vzhledem k tomu, že test na „živost“ objektů je poměrně náročný, jsou objekty podle svého chování za běhu aplikace rozdělovány do několika skupin, přičemž se s každou skupinou objektů zachází poněkud odlišným způsobem. Bližší informace o principu práce těchto správců paměti, způsobů jejich monitorování (to je samozřejmě možné a někdy i nutné) i optimalizací jejich parametrů, budou uvedeny v následující části tohoto seriálu.

9. Odkazy na Internetu

  1. Dr. Dobb's | G1: Java's Garbage First Garbage Collector
    http://www.drdob­bs.com/article/prin­tableArticle.jhtml?ar­ticleId=219401061­&dept_url=/ja­va/
  2. Java's garbage-collected heap
    http://www.ja­vaworld.com/ja­vaworld/jw-08–1996/jw-08-gc.html
  3. Compressed oops in the Hotspot JVM
    http://wikis.sun­.com/display/Hot­SpotInternals/Com­pressedOops
  4. 32-bit or 64-bit JVM? How about a Hybrid?
    http://blog.ju­ma.me.uk/2008/10/­14/32-bit-or-64-bit-jvm-how-about-a-hybrid/
  5. Compressed object pointers in Hotspot VM
    http://blogs.sun­.com/nike/entry/com­pressed_objec­t_pointers_in_hot­spot
  6. Java HotSpot™ Virtual Machine Performance Enhancements
    http://downlo­ad.oracle.com/ja­vase/7/docs/techno­tes/guides/vm/per­formance-enhancements-7.html
  7. Using jconsole
    http://downlo­ad.oracle.com/ja­vase/1.5.0/doc­s/guide/manage­ment/jconsole­.html
  8. jconsole – Java Monitoring and Management Console
    http://downlo­ad.oracle.com/ja­vase/1.5.0/doc­s/tooldocs/sha­re/jconsole.html
  9. Great Computer Language Shootout
    http://c2.com/cgi/wi­ki?GreatCompu­terLanguageSho­otout
  10. x86–64
    http://en.wiki­pedia.org/wiki/X86–64
  11. Physical Address Extension
    http://en.wiki­pedia.org/wiki/Phy­sical_Address_Ex­tension
  12. Java performance
    http://en.wiki­pedia.org/wiki/Ja­va_performance
  13. 1.6.0_14 (6u14)
    http://www.ora­cle.com/technet­work/java/java­se/6u14–137039.html?ssSou­rceSiteId=otncn
  14. Update Release Notes
    http://www.ora­cle.com/technet­work/java/java­se/releasenotes-136954.html
  15. 4.10 Limitations of the Java Virtual Machine
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/Clas­sFile.doc.html#88659
  16. Java™ Platform, Standard Edition 7 Binary Snapshot Releases
    http://dlc.sun­.com.edgesuite­.net/jdk7/bina­ries/index.html
  17. Trying the prototype
    http://mail.o­penjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  18. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  19. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis
  20. Class ReflectiveOpe­rationExcepti­on
    http://downlo­ad.java.net/jdk7/doc­s/api/java/lan­g/ReflectiveO­perationExcep­tion.html
  21. Proposal: Indexing access syntax for Lists and Maps
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  22. Proposal: Elvis and Other Null-Safe Operators
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  23. Java 7 : Oracle pushes a first version of closures
    http://www.bap­tiste-wicht.com/2010/05­/oracle-pushes-a-first-version-of-closures/
  24. Groovy: An agile dynamic language for the Java Platform
    http://groovy­.codehaus.org/O­perators
  25. Better Strategies for Null Handling in Java
    http://www.sli­deshare.net/Step­han.Schmidt/bet­ter-strategies-for-null-handling-in-java
  26. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  27. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  28. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  29. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  30. Project Coin: Bringing it to a Close(able)
    http://blogs.sun­.com/darcy/en­try/project_co­in_bring_close
  31. ClosableFinder source code
    http://blogs.sun­.com/darcy/re­source/Projec­tCoin/Closeable­Finder.java
  32. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  33. Java 7 – more dynamics
    http://www.bap­tiste-wicht.com/2010/04­/java-7-more-dynamics/
  34. ArrayList (JDK 1.4)
    http://downlo­ad.oracle.com/ja­vase/1.4.2/doc­s/api/java/util/A­rrayList.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.