﻿<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/index.xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>A Java szemétgyűjtő titkai</title>
<link href="/_sweb4/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div style="margin: 0px auto; width: 985px;" id="menudiv">
  <!-- Menü -->
  <ul class="menu" id="menu">
    <li>
      <a href="#" class="menulink">Informatika</a>
      <ul>
        <li>
          <a href="#" class="sub">Nyílt forrás</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/nyilt/lnw.htm">A Linux nem Windows</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-katedralis-es-a-bazar/">A katedrális és a bazár</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Zárt forrás</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/what-is-the-matrix/">What is the Matrix?</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Történelem</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/tortenelem/commodore.htm">Commodore</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/szszgep.htm">Személyi számítógépek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/x86cpu.htm">PC processzorok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/archi.htm">Számítógép-architektúrák</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/geos.htm">GEOS</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Joel on Software</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/informatika/joel/gui.htm">Felhasználói felületek</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/mkernel.htm">Mikrokernel</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/hasznalat.htm">A gép használata</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/linux-vagy-windows/">Linux vagy Windows?</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/javabytecode.htm">Java bájtkód</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/javagc.htm">Java szemétgyűjtő</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/java/java8.htm">A Java 8 újdonságai</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/mbrgpt.htm">Az MBR-től a GPT-ig</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Tesztek</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/tesztek/winteszt.htm">Melyik a gyorsabb?</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tesztek/kindle.htm">Amazon Kindle</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Tudomány</a>
      <ul>
        <li>
          <a href="#" class="sub">Atomerőművek</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/tudomany/atom/baleset.htm">Atomerőmű balesetek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/atom/19ev.htm">Csernobil 19 évvel később</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/atom/csernobil.htm">Csernobil</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Űrkutatás</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/tudomany/urkutatas/voyager.htm">A Voyager szondák</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/urkutatas/voyagercomp.htm">A Voyager-ek számítógépe</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Szárazföld</a>
      <ul>
        <li>
          <a href="#" class="sub">Lamborghini</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/gt.htm">350 GT/400 GT</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/miura.htm">Miura</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/countach.htm">Countach</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Munkagépek</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Dömperek</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/terex-titan/">Terex Titan</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/munkagepek/domper/cat797/cat797.htm">Caterpillar 797</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Kotrógépek</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/munkagepek/kotrogep/muskie.htm">Big Muskie</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Építmények</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Épületek</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/rekord/rekord.htm">Magassági rekordok</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/taipei101/taipei101.htm">Taipei 101</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/petronas/petronas.htm">Petronas tornyok</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/swfc/swfc.htm">SWFC</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/hins/hins.htm">Home Insurance</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Tornyok</a>
              <ul>
                <li class="topline">
                  <a href="https://new.egalizer.hu/osztyankino-tv-torony/">Osztyankino TV-torony</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/eiffel-torony/">Eiffel torony</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Egyéb</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/egyeb/tenna.htm">Antennák</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/akashi-kaikyo-hid/">Akashi Kaikyo</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Vasút</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/sinkanzen/">Sinkanzen</a>
            </li>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/vasut/nagyseb.htm">Nagy sebességű vonatok</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Egyéb</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/egyeb/wind.htm">Big Wind</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Zene</a>
      <ul>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/paul-hindemith-zene-es-erzelem/">Zene és érzelem</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/arthur-honegger-a-jelen-es-a-jovo-kilatasai/">A jövőről</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/benjamin-britten-palyamrol/">Pályámról</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Zeneszerzők</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Wolfgang Amadeus Mozart</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/mozart-porosz-vonosnegyesei/">Porosz vonósnégyesek</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/zeneszerzok/mozart/zvers.htm">Fortepiano versenyek</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/utazas-a-jupiterre/">Utazás a Jupiterre</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Joseph Haydn</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/a-szimfonia-szuletese/">A szimfónia születése</a>
                </li>
              </ul>
            </li>			
            <li>
              <a href="#" class="sub">Bartók Béla</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/bartok-bela-oneletrajz/">Önéletrajz</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/szamomra-minden-nap-bartok-evfordulo/">Évforduló</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Gustav Mahler</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/ket-es-fel-perc-csond/">Két és fél perc csönd</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Ludwig van Beethoven</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/zeneszerzok/beethoven/kilenc.htm">Kilencedik szimfónia</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="http://www.egalizer.hu/zene/zeneszerzok/webern.htm">Anton Webern</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Karmesterek</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Wilhelm Furtwängler</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw.htm">Bevezetés</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw2.htm">Furtwängler élete</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw3.htm">Furtwängler lemezen</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/friedrich-schnapp/">Friedrich Schnapp</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Willem Mengelberg</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/karmesterek/mengel.htm">Columbia felvételek</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/mengel_opk.htm">Opus Kura lemezek</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/cd-velemenyek/">CD vélemény</a>
        </li>
        <li>
          <a href="#" class="sub">Sorozatok, kiadók</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/zene/kiado/greatcon.htm">Great Conductors</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://www.egalizer.hu/zene/librettok/librettok.htm">Szövegkönyvek</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">High Fidelity</a>
      <ul>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/esszek/zenehifi.htm">A zene és a hifi</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/esszek/termtorz.htm">A HiFiről</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-high-end-rol/">A High End</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/esszek/modszer.htm">Módszerek és csapdák</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-digitalis-kihivas/">A digitális kihívás</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/loudness-unit-cd-adatbazis/">Hangosság adatbázis</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/sajat-rendszerem/">Saját rendszer</a>
        </li>
        <li>
          <a href="#" class="sub">Salvatore</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/highend/bev.htm">Bevezetés</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/tortenet.htm">Történetem</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/kereskedelem.htm">Kereskedelem</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/lemezek.htm">Hanglemezek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/filo.htm">Filozófiám</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/magazin.htm">Magazinok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/lp12.htm">Linn Sondek</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Hangtechnika</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/hangtechnika/tda1541/tda1541.htm">TDA1541</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/resta/resta.htm">Restaurálás</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/cd-sacd-dvd-audio/">Formátumok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/zfelv.htm">A zenei felvételekről</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/nagyfel/nagyfel.htm">A nagy felbontásról</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/futomuvek.htm">CD futóművek tesztje</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Történelem</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/tortenelem/cd.htm">A Compact Disc</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/pcm-felvetelek/">Az első PCM felvételek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/tortenelem/gramo.htm">Gramofónia</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/solti-ringje-es-a-bitrata/">Solti Ringje és a bitráta</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Készülékek</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/magyar-hi-fi-keszulekgyartok-listaja/">Magyar gyártók</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Irodalom</a>
      <ul>
        <li class="topline">
          <a href="#" class="sub">Írók</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/rejto-jeno-szomoru-elete/">Rejtő Jenő</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/olaf-stapledon-elete/">Olaf Stapledon</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/a-kepes-kronika/">Képes Krónika</a>
        </li>		
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Tárgyak</a>
        <ul>
            <li class="topline">
              <a href="#" class="sub">Kockák a négyzeten</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak1.htm">Az őskor</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak2.htm">A hőskor 1.</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak3.htm">A hőskor 2.</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/ajandek.htm">Ajándék- készletek</a>
                </li>
              </ul>                  
            </li>			
            <li>
              <a href="#" class="sub">Karórák</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/eichi2.htm">Eichi 2</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/orakar.htm">Rejtett költségek</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/udvozlet-japanbol/">Üdvözlet Japánból</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/kings.htm">A King Seiko története</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/king1.htm">Az első King Seiko</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/57gs.htm">Az második Grand Seiko</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/cosc.htm">A COSC röviden</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/regi-seiko-arlistak/">Régi Seiko árlisták</a>
                </li>
              </ul>                  
            </li>			
        </ul>
    </li>
    <li>
      <a href="#" class="menulink">Névjegy</a>
      <ul>
        <li>
          <a href="http://www.egalizer.hu/nevjegy.htm">Bemutatkozás</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/szakdoga.htm">Krónikás</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/letolt.htm">Letöltés</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/linkek.htm">Linkek</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="https://new.egalizer.hu" class="menulink" style="border-right: 1px solid #909090;">Kezdőlap</a>
    </li>
  </ul>
  <!-- Menü vége -->
</div>

  <p id="sweb_elements">
    <object id="footer">
      Sipos Róbert (2017)<br /> Az összeállításhoz használt oldalak:<br /> <a onclick="window.open(this.href);return false;"
        href="http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html">Java HotSpot Virtual Machine Performance Enhancements</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html">Safepoints in HotSpot</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html">HotSpot Glossary of Terms</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.infoq.com/articles/java-threading-optimizations-p1">Java Threading Optimizations</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://arturmkrtchyan.com/java-object-header">Java Object Header</a><br /> <a onclick="window.open(this.href);return false;"
        href="https://zeroturnaround.com/rebellabs/dangerous-code-how-to-be-unsafe-with-java-classes-objects-in-memory/">How to be unsafe</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/">Tricks with Direct Memory Access in Java</a><br />
      <a onclick="window.open(this.href);return false;" href="https://awaiswaheed.wordpress.com/category/java-learning/java-core/java-object-memory-structure/">Java Object Memory
        Structure</a><br /> Memory Management in the Java HotSpot Virtual Machine (Sun, 2006)<br /> <a onclick="window.open(this.href);return false;"
        href="https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references">Understanding Weak References</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://tifyty.wordpress.com/2013/01/02/gyengek-vagyunk-puhak-vagyunk-vagy-nem-is-vagyunk/">Gyengék vagyunk, puhák vagyunk,
        vagy nem is vagyunk</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/">How garbage
        collection works</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.jtechlog.hu/2011/12/30/java-memoriakezeles-szemetgyujto.html">Java memóriakezelés,
        szemétgyűjtő algoritmusok</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html">Java SE 6 HotSpot
        Virtual Machine Garbage Collection Tuning</a><br /> <a onclick="window.open(this.href);return false;" href="https://dzone.com/articles/string-interning-what-why-and">String
        Interning - What, Why and When?</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html">Java
        Garbage collector basics</a><br /> <a onclick="window.open(this.href);return false;" href="http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html">Java Reflection -
        Dynamic Proxies</a><br /> <a onclick="window.open(this.href);return false;" href="https://blogs.oracle.com/dave/false-sharing-induced-by-card-table-marking">False sharing induced
        by card table marking</a><br /> <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html">Concurrent Mark
        Sweep (CMS) Collector</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html">Getting Started with the
        G1 Garbage Collector</a><br /> <a onclick="window.open(this.href);return false;" href="https://plumbr.eu/handbook/garbage-collection-algorithms-implementations/g1">G1 – Garbage
        First</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.fasterj.com/articles/oraclecollectors1.shtml">Oracle JVM Garbage Collectors</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://blog.codecentric.de/en/2014/08/string-deduplication-new-feature-java-8-update-20-2/">String Deduplication – A new
        feature in Java</a><br /> <a onclick="window.open(this.href);return false;" href="http://psy-lob-saw.blogspot.hu/2014/10/the-jvm-write-barrier-card-marking.html">The JVM Write
        Barrier - Card Marking</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.fasterj.com/articles/finalizer1.shtml">The Secret Life Of The Finalizer</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://cr.openjdk.java.net/~brutisso/JEP-271/pre-review.00/compare.html">JEP 271 GC Log Comparison</a><br />
    </object>
    <object id="buttons">
      <button title="tartalombutton" />
    </object>
  </p>
  <h2>Bevezetés</h2>
  <p class="essze_bevezetes">
    A Java bájtkódról írt gyorstalpalóm még 2014-ben készült, így már igencsak itt van az ideje, hogy tovább vájkáljunk a Java belsejében, mégpedig egy másik szép téma, a szemétgyűjtő,
    polgári nevén garbage collector (GC) témakörében. Ehhez persze nem árt egy picit mélyebben tisztában lenni a Java memóriafelépítésével és még a HotSpot virtuális gép belvilágát is
    érinteni fogjuk. A megértéshez azért mindenképpen ajánlatos <a onclick="window.open(this.href);return false;" href="http://www.egalizer.hu/informatika/essze/javabytecode.htm">a
      korábbi cikk</a> elolvasása is. Most ebben az írásban pedig elsősorban az Oracle Java VM implementációjával, a HotSpottal foglalkozom, mégpedig annak 64 bites verziójával. A kódrészletek
    és a tesztprogramok a következő Java verziókat használják:
  </p>
  <ul>
    <li>Java 8u121 x64</li>
    <li>Java 7u80 x64</li>
  </ul>
  <p class="bekezd">
    <b>Forró nyomon</b>
  </p>
  <p class="bekezd">A HotSpot-ra keresztelt virtuális gépet még 1999. április 27-én mutatta be a szép emlékű Sun Microsystems. A Java eredeti, még kizárólag értelmezőt (interpreter)
    használó virtuális gépe mellett az 1.2-es Java-ban a HotSpot bekapcsolható kiegészítésként szerepelt, de az 1.3-tól már a Sun elsődleges JVM-e lett. Miután az Oracle 2010 januárjában
    felvásárolta a Sun-t (a felvásárlás első szerződése egyébként 2009 április 20-án kelt), továbbra is ez maradt a Java elsődleges virtuális gépe. A képet árnyalja, hogy a Sun még 2006-ban
    elhatározta, hogy a Java-t nyílt forráskódúvá teszi. Az elhatározást tett követte, megnyitották a teljes Java, ezzel együtt pedig a HotSpot forráskódját. Ebből jött létre az OpenJDK,
    ami az Oracle mellett tehát szintén HotSpot-ot használ.</p>
  <p class="bekezd">
    A helyzetet tovább bonyolítja, hogy a Java nyíltsága miatt boldog-boldogtalan készített hozzá saját virtuális gépet. A Wikipédia nem kevesebb, mint 77 különféle JVM implementációt sorol
    fel, ezek egy részét természetesen ma már nem fejlesztik. Kavarodást jelentett az is, amikor a BEA Systems által fejlesztett JRockit JVM (amihez a BEA is felvásárlással jutott) bekerült
    a képbe. Az Oracle éhsége közismert, a Sun előtt egy évvel, 2008-ban a BEA Systems-et is felvásárolta, így a HotSpot előtt már megvolt neki a JRockit mint házon belüli JVM. A zavar
    abból látható, hogy az Oracle dokumentációi hol a JRockit-ról, hol a HotSpot-ról szólnak. Előfordul, hogy az ember keres valamit a HotSpot-ról és észre sem veszi hogy egyszercsak már a
    JRockit-ról olvas. Ez szép félreértéseket tud okozni, <a onclick="window.open(this.href);return false;"
      href="http://stackoverflow.com/questions/3773775/default-for-xxmaxdirectmemorysize">például itt</a>, ahol a kérdező azt tudakolja, hogy mi a MaxDirectMemorySize paraméter értéke a SUN
    1.6-os JVM-jében, majd az első válaszoló belinkeli <a onclick="window.open(this.href);return false;" href="http://docs.oracle.com/cd/E15289_01/doc.40/e15062/optionxx.htm#BABGCFFB">a
      JRockit dokumentációját</a>. Az Oracle terve mindenesetre az, hogy hosszú távon a JRockit-ot beleolvassza a HotSpot-ba, a JDK8 már részben egy ilyen, összevont kódbázison alapul. JRockit
    JVM-ből Java 7-es változat már meg sem jelent.
  </p>
  <h3>Hot spot detektálás</h3>
  <p class="bekezd">Régi bölcsesség, hogy a legtöbb program futási idejének nagy részét a kód egy kis részének végrehajtásával tölti. Ezt a bölcsességet fogadták meg a HotSpot tervezői
    is, mert ahelyett, hogy futásidőben metódusról metódusra lefordítana mindent, a HotSpot azonnal elkezdi futtatni a programot egy értelmezővel és közben analizálja azt, hogy kritikus
    részeket (hot spot - forró pont) figyeljen meg, majd pedig ezeket fordítsa le és optimalizálja. Mivel elkerüli a kód ritkán használt részeinek (vagyis nagy részének) fordítását, a
    HotSpot több figyelmet tud szentelni a program teljesítménykritikus részére anélkül, hogy a fordítási idő jelentősen megnőne. Ez a forrópont-figyelés a program futása során is
    folytatódik, tehát gyakorlatilag a teljesítményt dinamikusan, a felhasználó igényei szerint alakítja. Ennek a megközelítésnek lényeges tulajdonsága, hogy a fordítást elodázza addig, míg
    a kód már futott egy darabig (egy darabig - gépi, nem pedig felhasználói idővel mérve). Eközben információt gyűjt a kód használatáról, ami alapján már okosabb optimalizációt tud
    végezni.</p>
  <p class="bekezd">A HotSpot-nak valójában kétféle verziója van: egy kilensoldali és egy szerveroldali VM. A kettő ugyanarra a kódbázisra épül, de különböző fordítót és környezeti
    beállításokat alkalmaz. (Indításkor -client vagy -server kapcsolóval adhatjuk meg, hogy melyiket szeretnénk használni.) A szerver VM esetén a hosszú távú minél nagyob működési sebesség
    elérése a lényeg, ez folyamatosan működő szerverekhez alkalmazható, ahol a gyors indulási idő vagy a futás közbeni memóriafoglalás kevésbé fontos. A kliens VM arra lett optimalizálva,
    hogy minél gyorsabban elinduljon és minél kevesebb memóriát használjon. Ez a lokális kódminőségre koncentrál és kevés globális optimalizációt végez, mivel azok gyakran nagyon
    költségesek a fordítási idő szempontjából.</p>
  <p class="bekezd">
    <i>Kliens fordító</i>: egy egyszerű, ún. háromfázisú fordító. Az első fázisban egy platformfüggetlen frontend egy magasszintű közbenső reprezentációt (high-level intermediate
    representation; HIR) hoz létre a bájtkódból. A második fázisban ebből a platformspecifikus backend alacsonyszintű közbenső reprezentációt (low-level intermediate representation; LIR)
    állít elő. Az utolsó fokozat végzi el a LIR-en a regiszterhozzárendelést, emellett egyszerű optimalizálást is végez, majd pedig gépi kódot generál belőle.
  </p>
  <p class="bekezd">
    <i>Szerver fordító</i>: ez már minden hagyományos optimalizációt megcsinál, amit például egy C++ fordító is. Ilyen például a sosem hívott kód kiküszöbölése, ciklusinvariáns kiemelése,
    közös alkifejezés kiküszöbölése, konstans továbbterjesztés, globális értékszámolás és kódmozgatás. Emellett olyan optimalizálásokat is elvégez, amik speciálisan a Java nyelvhez
    készültek. Ilyen a nullellenőrzés és a tartományellenőrzés kiküszöbölése valamint a kivételek dobásának optimalizálása. A fordító nagymértékben hordozható és egy gépleíró fájlon alapul,
    ami a célhardver minden aspektusát leírja. Bár kategóriájában ez a fordító elég lassúnak számít, még mindig sokkal gyorsabb, mint a hagyományos, optimalizáló fordítók.
  </p>
  <p class="bekezd">
    <b>Többszintű fordítás</b>
  </p>
  <p class="bekezd">
    A többszintű fordítást azért vezették be a Java SE 7-ben, hogy a szerver VM indulási sebessége közelebb legyen a kliens VM indulási sebességéhez. A szerver VM esetén alapesetben az
    értelmező gyűjt információt a metódusokról amiket aztán a fordító felhasznál. A többszintű rendszerben induláskor viszont nem csak az értelmező dolgozik, hanem egyes metódusokat a
    fordító lefordít (de még nem optimalizál szénné) és azután ezt a lefordított változatot elemzi a beépített profiler. Mivel a lefordított változat alapvetően gyorsabb, mint az
    interpreteres, a program már az elemző fázisban (profiling) nagyobb sebességgel fut. A többszintű fordítás 32 és 64 bites módokban is támogatott és a <span class="programkod">-XX:+TieredCompilation</span>
    opcióval lehet bekapcsolni. (Java 7-ben ez alapesetben kikapcsolt, Java 8-ban pedig már alapesetben bekapcsolt.) Megjegyzendő egyébként, hogy ez a Java 7-ben még nem volt teljesen
    kiforrott és a bekapcsolása azt eredményezte, hogy nagyjából 24 óra futás után az alkalmazás nemhogy gyorsult volna, hanem elkezdett lassulni és egy idő után teljesen megállt.
  </p>
  <p class="bekezd">
    A HotSpot az Oracle dokumentációi szerint kliens VM esetén 1000, szerver VM esetén pedig 10000 interpretált meghívás után gyűjt annyi információt, hogy megfelelően optimalizálva
    lefordíthassa az adott metódust. Amennyiben nem szeretnénk semmiféle optimalizáló fordítást, akkor a <span class="programkod">-Xcomp</span> parancssori opcióval kikényszeríthetjük, hogy
    a JVM minden metódust az első meghíváskor azonnal lefordítson. De a fordítás előtti meghívások számát is módosíthatjuk a <span class="programkod">-XX:CompileThreshold</span>
    paraméterrel. Ha pedig az egész lefordítási mulatságot ki szeretnénk kapcsolni, ne habozzunk a <span class="programkod">-Xint</span> parancssori opciót használni. Ekkor csak az
    interpreter fogja futtatni a bájtkódunkat.
  </p>
  <h3>Optimalizálások</h3>
  <p class="bekezd">A szerver JVM tehát sok optimalizációt tud elvégezni a bájtkódú programon, azonban a nyelv néhány speciális tulajdonsága miatt több esetben különleges megoldásokra
    van szükség a kívánt teljesítmény eléréséhez. Java esetén a legtöbb metódushívás virtuális, ami azt jelenti, hogy a statikus fordítóprogram-optimalizációkat (különösen az olyan globális
    érvényűeket, mint a metódusok kifejtése) sokkal nehezebb elvégezni. Ráadásul az osztályok dinamikus betöltési lehetőségének következtében a Java alapú programok futásidőben is
    megváltozhatnak. A fordítónak nemcsak azt kell észrevennie, hogy a dinamikus betöltés miatt mikor válnak az ilyen optimalizációk érvénytelenné, de vissza kell tudnia vonni illetve újra
    végre kell tudnia hajtani ezeket az optimalizációkat a program végrehajása közben még akkor is, ha ez a vermen lévő aktív metódushívásokat is érinti. Ezt anélkül kell elvégeznie, hogy
    bármilyen módon hatással lenne a programok végrehajtására. A HotSpot VM ún. adaptív (vagyis alkalmazkodó) optimalizációt alkalmaz, amely mindezeket a problémákat képes kezelni.</p>
  <p class="bekezd">
    <b>Metódus kifejtés</b>
  </p>
  <p class="bekezd">
    Amikor a HotSpot elég információt gyűjtött a program forró pontjainak végrehajtásáról, nemcsak natív kóddá fordítja azokat, hanem metódus kifejtést (inline) is végez rajtuk. A kifejtés
    drámaian le tudja csökkenteni a metódushívások gyakoriságát, viszont talán még fontosabb, hogy így sokkal nagyobb kódblokkok jönnek létre, amiket hatékonyabban lehet tovább
    optimalizálni. A kifejtés tehát más optimalizációkkal együtt igazán hatásos, mert egyrészt lehetővé másrészt hatékonyabbá is teszi azokat. (Viszont ki is kapcsolhatjuk az egész
    inline-olást a fenébe a <span class="programkod">-XX:-Inline</span> parancssori kapcsolóval.)
  </p>
  <div class="keretes">
    <h3>Metódus kifejtés</h3>
    <p>A metóduskifejtés során a fordító kiküszöböli a metódushívásokat úgy, hogy az azok által végrehajtott kóddal lecseréli a hívó utasítást. Tekintsük a következő példát, ami a
      könnyebb érthetőség kedvéért Java nyelven van, a fordító természetesen mindezt gépi kódban végzi el, de a lényeg ugyanaz:</p>
    <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">int</span> addAndInc(<span class="java_keyword">int</span> a, <span
        class="java_keyword">int</span> b) {
        <span class="java_keyword">return</span> a + b + 1;
    }</pre>
    <p>Ezt pedig hívjuk így:</p>
    <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">void</span> doEverything() {
        <span class="java_keyword">int</span> var1 = addAndInc(6, 10);
        <span class="java_keyword">int</span> var2 = addAndInc(9, 20);
        ... <span class="java_comment">// a metódus további része itt most lényegtelen</span>
    }</pre>
    <p>A fordító azt látja, hogy hatékonyabb lenne kifejteni a két metódushívást, nosza meg is teszi:</p>
    <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">void</span> doEverything() {
        <span class="java_keyword">int</span> var1 = 6 + 10 + 1;
        <span class="java_keyword">int</span> var2 = 9 + 20 + 1;
        ... <span class="java_comment">// a metódus további része itt most lényegtelen</span>
    }</pre>
    <p>A példából is sejthető, hogy ez az egyszerű módszer a virtuális metódusok használata miatt jelentősen bonyolódhat, hiszen az addAndInc metódus megváltozhat egy alosztály
      betöltésével, így a korábbi inline-olt kód már nem lesz jó. A fordítót lehet segíteni final metódusok és osztályok deklarálásával, hiszen azzal jelezzük, hogy azok már nem fognak
      változni később, de persze ez nem jelenti azt, hogy a HotSpot valóban kifejtést is végez majd azon a kódon, hiszen az optimalizálást (és egyáltalán a lefordítást) számos egyéb
      paraméter befolyásolja. Teljesen biztos módszer tehát nincs a programozó kezében a kifejtés kényszerítésére, azt mindig futásidőben fogja eldönteni a JVM.</p>
  </div>
  <p class="bekezd">
    <b>Dinamikus visszaoptimalizálás</b>
  </p>
  <p class="bekezd">Bár a metóduskifejtés fontos optimalizálás, a dinamikus betöltés jelentősen bonyolítja a helyzetet, mert megváltoztatja a programon belüli kapcsolatokat. Egy új
    osztály tartalmazhat új metódusokat is, amiket ki kell fejteni megfelelő helyeken. Ezért a HotSpot-nak képesnek kell lennie dinamikus visszaoptimalizálásra (dynamic deoptimization),
    vagyis a korábban már optimalizált kód visszaállítására, amit szükség szerint újra optimalizálni tud. Ja, és mindezt futás közben! Enélkül a kifejtést nem lehetne biztonságosan
    elvégezni Java alapú programokban. A HotSpot kliens és szerver változata is támogatja a dinamikus visszaoptimalizálást. Ez azért is fontos, mert ez tesz lehetővé további
    optimalizációkat:</p>
  <ul>
    <li>gyors instanceof/típusellenőrzés: a HotSpot egy speciális megoldást használ a Java által gyakran igényelt típusfigyelések gyorsításának érdekében</li>
    <li>tartományellenőrzés megszüntetés: a Java nyelvi specifikációja megköveteli, hogy minden egyes tömbelérésnél ellenőrizni kell a tömbhatárokat. Egy index-ellenőrzést meg lehet
      szüntetni, amikor a fordító biztosítani tudja, hogy az eléréshez használt index a határon belül van.</li>
    <li>cikluskifejtés (loop unrolling): a szerver VM cikluskifejtést is alkalmaz, ami eléggé általános módszer és adott esetben lehetővé teszi a gyorsabb ciklusvégrehajtást. Bár ez
      növeli a ciklusmag méretét, de ezzel párhuzamosan csökkenti az iterációk számát és növeli más optimalizálások hatékonyságát is.</li>
    <li>visszacsatolás-vezérelt optimalizációk (feedback-directed optimizations): szerver VM esetén az értelmező futásidőben analizálja a programot, mielőtt a fordító lefordítaná a
      bájtkódot optimalizált gépi kódra. Ez az analizálás több információt ad a fordítónak a használt adattípusokról, a kód gyakran használt végrehajtási utairól és egyéb tulajdonságairól.
      A fordító ezt az információt arra használja, hogy agresszívebben tudjon optimalizálni. Amennyiben futásidőben a kód egyik tulajdonséga megváltozik, akkor a fordító visszaoptimalizál
      és később újrafordít és újraoptimalizál.</li>
    <li>a szerver VM fordítója szinkronizációs blokkokat is ki tud küszöbölni ha úgy látja, hogy egy objektum thread local. A StringBuffer és Vector osztályok metódusai például
      szinkronizáltak, mert különböző szálakból is elérhetőnek kell lenniük, viszont a legtöbb esetben thread local módon vannak használva. Ilyen esetekben a fordító optimalizálhatja úgy a
      kódot, hogy eltávolítja a szinkronizációs blokkokat.</li>
  </ul>
  <p class="bekezd">
    <b>Objektumcsomagolás</b>
  </p>
  <p class="bekezd">A 64 bites korszak eljövetelével bekerült a HotSpot-ba egy objektumcsomagolás nevű lehetőség is, hogy az adattípusok közötti elpazarolt területet minél kisebbre
    csökkentsék. Ez főként a 64 bites rendszerekben hatásos, de némi pozitív hozadéka még 32 bites környezetben is van. A megértése példán keresztül a legegyszerűbb. Tekintsük a következő
    osztályt:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Button {
        <span class="java_keyword">char</span> shape;
        String label;
        <span class="java_keyword">int</span> xposition;
        <span class="java_keyword">int</span> yposition;
        <span class="java_keyword">char</span> color;
        <span class="java_keyword">int</span> joe;
        Object mike;
        <span class="java_keyword">char</span> armed;
    }</pre>
  <p class="bekezd">A modern processzorok esetén a legnagyobb teljesítmény érdekében nem árt, ha az adattípusaink megfelelő (például négybájtos) határra illeszkednek. Az illeszkedés
    megvalósítása viszont területet pazarol a color és a joe (három bájt a 4-bájtos int határ miatt) és a joe és mike (négy bájt egy 64 bites VM-ben, hogy mutató határra essen) között. Az
    objektumcsomagolás a mezőket a következőképp rendezi át:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Button {
        ...
        Object mike;
        <span class="java_keyword">int</span> joe;
        <span class="java_keyword">char</span> color;
        <span class="java_keyword">char</span> armed;
        ...
    }</pre>
  <p class="bekezd">Így már nincs elpazarolt memóriaterület.</p>
  <p class="bekezd">
    <b>Mutatók és tömörített oop-ok</b>
  </p>
  <p class="bekezd">Érdekességképp megemlíthető, hogy a HotSpot előtt még handle-ök voltak objektum referenciaként használva. Bár ez szemétgyűjtés során könnyűvé tette az objektumok
    áthelyezését, jelentős teljesítménybeli hátrányt jelentett, mert a példányváltozók eléréséhez kétszintű indirekcióra volt szükség. A HotSpot már nem használ handle-öket, az objektum
    referenciák közvetlen mutatókként vannak implementálva. Ez C-sebességű példányváltozó-elérést tesz lehetővé. Amikor egy objektumot a memóriafelszabadítás során át kell helyezni (a
    memória töredezettségének elkerülése érdekében), a szemétgyűjtő felelős azért, hogy megtalálja és frissítse az összes arra hivatkozó referenciát.</p>
  <div class="keretes">
    <p>
      <b>Mi az a handle?</b>
    </p>
    <p>A handle röviden egy környezetfüggő egyedi azonosító. A környezetfüggőség azt jelenti, hogy az egyik kontextusban használt handle nem feltétlenül használható máshoz. A handle
      valamilyen erőforrásra hivatkozik, mint például memória, megnyitott fájl, stb. Windows esetén a handle egy olyan absztrakció, ami elrejti a valódi memóriacímeket az API használója
      elől és így lehetővé teszi az operációs rendszernek, hogy a felhasználói program számára átlátszó módon átrendezze a memóriát. A handle feloldása mutatóvá megadja a kívánt
      memóriacímet, a handle felszabadítása pedig érvénytelenné teszi a mutatót. Tekinthető például úgy, mint index egy mutatótáblában. A rendszer API hívásoknál ezt az indexet lehet
      használni, a rendszer pedig így tetszőlegesen módosítani tudja a táblában lévő valódi mutatókat.</p>
  </div>
  <p class="bekezd">A HotSpot-ban egy objektumra hivatkozó menedzselt mutatót &quot;oop&quot;-nak, vagyis ordinary object pointer-nek hívnak. Egy oop jellemzően ugyanolyan méretű, mint
    egy natív gépi mutató, vagyis egy 64 bites rendszeren 64 bit. 32 bites rendszeren a heap legnagyobb mérete egy kicsivel kevesebb, mint 4 GB lehet, ez pedig sok alkalmazásnak kevés,
    különösen szerveres környezetben. 64 bites rendszeren viszont egy adott program által használt heap-méret akár másfélszer akkora is lehet, mint ugyanaz a program 32 bites rendszeren,
    ennek pedig nagyrészt a menedzselt mutató nagyobb mérete az oka. Bár a memória manapság már nem túl költséges, a sávszélesség és a cache (adott esetben pedig akár a memória is) még
    mindig eléggé behatárolja a lehetőségeket, a heap jelentős növekedése tehát nem kívánatos. Ezért találták ki a tömörített oop-okat.</p>
  <p class="bekezd">A menedzselt mutatók a Java heap-en 8 bájtos határra illesztett objektumokra mutatnak. A tömörített oop-ok gyakorlatilag menedzselt mutatók (bár a JVM belsejében nem
    minden esetben) és ez pedig a 64 bites heap báziscímtől számított 32 bites objektum offszet. Fontos különbség, hogy ezek nem bájt, hanem objektum offszetek, amik négy milliárd
    objektumot (nem pedig bájtot) tudnak megcímezni. Így tehát akár 32 GB-nyi heap méretig bezárólag lehet objektumokat kezelni. A használathoz ezeket skálázni kell 8-cal és hozzá kell adni
    a Java heap báziscímhez, hogy megtaláljuk azt az objektumot, amelyre hivatkozunk. A tömörített oop-okkal használt objektumméretek kompatibilisek a 32 bites módban használt oop-okkal.</p>
  <p class="bekezd">
    Dekódolásnak hívják, amikor egy 32 bites tömörített oop átkonvertálódik 64 bites natív heap-címmé. Ennek inverz művelete a kódolás (milyen meglepő). A tömörített oop-okat a Java SE 6u23
    óta alapértelmezettként támogatja a JVM. Java 7-ben 64 bites JVM esetén szintén alapértelmezettnek számítanak, ha a <span class="programkod">-Xmx</span> nincs megadva vagy az értéke
    kisebb, mint 32 GB.
  </p>
  <p class="bekezd">
    <b>Nulla alapú oop-ok</b>
  </p>
  <p class="bekezd">Amikor 64 bites JVM folyamatok tömörített oop-okat használnak, a JVM azt kéri az operációs rendszertől, hogy a heap-nek 0 virtuális címen kezdődjön a címtartománya.
    Amennyiben ezt az operációs rendszer támogatja, akkor a JVM nulla alapú tömörített oop-okat használ. Ilyenkor egy 64 bites mutató a báziscím hozzáadása nélkül dekódolható egy 32 bites
    objektum-offszetből. 4 GB-nál kisebb heap méret esetén a JVM objektumoffszet helyett bájtoffszetet is tud használni és ebben az esetben még a 8-cal való skálázás is kihagyható. 64 bites
    cím 32 bites offszetté kódolása ilyenkor hasonlóan hatékony. 26 GB méretig a Solaris, Linux és Windows rendszerek is képesek általában 0 báziscímre heap-et foglalni.</p>
  <h3>Szálak és zárak</h3>
  <p class="bekezd">
    <b>Szál</b>
  </p>
  <p class="bekezd">
    Valószínűleg mindenki tudja, mi a különbség programozásban a processz és a szál között, így aztán senkit nem fog sokkoló meglepetésként érni a következő pár mondat: egy processz egy
    alkalmazás éppen végrehajtott példánya, egy szál pedig egy éppen végrehajtott processzen belüli végrehajtási út. Egy processz tartalmazhat több szálat. Fontos különbség, hogy egy adott
    processzen belüli szálak ugyanazt a címtartományt használják, míg különböző processzek nem. Java esetén maga a JVM a processz, ami végrehajtja a Java programunkat, az pedig több szálat
    létrehozhat és futtathat párhuzamosan. A HotSpot-ban közvetlen leképezés van egy Java szál és egy natív operációs rendszerbeli szál között. Miután egy Java szálhoz szükséges minden
    összetevő megvan (thread-local memória, foglalási pufferek, szinkronizációs objektumok, vermek és programszámláló), létrejön egy natív szál. Amikor a Java szál megszűnik, a natív szál
    pályafutása is végetér. Ennek következtében az operációs rendszer felelős a szálak ütemezéséért és azért, hogy erőforrást és CPU-időt kapjanak. A natív szál, miután létrejött, meghívja
    a Java szál <span class="programkod">run()</span> metódusát. Amikor a <span class="programkod">run()</span> befejeződik, a kezeletlen kivételek lekezelődnek, a natív szál pedig
    megvizsgálja, hogy a JVM-et is le kell-e állítani (például ha ez volt az utolsó nem-démon szál). Amikor a szál leáll, az összes natív és Java szálhoz rendelt erőforrás felszabadul.
  </p>
  <p class="bekezd">
    <b>JVM rendszer szálak</b>
  </p>
  <p class="bekezd">
    A jConsole vagy egyéb nyomkövető használatával meg lehet sasolni, hogy még egy egyszerű program esetén is számos szál fut a háttérben. Ezek a fő programszálak mellett futnak, ami a <span
      class="programkod">public static void main (String[])</span> hatására jött létre illetve amiket az hozott létre. A fő rendszer-háttérszálak a HotSpot-ban a következők (egyes rendszer
    szálakat a jConsole nem mutat, de egy thread dump igen):
  </p>
  <p class="bekezd">
    <b>VM Thread</b>: ez a szál vár az ún. safe-point-hoz szükséges események megjelenésére. Ehhez azért kell külön szál, mert ezen kívül mindegyiknek szükséges safe-point-ban lenni,
    ugyanis a heap-en csak így történhetnek módosítások. Ez a szál olyan műveleteket végez, mint a mindent megállító szemétgyűjtés, thread és stack dump-ok, szál felfüggesztés és a
    rögzített zárolás visszavonás (biased locking revocation).
  </p>
  <div class="keretes">
    <p>
      <b>Rögzített zárolás visszavonása</b>
    </p>
    <p>Amennyiben egy objektumot csak egy szál zárol, a VM képes olyan optimalizálásra, hogy az objektumot ahhoz a szálhoz rögzíti (bias) és onnantól kezdve azon az objektumon végzett
      további atomi műveleteknek nincs szinkronizációs költségük. Ez jelentős sebességnövekedést eredményezhet. Ezt a -XX:+UseBiasedLocking kapcsolóval lehet beállítani a JVM-ben, de már a
      Java 6 óta alapértelmezett. A tulajdonképpeni szinkronizálás csak akkor történik meg, ha másik szál is megpróbálja zárolni azt az objektumot. Ilyenkor vissza kell vonni (revoke) a
      zárolást. Ez a művelet a rögzítet zárolás visszavonás (biased locking revocation).</p>
  </div>
  <p class="bekezd">
    <b>VM Periodic Task Thread</b>: ez felelős az időzített eseményekért (pl megszakítások), amelyeket periodikus műveletek ütemezésére lehet használni.
  </p>
  <p class="bekezd">
    <b>GC szálak</b>: ezek a szálak valósítják meg a különböző típusú szemétgyűjtő műveleteket.
  </p>
  <p class="bekezd">
    <b>Fordító szálak</b>: ezek a szálak fordítják a bájtkódot natív kódra futásidőben.
  </p>
  <p class="bekezd">
    <b>Signal dispatcher szál</b>: fogadja az operációs rendszertől JVM processzeknek küldött natív jelzéseket (signal) és a JVM-en belül kezeli azokat úgy, hogy meghívja a megfelelő JVM
    metódusokat.
  </p>
  <p class="bekezd">
    <b>Reference Handler</b>: azokat a műveleteket végzi, amit minden referencia objektummal meg kell csinálni, ilyen például az, hogy berakja őket a finalization sorba.
  </p>
  <p class="bekezd">
    <b>Finalizer</b>: kivesz objektumokat a finalization sorból és meghívja azok <span class="programkod">finalize()</span> metódusát. Ezután a szemétgyűjtő már felszabadíthatja azok
    helyét. Ha nem képes lépést tartani a nagyobb prioritású folyamatokkal, amelyek ebbe a sorba rakják az objektumokat, akkor egy idő után <span class="programkod">java.lang.OutOfMemoryError</span>
    lesz a jutalmunk.
  </p>
  <p class="bekezd">
    <b>DestroyJavaVM</b>: leállítja a Java VM-et, mikor a program végetér. Az idő legnagyobb részében szépen várakozik, amíg a VM apokalipszisa el nem érkezik.
  </p>
  <div class="keretes">
    <p>
      <b>Mi az a safe point?</b>
    </p>
    <p>A safe point egy olyan pont a program végrehajtása során, amikor minden GC gyökérelem ismert (GC gyökérelemekről később bővebben lesz szó) és a teljes heap tartalom konzisztens.
      Safe point esetén az összes Java kódot futtató szál működése felfüggeszthető. Vannak JVM-ek, ahol adott szálra is lehet safe pointot érvényesíteni anélkül, hogy a teljes világ
      megállna, de a HotSpot nem ilyen. Mielőtt egy szemétgyűjtő elindulhatna, minden szálnak blokkolódnia kell egy safe point-ban. (Speciális esetben a JNI, vagyis Java natív kódot futtató
      szálak tovább futhatnak, ha közben nem szólnak a JVM-hez. De ha Java objektumokhoz próbálnak hozzáférni, Java metódust akarnak meghívni vagy vissza akarnak térni natív módból, akkor
      ezek is felfüggesztésre kerülnek a safe point végéig.) A program szempontjából a safe point a kód egy speciális része, ahol a végrehajtó szálat a szemétgyűjtő kedvéért blokkolni
      lehet. Ennek minősül például a legtöbb hívási pont. Erre az egész mókára azért van szükség, hogy a safe point kezdeményezőjének teljes hozzáférére legyen a JVM adatstruktúráihoz és
      olyan őrültségeket tudjon csinálni, mint objektumok mozgatása a heap-en vagy éppen végrehajtás alatt álló metódus kódjának lecserélése.</p>
    <p>A HotSpot-ban a safe point protokollja együttműködésen alapul: minden szál maga ellenőrzi a safe point státuszt és ha szükséges, akkor parkolópályára áll. A JVM-ek valamilyen
      hatékony mechanizmust használnak, hogy rendszeresen vizsgálják, szükséges-e megállás. A fordító a lefordított kódba is safe point ellenőrzéseket rak bizonyos pontokra (általában
      hívások utáni visszatérésekhez vagy ciklusok törzsének végére, mielőtt a ciklus elejére visszaugrás megtörténne). A HotSpot egy egyszerű, globális &quot;go to safepoint&quot; jelzőt
      használ. Ez gyakorlatilag egy olyan lap, ami védett akkor amikor egy safe pointra van szükség és nem védett egyébként. A safepoint figyelő mechanizmus pedig ennek a lapnak egy adott
      címéről próbál adatot olvasni. Ha az olvasás csapdázódik, akkor a szál tudja, hogy safepoint-ba kell lépnie. Ez a mechanizmus azért is jó, mert a modern szuperskalár processzorokban
      nem okoz csővezeték-kiürítést a safepoint vizsgálatnál.</p>
    <p>Kétségtelen, hogy a szemétgyűjtés a legfontosabb funkció, aminek a kedvéért safepointokat kell a kódban kijelölni, azonban nem az egyetlen. Ezekben az esetekben használ a JVM
      safepoint-okat:</p>
    <ul>
      <li>szemétgyűjtő leállások</li>
      <li>kód visszaoptimalizálás</li>
      <li>kód-cache kiürítése</li>
      <li>osztály újradefiniálás (pl. hot swap vagy instrumentation)</li>
      <li>rögzített zárolás visszavonása</li>
      <li>számos debug művelet (pl. deadlock ellenőrzés vagy stacktrace dump)</li>
    </ul>
    <p>A safepointokra többnyire nem kell nagy figyelmet fordítani, mert a GC-t kivéve ezek általában nagyon gyorsan lefutnak. De ha valami probléma adódna, van néhány diagnosztikai
      lehetőség a JVM parancsok között:</p>
    <ul>
      <li><span class="programkod">-XX:+PrintGCApplicationStoppedTime</span>: ez kiírja a tulajdonképpeni állási időket az összes safepoint típushoz. Sajnos ennek a kimenetében
        nincsenek időbélyegek, de attól még használható, ha bizonyos problémáknál felmerül a safepointok felelőssége.</li>
      <li><span class="programkod">-XX:+PrintSafepointStatistics</span> <span class="programkod">–XX:PrintSafepointStatisticsCount=1</span>: ez a két beállítás kikényszeríti a JVM-től,
        hogy minden safepoint okát és időzítéseit kiírja a standard kimenetre.</li>
    </ul>
  </div>
  <p class="bekezd">
    <b>Zárolás</b>
  </p>
  <p class="bekezd">A Java által alkalmazott zárolási módszer (akárcsak a legtöbb többszálú osztálykönyvtár) nagyon pesszimista. Ha csak a legkisebb esélye fennáll annak, hogy két vagy
    több szál elér adatot konkurens módon, akkor egy nagyon szigorú zárolási megoldást kell használni. Mindezt annak ellenére, hogy kutatások kimutatták, hogy a zárolásokra nagyon ritkán -
    ha egyáltalán - van valóban szükség. Tehát egy szálnak, ami zárolást kér, ritkán kell várni arra, hogy megkapja. A zárolás kérésének költsége viszont nem nulla, ezért legjobb lenne
    elkerülni. A JVM fejlesztői ezért többféle optimalizálást vezettek be a zárolásra:</p>
  <ul>
    <li>adaptív spinning</li>
    <li>rögzített zárolás (biased locking)</li>
    <li>kétféle zárolás kiküszöbölés:
      <ul>
        <li>lock coarsening</li>
        <li>lock elision</li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">A részletezés előtt nézzünk egy példát!</p>
  <p class="bekezd">
    <b>Vezérlés analízis</b>
  </p>
  <p class="bekezd">A vezérlés analízis (escape analysis) az, amikor a futó program összes referenciájának hatókörét megvizsgáljuk. Ez a HotSpot profilerének szokványos munka. Ha a
    HotSpot ezzel meg tudja állapítani, hogy egy objektumra hivatkozó minden referencia korlátozott, lokális láthatóságú és egyik referencia sem tud kilépni ebből egy bővebb láthatósági
    körbe, akkor a JIT számos futásidejű optimalizálást végre tud rajta hajtani. Egyik ilyen a zárolás kiküszöbölés. Amikor referenciák zárolása lokális hatókörre korlátozódik, akkor csak
    az a szál fér hozzá, ami létrehozta. Ilyenkor tehát a szinkronizált blokkban lévő értékekért sosem fog több szál versenyezni, tehát sosem lesz igazán szükség a zárolásra és ezért
    nyugodt szívvel elhagyható. Tekintsük a következő metódust:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> String concatBuffer(String s1, String s2, String s3) {
        StringBuffer sb = <span class="java_keyword">new</span> StringBuffer();
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        <span class="java_keyword">return</span> sb.toString();
    }</pre>
  <p class="bekezd">Vegyük észre, hogy az sb változtó csak a metóduson belül él, ráadásul a rá való hivatkozások sosem lépnek ki abból a hatókörből, amelyben deklarálva lettek. Nincs rá
    mód tehát, hogy másik szál hozzáférjen az sb lokális másolatához. Ezért tudható, hogy az sb-t védő zárolások elhagyhatók.</p>
  <p class="bekezd">
    <b>Rögzített zárolás</b>
  </p>
  <p class="bekezd">A rögzített zárolást (biased locking) az a megfigyelés inspirálta, hogy a legtöbb zárolást egynél több szál sosem éri el az élettartama alatt. De még azon ritka
    alkalmakkor is, amikor több szál között oszlik meg az adat, az elérések között ritkán van versengés. A rögzített zárolás előnyének megértéséhez az előbbi megfigyelés fényében először
    nézzük meg, hogyan is kapnak zárolást az adatok.</p>
  <p class="bekezd">A zárolás megszerzése kétlépéses folyamat. Először igényelni kell (lease), aztán amikor az megvan, akkor lehet kérni a zárolást. Az igényléshez viszont egy elég
    költséges atomi műveletre van szükség. A zárolás feloldása általában feloldja az igénylést is. És mi van akkor, ha a szál egy szinkronizált kódblokkon iterál végig és azt szeretnénk
    optimalizálni? Megtehetnénk, hogy az egész ciklust szinkronizálttá tesszük, így a szál csak egyszer igényli meg a zárolást, nem pedig minden iterációban. Ez viszont azért nem jó
    megoldás, mert más szálakat zárhat ki az adat igazságos eléréséből. Sokkal okosabb megoldás, ha rögzítjük (bias) a zárolást a ciklust tartalmazó szálhoz. Ilyenkor a szálnak nem kell
    eldobnia az igénylést a zárolás végén, így a következő zárolások megszerzése sokkal kevésbé lesz költséges. A szál csak akkor engedi el az igénylést, ha másik szál is szeretne zárolást
    kapni. A Java 6 óta a HotSpot már alapból rögzített zárolást használ.</p>
  <p class="bekezd">
    <b>Lock coarsening</b>
  </p>
  <p class="bekezd">Egy másik optimalizálási módszer a lock coarsening vagy összefűzés. Ez akkor alkalmazható, amikor szomszédos szinkronizált blokkok összefűzhetőek egyetlen
    szinkronizált blokká. Ennek egy másik változata, amikor több szinkronizált metódust fűzünk egybe, ami akkor alkalmazható, ha ugyanazt a zárolt objektumot használja az összes. Tekintsük
    a következő példát:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">static</span> String concatToBuffer(StringBuffer sb, String s1, String s2, String s3) {
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        <span class="java_keyword">return</span> sb.toString();
    }</pre>
  <p class="bekezd">Ebben az esetben a StringBuffernek nem helyi a láthatósági köre és akár több szál is elérheti. A vezérlés analízis meg fogja mutatni, hogy ennek a zárolását nem
    lehet biztonságosan elhagyni. Ha a zárolást történetesen csak egy szál érné el, akkor persze a rögzített zárolás is alkalmazható. Érdekes módon a döntés, hogy a JVM összefűzze-e ezeket,
    attól függetlenül is eldönthető, hogy hány szál verseng az erőforrásért. A példában a zárolás négyszer egymás után lesz megkérve: háromszor az append metódusnál és egyszer a
    toString-nél. A fordító első tennivalója a metódusok kifejtése, de ezután már mind a négy műveletet módosítani lehet úgy, hogy a zárolást már csak egyszer kelljen megszerezni úgy, hogy
    a teljes metódus törzset körbefogja. Ennek persze az lesz a hatása, hogy hosszabb kritikus szakaszt kapunk, tehát lehetséges, hogy más szálak várakozni fognak és csökken az
    áteresztőképesség. Ezért egy ciklusban lévő zárolást sosem fűz ösze a JVM úgy, hogy az a teljes ciklust magába foglalja.</p>
  <p class="bekezd">
    <b>Szál felfüggesztés és spinning</b>
  </p>
  <p class="bekezd">Amikor egy szál arra vár, hogy egy zárolást egy másik szál elengedjen, általában felfüggeszti az operációs rendszer. A felfüggesztés azzal jár, hogy az operációs
    rendszer esetleg még azelőtt elveszi tőle a processzort, mielőtt a kvantumja lejárna (preemptív operációs rendszerekben általában kvantumnak hívják azt az időszeletet, ameddig egy szál
    futhat, mielőtt az ütemező egy másik szálnak adná a CPU-t). Amikor a kérdéses zárolást birtokló szál kilép a kritikus szekciójából, a felfüggesztett szálat fel kell ébreszteni, majd
    újra kell ütemezni és kontextuskapcsolással CPU-t adni neki. Ez mind extra feladatot ad a JVM-nek, az operációs rendszernek és a hardvernek is.</p>
  <p class="bekezd">Ebben az esetben a következő megfigyelés használható: a zárolásokat általában nagyon rövid ideig tartják meg az igénylők, amiből az következik, hogy ha még várnánk
    egy kicsit, akkor lehet, hogy megkapnánk a zárolást anélkül, hogy fel kellene függeszteni a várakozó szálat. Ehhez csak annyit kell tenni, hogy a szálban cikluson belül tevékenyen
    várakozunk. Ez a spinningnek nevezett technika. A spinning olyan esetekben működik jól, amikor a zárolások tartama nagyon rövid. Ha a zárolásra hosszabb ideig van szükség, akkor a
    spinning fölöslegesen pazarolja a CPU-t és semmi értelmeset nem csinál. A spinninget a JDK 1.4.2-ben hozták be és két fázisra osztották: először alapértelmezetten 10 iterációt
    várakozik, majd csak utána függesztődik fel a szál.</p>
  <p class="bekezd">
    <b>Adaptív spinning</b>
  </p>
  <p class="bekezd">A JDK 1.6 hozta be az adaptív spinning-et, ahol a spinning iterációk száma immár nem rögzített, hanem a korábbi spin próbálkozások alapján meghatározott szabály adja
    meg a zárolás tulajdonosának állapota mellett. Ha a közelmúltban a spinning sikerült ugyanazon a zárolt objektumon és a zárolást tulajdonló szál épp fut, akkor a spinning valószínűleg
    újra sikeres lesz. Ezért most már relatív hosszabb időtartammal is lehet próbálkozni, mondjuk 100 iterációval. Ha viszont a spinning nem valószínű, hogy sikeres lesz, akkor teljesen le
    lehet állítani, így nem pazarol CPU időt.</p>
  <h2>Memóriafoglalás és egyéb huncutságok</h2>
  <p class="bekezd">
    Mielőtt a szemétgyűjtőt a maga valójában meg mernénk közelíteni (nem harap), azért nem árt, ha az objektumok memóriabeli felépítéséről és egyéb izgalmas dolgokról is van némi
    tudomásunk. A Java nyelvben a programozók tehát objektumokat birizgálnak. Egy objektumhoz szükséges memória mindig a heap-en lesz lefoglalva és ez a művelet mindig impliciten, a new
    operátorral történik. Tegyük fel, hogy van egy <span class="programkod">Vadállat</span> nevű osztályunk. Ebből az osztályból csinálunk egy példányt:
  </p>
  <pre class="programkod"> Vadállat vad = <span class="java_keyword">new</span> Vadállat();</pre>
  <p class="bekezd">
    A JVM ilyenkor kiszámítja, hogy mennyi memória szükséges (a Vadállat osztály definíciójától függően), aztán lefoglalja azt a heap-en és egy erre a memóriaterületre hivatkozó referenciát
    tárol a <span class="programkod">vad</span> változóban. Amikor tehát egy <span class="programkod">Vadállat</span> objektumot akarunk létrehozni, nem kell megadnunk, hogy mennyi memóriát
    szeretnénk (milyen meglepő). A kifejezés <span class="programkod">new Vadállat()</span> része megmondja a Java-nak, hogy mit akarunk csinálni. A JVM pedig felhasználja a <span
      class="programkod">Vadállat</span> osztály definícióját a szükséges memóriaméretet kiszámolásához. Hogyan néz ki valójában egy objektum a heap-en?
  </p>
  <p class="bekezd">A memóriában egy objektumot egy oopDesc nevű adatszerkezet ír le. Minden objektumnak van egy fejléce (header) és egy adatrésze. A fejléc mindenféle, a JVM által
    használt könyvelési információt tárol. Itt van például egy mutató az objektum osztályára, információ az objektum szemétgyűjtési státuszáról, zárolási információ, tömb esetén annak
    hossza, stb. Az adatterületen tárolódik az objektum összes példányváltozójának értéke. Egy adott JVM implementáció esetén a fejléc felépítése rögzített, az adat terület felépítése
    viszont az objektum típusától függ. A HotSpot két gépi szót használ fel az objektum fejlécének (32 bites architektúra esetén egy szó 4 bájt), kivéve ha az objektum tömb, mert ekkor
    hármat. Ekkor egy extra szó szolgál a tömb hosszának tárolására. Így néz ki egy objektum és egy osztály a memóriában HotSpot JVM esetén:</p>
  <p class="kozep">
    <img alt="header" src="/informatika/essze/garbage/header.png" />
  </p>
  <h3>Objektumok felépítése</h3>
  <p class="bekezd">Egy objektum memóriabeli fejléce részletesebben az alábbi módon néz ki:</p>
  <p class="bekezd">
    <b>32 bites JVM</b>
  </p>
  <table cellpadding="0" cellspacing="0" width="75%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal" colspan="2" style="border-right: 1px solid white;"><b>Objektum fejléce (64 bit)</b></th>
        <th class="normal"><b>Állapot</b></th>
      </tr>
      <tr>
        <th class="normal" style="border-right: 1px solid white;"><b>Mark szó (32 bit)</b></th>
        <th class="normal" style="border-right: 1px solid white;"><b>Klass szó (32 bit)</b></th>
        <th class="normal"></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">identity_hashcode:25 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">normál</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">biased</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_lock_record:30 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">lightweight locked</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_heavyweight_monitor:30 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">heavyweight locked</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">| lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">szemétgyűjtésre kijelölve</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <b>64 bites JVM</b>
  </p>
  <table cellpadding="0" cellspacing="0" width="95%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal" colspan="2" style="border-right: 1px solid white;"><b>Objektum fejléce (128 bit)</b></th>
        <th class="normal"><b>Állapot</b></th>
      </tr>
      <tr>
        <th class="normal" style="border-right: 1px solid white;"><b>Mark szó (64 bit)</b></th>
        <th class="normal" style="border-right: 1px solid white;"><b>Klass szó (64 bit)</b></th>
        <th class="normal"></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">használatlan:25 | identity_hashcode:31 | használatlan:1 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">normál</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">biased</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_lock_record:62 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">lightweight locked</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_heavyweight_monitor:62 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">heavyweight locked</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">| lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">szemétgyűjtésre kijelölve</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <b>64 bites JVM tömörített oop-okkal</b>
  </p>
  <table cellpadding="0" cellspacing="0" width="95%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal" colspan="2" style="border-right: 1px solid white;"><b>Objektum fejléce (96 bit)</b></th>
        <th class="normal"><b>Állapot</b></th>
      </tr>
      <tr>
        <th class="normal" style="border-right: 1px solid white;"><b>Mark szó (64 bit)</b></th>
        <th class="normal" style="border-right: 1px solid white;"><b>Klass szó (32 bit)</b></th>
        <th class="normal"></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">használatlan:25 | identity_hashcode:31 | cms_free:1 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">normál</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">thread:54 | epoch:2 | cms_free:1 | age:4 | biased_lock:1 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">biased</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_lock_record:62 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">lightweight locked</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right; border-right: 1px solid white;">ptr_to_heavyweight_monitor:62 | lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">heavyweight locked</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right; border-right: 1px solid white;">| lock:2</td>
        <td style="border-right: 1px solid white;">osztályra mutató oop</td>
        <td style="text-align: right;">szemétgyűjtésre kijelölve</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">Tömbök esetén a fejléc mindhárom esetben kiegészül egy plusz 32 bites mezővel, ami a tömb méretét tárolja. Egy objektum kezdete mindig 8 bájtos határra van
    illesztve. A mark szó mezőinek jelentése:</p>
  <ul>
    <li><b>identity_hashcode</b>: az objektum identity hashkódja. Ha a System.identityHashCode(this) meghívódik, az eredmény a fejléc ezen mezőjébe kerül. Ez a mező lusta
      kiértékeléssel töltődik fel, vagyis az objektum létrehozásakor mindig 0 értéket kap és csak a hashCode() vagy - amennyiben az osztály felüldefiniálja az Object osztály hashCode
      metódusát annak meghívása nélkül - a System.identityHashCode(this) meghívásakor kerül bele hash kód.</li>
    <li><b>age</b>: az objektum által túlélt szemétgyűjtési ciklusok száma. Minden alkalommal inkrementálódik, amikor az objektum a young generáción belül másolódik. Amikor a mező
      értéke eléri a max tenuring threshold nevű küszöbértéket, akkor az objektum átkerül az old generációba.</li>
    <li><b>biased_lock</b>: 1-et tartalmaz, ha az objektumon engedélyezett a rögzített zárolás, 0-t ha nem.</li>
    <li><b>lock</b>: az objektum zárolási állapota:
      <ul>
        <li>00 - lightweight zárolt</li>
        <li>01 - unlocked vagy biased</li>
        <li>10 - heavyweight zárolt</li>
        <li>11 - szemétgyűjtésre kijelölve</li>
      </ul></li>
    <li><b>thread</b>: amikor az objektum egy adott szálhoz van rögzítve, akkor az identity hash kód mező a thread id-t tartalmazza</li>
    <li><b>epoch</b>: időbélyeg, ami a rögzítés (bias) érvényességét mutatja meg</li>
    <li><b>ptr_to_lock_record</b>: amikor a zárolási kérésekkel nincs probléma, a JVM atomi műveleteket használ OS mutexek helyet. Ez a megoldás lightweight zárolás néven ismert. Ekkor
      a JVM egy CAS (compare-and-swap) művelettel a lock rekordra mutató pointert állít be az objektum fejlécébe. (A CAS egy olyan atomi utasítás, amit a többszálúsításban szinkronizációhoz
      használnak. Egy memóriacímen lévő értéket összehasonlít egy adott értékkel és csak ha a kettő megegyezik, akkor cseréli le a memóriacímen lévő értéket egy másikra.)</li>
    <li><b>ptr_to_heavyweight_monitor</b>: ha két különböző szál párhuzamosan szinkronizál ugyanarra az objektumra, akkor a lightweight zárolást heavyweight monitorrá kell erősíteni a
      várakozó szálak kezeléséhez. Heavyweight zárolás esetén a JVM beállít az objektum fejlécében egy mutatót a monitorra.</li>
    <li><b>tömb mérete</b>: egy fejléc harmadik szava, de csak akkor szerepel, ha az objektum tömb. Ekkor a tömb méretét tartalmazza és az objektum adatterülete tartalmazza a tömb
      elemeit.</li>
  </ul>
  <p class="bekezd">A klass mező egy mutató az osztály metaadatra (később még lesz róla bővebben szó). Mivel egy objektum összes mezőihez tartozó információk egyenkénti letárolása
    nagyon gazdaságtalan lenne, a klass jó módszer ezeknek az információknak példányok közötti megosztására. Fontos azonban megjegyezni, hogy a klass által hivatkozott metaadatok
    különböznek az osztályok adataitól, amik egy osztálybetöltés eredményeképpen állnak elő. A különbség:</p>
  <ul>
    <li>az osztály objektumok (mint például a String osztály) szokványos Java objektumok. Olyan adatszerkezet reprezentálja ezeket a heap-en, mint más Java objektumokat és ugyanúgy is
      viselkednek, mint más objektumok. Java változók is hivatkozhatnak rájuk.</li>
    <li>klass: osztály metaadat JVM reprezentációk. Java kódban közvetlenül nem lehetséges referenciát szerezni egy klass-ra (ezek Java 7-ig a PermGen területen voltak). A klass
      gyakorlatilag egy JVM szintű tükre az osztály objektumnak az adott osztályhoz.</li>
  </ul>
  <p class="bekezd">
    A memóriában a fejléc után következnek az osztály mezői. A mezők mindig a típusuk méretének megfelelően vannak illesztve; tehát az int-ek 4 bájtos határra, a long-ok pedig 8 bájtos
    határra. Ennek teljesítménybeli okai vannak: illesztett adatokat a modern processzorok sokkal hatékonyabban be tudnak olvasni egy regiszterbe, mint nem illesztetteket. Föntebb már volt
    szó az objektumcsomagolásról, nézzük meg ezt és a mezők tárolását a gyakorlatban! (A példák mindig a <span class="programkod">-XX:-UseCompressedOops</span> JVM kapcsolóval készültek,
    vagyis a tömörített oop-ok kikapcsolásával.)
  </p>
  <p class="bekezd">Tekintsük a következő osztályt:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Vadallat {
        <span class="java_keyword">byte</span> a;
        <span class="java_keyword">int</span> b;
        <span class="java_keyword">boolean</span> c;
        <span class="java_keyword">long</span> d;
        Object e;
    }</pre>
  <p class="bekezd">Ha a JVM nem rendezné át a mezőket, akkor ennek egy példánya a memóriában (természetesen a teszthez használt 64 bites JVM-ek esetén) így nézne ki:</p>
  <pre class="programkod">  [FEJLÉC:  16 bájt] 16
  [a:        1 bájt] 17
  <span class="piros_kiemeles">[kitöltés: 3 bájt] 20</span>
  [b:        4 bájt] 24
  [c:        1 bájt] 25
  <span class="piros_kiemeles">[kitöltés: 7 bájt] 32</span>
  [d:        8 bájt] 40
  [e:        8 bájt] 48
  </pre>
  <p class="bekezd">Ebben az esetben a kitöltés 14 bájtot elpazarolna és az objektum 48 bájtot foglalna a memóriából (32 bites JVM esetén 40-et). A mezőket a JVM ezért a következő
    szabály szerint rendezi át:</p>
  <ol>
    <li>double és long</li>
    <li>int és float</li>
    <li>short és char</li>
    <li>boolean és bájt</li>
    <li>referenciák</li>
  </ol>
  <p class="bekezd">Az átrendezéssel a memória így fog kinézni a fenti objektum esetén:</p>
  <pre class="programkod">  [FEJLÉC:  16 bájt] 16
  [d:        8 bájt] 24
  [b:        4 bájt] 28
  [a:        1 bájt] 29
  [c:        1 bájt] 30
  <span class="piros_kiemeles">[kitöltés: 2 bájt] 32</span>
  [e:        8 bájt] 40
  </pre>
  <p class="bekezd">
    Ezúttal csak 2 bájt van kitöltésnek használva és az objektumok csak 40 bájt memóriát foglalnak (32 bites JVM esetén ez 32 lenne). Most már tudjuk, hogyan számoljuk ki bármely, az <span
      class="programkod">Object</span>-ből származó osztály példányának memóriafoglalását. Egy jó példa mondjuk a <span class="programkod">java.lang.Boolean</span> osztály. Íme ennek a
    memóriakiosztása:
  </p>
  <pre class="programkod">  [FEJLÉC:  16 bájt] 16 
  [érték:    1 bájt] 17
  <span class="piros_kiemeles">[kitöltés: 7 bájt] 24</span>
  </pre>
  <p class="bekezd">
    Egyetlen <span class="programkod">Boolean</span> objektumpéldány 24 bájt memóriát eszik (32 bites JVM esetén ez 16, hiszen a fejléc ott csak 8 bájtos).
  </p>
  <p class="bekezd">
    <b>Alosztályok és egyéb állatfajták</b>
  </p>
  <p class="bekezd">A következőkben megnézzük, hogy mit csinál a JVM a leszármazott osztályoknál. Az osztályhierarchiában különböző szinten lévő mezőket a JVM sosem keveri össze.
    Elsőként az ősosztály mezői jönnek a korábbiak szerint, aztán pedig az alosztályé. Nézzük ezt a példát:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Osallat {
        <span class="java_keyword">long</span> a;
        <span class="java_keyword">int</span> b;
        <span class="java_keyword">int</span> c;
    }
    
    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Vadallat <span class="java_keyword">extends</span> Osallat {
        <span class="java_keyword">long</span> d;
    }</pre>
  <p class="bekezd">A Vadallat egy példánya a következőképpen fog kinézni a memóriában:</p>
  <pre class="programkod">  [FEJLÉC: 16 bájt] 16
  [a:       8 bájt] 24
  [b:       4 bájt] 28
  [c:       4 bájt] 32
  [d:       8 bájt] 40
  </pre>
  <p class="bekezd">Amikor az ősosztály mezői nem illeszkednek a 8 bájtos határra, a JVM kitöltést alkalmaz az ősosztály és a leszármazott mezői között. Íme egy példa:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Osallat {
        <span class="java_keyword">byte</span> a;
    }
    
    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Vadallat <span class="java_keyword">extends</span> Osallat {
        <span class="java_keyword">byte</span> b;
    }
    
  [FEJLÉC:  16 bájt] 16
  [a:        1 bájt] 17
  <span class="piros_kiemeles">[kitöltés: 7 bájt] 24</span>
  [b:        1 bájt] 25
  <span class="piros_kiemeles">[kitöltés: 7 bájt] 32</span>
  </pre>
  <p class="bekezd">
    Látható az <span class="programkod">a</span> mezőt követő 7 bájtos kitöltés. Ezt a helyet a <span class="programkod">Vadallat</span> mezői nem használhatják. Ezen kívül érdemes
    megemlíteni, hogy 32 bites JVM esetén van még egy utolsó eset. Ha meg akarunk takarítani némi helyet, mert a leszármazott első mezője long vagy double, viszont az ősosztály mezői nem 8
    bájtos határon végződnek, a JVM megszegi az átrendezési szabályt. Ilyenkor megpróbál int-et, short-ot, majd bájtot és aztán referenciákat tenni az alosztálynak fenntartott hely elejére,
    amíg fel nem tölti a 8 bájtos illeszkedéshez szükséges rést. Ez 64 bájtos JVM esetén nem szükséges, mert ott az ősosztályok mindig 8 bájtos határon kell, hogy végződjenek, ahogy az
    előbbiekben láttuk.
  </p>
  <p class="bekezd">A tömbök esetén a fejléc kiegészül egy plusz 8 bájtos mezővel, ami a length változó értékét tárolja. Ezt követik a tömbben lévő elemek, de a tömbök természetesen
    szintén 8 bájtos határra illesztettek. Így néz ki egy bájttömb a memóriában:</p>
  <pre class="programkod">  [FEJLÉC:  24 bájt] 24
  [[0]:      1 bájt] 25
  [[1]:      1 bájt] 26
  [[2]:      1 bájt] 27
  <span class="piros_kiemeles">[kitöltés: 5 bájt] 32</span>
  </pre>
  <p class="bekezd">32 bites JVM-nél long tömbök esetén a fejlécet követi egy 4 bájtos kitöltés, mert ott a fejléc csak 12 bájtos. A fenti példák tömörített oop-ok esetén 64 bites
    JVM-nél nem változnak, mert a fejléc mérete még úgy is 8-cal osztható marad. Nem statikus belső osztályoknak van egy kiegészítő &quot;rejtett&quot; mezője, ami egy referenciát tartalmaz
    a külső osztályra. Ez azonban teljesen szokványos referencia, ami a mezőátrendezés szabályai szerint kezelhető, bár emiatt a belső osztályok esetén mindig van egy plusz 8 bájtos
    méretköltség.</p>
  <h3>Osztályok felépítése</h3>
  <p class="bekezd">Osztályok felépítése a memóriában Java 7 esetén a következőképpen néz ki (Java 8 esetén ez megváltozott és kikerült a heap-ről, ezért annak tárgyalásától
    eltekintek):</p>
  <p class="bekezd">
    <b>32 bites JVM</b>
  </p>
  <table cellpadding="0" cellspacing="0" width="45%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Mező</b></th>
        <th class="normal"><b>Hossz</b></th>
        <th class="normal"><b>Típus</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="text-align: right;">fejléc</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">klass</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">C++ vtbl ptr</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">layout helper</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">super check offset</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">name</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">secondary super cache</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">secondary supers</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">primary supers</td>
        <td style="text-align: right;">32 bájt</td>
        <td style="text-align: right;">8 elemű mutatótömb</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">java mirror</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">super</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">first subklass</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">next sibling</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">modifier flags</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">access flags</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <b>64 bites JVM</b>
  </p>
  <table cellpadding="0" cellspacing="0" width="65%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Mező</b></th>
        <th class="normal"><b>Hossz</b></th>
        <th class="normal"><b>Típus</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="text-align: right;">fejléc</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">klass</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">C++ vtbl ptr</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">layout helper</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">super check offset</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">name</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">secondary super cache</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">secondary supers</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">primary supers</td>
        <td style="text-align: right;">64 bájt (tömörített oop-ok esetén 32 bájt)</td>
        <td style="text-align: right;">8 elemű mutatótömb</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">java mirror</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">super</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">first subklass</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">next sibling</td>
        <td style="text-align: right;">8 bájt (tömörített oop-ok esetén 4 bájt)</td>
        <td style="text-align: right;">mutató</td>
      </tr>
      <tr class="tr2">
        <td style="text-align: right;">modifier flags</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
      <tr class="tr1">
        <td style="text-align: right;">access flags</td>
        <td style="text-align: right;">4 bájt</td>
        <td style="text-align: right;"></td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">Az osztály és objektum leírója le sem tagadhatnák, hogy közük van egymáshoz, hiszen mindkettő a fejléccel és a klass mutatóval kezdődik. A JVM fejlesztői egyébként
    szándékosan a leggyakrabban használt mezőket rendezték előre, mégpedig azért, hogy az esetleges gyorsítótárazás jobban működjön (a JVM egyik fejlesztője a HotSpot forráskódjában meg is
    jegyezte: ha nem használ, hát ártani biztos nem árt). Az osztály mezői a következők:</p>
  <ul>
    <li>fejléc: értéke mindig 0x00000001</li>
    <li>klass mutató: ez a mutató hivatkozik a <span class="programkod">java.lang.Class</span> osztályra a memóriában, mivel ez az adatszerkezet egy osztály
    </li>
    <li>C++ vtbl ptr: mutató az osztály virtuális tábladefiníciójára. Ez használatos a dinamikus továbbítás megvalósítására (dynamic dispatch). Ez annak a folyamata, hogy egy
      polimorfikus metódusnak melyik implementációját kell meghívni futásidőben.</li>
    <li>layout helper: a példány felszínes méretét (shallow size) adja meg. Mivel ezt nagyon gyakran használja a JVM, egyből ez következik a vtbl ptr után. Ez a méret a JVM aktuális
      mezőillesztési mechanizmusának figyelembe vételével számolódik, ami 64 bites rendszerben 8 bájt. Azon osztályoknál, amelyek sem példányok, sem pedig tömbök, az értéke nulla. Példányok
      esetén értéke pozitív szám; a példány mérete. Tömbök esetén az értéke negatív szám és négy különálló bájtból áll: tag, hsz, ebt, log2(esz), ahol:
      <ul>
        <li>tag: 0x80 ha az elemek oop-ok, 0xC0 ha nem. Ez a bájt példányosztályoknál egyébként mindig 0x00, mivel azok mérete bájtokban mérve mindig kisebb, mint 24 MB.</li>
        <li>hsz: a tömb fejlécének mérete bájtokban (vagyis az első elem offszetje)</li>
        <li>ebt: az elemek BasicType értéke</li>
        <li>esz: az eleméret bájtokban. A tárolt esz biteket az SLL assembly utasítás gyorsan, maszkolás nélkül tudja használni.</li>
      </ul>
    </li>
    <li>super check offset, secondary super cache, secondary supers és primary supers: ezek a mezők használatosak az ősosztály gyors ellenőrzéséhez</li>
    <li>name: mutató az osztály nevére. Példány osztályoknál: <span class="programkod">hu/egalizer/gctest/Vadallat</span>, stb. Tömb osztályoknál: <span class="programkod">[I</span>
      (int tömbnél) vagy például <span class="programkod">[Lhu/egalizer/gctest/Vadallat;</span>, stb. Minden más osztálynál az értéke nulla.
    </li>
    <li>super: az ősosztály definíciójára mutató hivatkozás, ami példánkban az <span class="programkod">Osallat</span>. Az <span class="programkod">Osallat</span> esetén pedig a <span
      class="programkod">java.lang.Object</span> osztály.
    </li>
    <li>subklass: az első alosztály, vagy nulla ha nincs ilyen</li>
    <li>módosító flag-ek: ez adja meg a Java módosítókat az alábbi táblázat szerint. Az értéke bitenkénti VAGY művelettel számolódik. Ha a példánkban a Vadallat &quot;public&quot;
      elérésű és &quot;final&quot;, a módosító flag-ek értéke 0x00000001 | 0x00000010 = 0x00000011. Az Osallat csak publikus, így a módosító flag mező értéke 1.</li>
    <li>access flag-ek: elérési flag-ek. Itt tárolódik az osztály/interface megkülönböztetés és egyéb rendszerszintű jelzőbitek.</li>
  </ul>
  <p class="bekezd">A módosító flag-ek értékei:</p>
  <p class="bekezd">Public: 0x00000001</p>
  <p class="bekezd">Protected: 0x00000002</p>
  <p class="bekezd">Private: 0x00000004</p>
  <p class="bekezd">Abstract: 0x00000400</p>
  <p class="bekezd">Static: 0x00000008</p>
  <p class="bekezd">Final: 0x00000010</p>
  <p class="bekezd">Strict: 0x00000800</p>
  <p class="bekezd">
    Nézzünk meg most már egy konkrét példát is az osztály memóriabeli szerkezetére. Az <span class="programkod">Osallat</span> osztály eddig is jó szolgálatot tett, legyen most (ebből ugyan
    már <span class="programkod">Vadallat</span> most nem fog származni, de annyi baj legyen...):
  </p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">final</span> <span class="java_keyword">class</span> Osallat {
        <span class="java_keyword">byte</span> a;
    }</pre>
  <p class="bekezd">Ennek a memóriabeli képe a következőképpen fog kinézni (alább pedig a színkódokkal megjelölt mezők, egymás utáni sorrendben):</p>
  <table cellpadding="0" cellspacing="0" width="60%" class="hexdatatable">
    <tbody>
      <tr>
        <td><pre class="programkod">
     0|  1|  2|  3|  4|  5|  6|  7|  8|  9|  A|  B|  C|  D|  E|  F|
00  <span style="color: #800000;">01| 00| 00| 00| 00| 00| 00| 00|</span> <span style="color: #6ce2fe;">70| 02| 00| 80| 00| 00| 00| 00|</span>
10  <span style="color: #ffaa00">88| 0A| 41| 57| 00| 00| 00| 00|</span> <span style="color: #0bc512">18| 00| 00| 00|</span> 40| 00| 00| 00|
20  20| 86| A7| 0A| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00|
30  58| 1C| 00| 80| 00| 00| 00| 00| 38| 2C| 00| 80| 00| 00| 00| 00|
40  08| E9| 27| 80| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00|
50  00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00|
60  00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00|
70  00| 00| 00| 00| 00| 00| 00| 00| 28| 19| 38| D9| 00| 00| 00| 00|
80  38| 2C| 00| 80| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00| 00|
90  88| E1| 27| 80| 00| 00| 00| 00| <span style="color: #1e000a">11| 00| 00| 00|</span> 31| 00| 20| 20|</pre></td>
      </tr>
    </tbody>
  </table>
  <ul>
    <li style="color: #800000">0x0000000000000001: fejléc</li>
    <li style="color: #6ce2fe">0x0000000080000270: mutató a Class osztályra</li>
    <li style="color: #ffaa00">0x0000000057410A88: vtbl ptr</li>
    <li style="color: #0bc512">0x00000018: az objektumpéldányok mérete</li>
    <li style="color: #1e000a">0x00000011: módosító flag-ek, az osztály láthatóan <span class="programkod">public final</span></li>
  </ul>
  <h2>Éljünk veszélyesen!</h2>
  <p class="bekezd">
    A Java eredetileg biztonságos, menedzselt környezetnek készült. A HotSpot azonban tartalmaz egy kiskaput, ami számos alacsonyszintű műveletet biztosít a memória és a szálak közvetlen
    piszkálásához. Ezt a kiskaput (<span class="programkod">sun.misc.Unsafe</span>) egyébként maga a Java is használja olyan csomagokban, mint a <span class="programkod">java.nio</span>
    vagy a <span class="programkod">java.util.concurrent</span>. Éles környezetben természetesen az <span class="programkod">Unsafe</span> használata egyáltalán nem ajánlott, mert ez az API
    eléggé veszélyes, nem hordozható és nem is szabványos. Nekünk viszont most remek eszközt ad ahhoz, hogy (ha van elég bátorságunk) belenézzünk a HotSpot JVM belsejébe és néhány trükköt
    is megcsináljunk. Néha C++ debugolás nélkül alkalmas a JVM belsejének tanulmányozásához, néha pedig használható profiling és fejlesztői eszközökhöz.
  </p>
  <p class="bekezd">
    A <span class="programkod">sun.misc.Unsafe</span> annyira nem támogatott, hogy a JDK fejlesztői speciális ellenőrzésekkel gátolták az elérését: a konstruktora privát és a <span
      class="programkod">getUnsafe()</span> gyártófüggvény hívóját a bootstrap osztálybetöltőnek (classloader) kell betöltenie. Amikor nem ez a helyzet (tehát minden felhasználói kódnál),
    akkor a hívása - ahogy az alábbi kódrészletből látható - <span class="programkod">SecurityException</span> kivételt fog dobni.
  </p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">final</span> <span class="java_keyword">class</span> Unsafe {
        ...
        <span class="java_keyword">private</span> Unsafe() {
        }
        <span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">final</span> Unsafe theUnsafe;
        ...
        <span class="java_keyword">public</span> <span class="java_keyword">static</span> Unsafe getUnsafe() {
            Class arg = Reflection.getCallerClass();
            <span class="java_keyword">if</span> (arg.getClassLoader() != <span class="java_keyword">null</span>) {
                <span class="java_keyword">throw</span> <span class="java_keyword">new</span> SecurityException(<span class="java_string">&quot;Unsafe&quot;</span>);
            } <span class="java_keyword">else</span> {
                <span class="java_keyword">return</span> theUnsafe;
            }
        }
        ...        
    }</pre>
  <p class="bekezd">
    (Megjegyzendő, hogy ha Eclipse fejlesztő környezetben szeretnénk az Unsafe osztályt használni, akkor &quot;Access restriction: The type 'Unsafe' is not API&quot; fordítási hibát kapunk.
    A szabvány javac fordító viszont csak warninggal figyelmeztet, hogy ez az API nem szabványos és a jövőben megváltozhat. Az Eclipse korlát kiküszöböléséhez a Java build path elérési
    jogainak <a onclick="window.open(this.href);return false;" href="http://stackoverflow.com/questions/9266632/access-restriction-is-not-accessible-due-to-restriction-on-required-library">beállítását
      kell módosítani</a>.)
  </p>
  <p class="bekezd">
    Bár JDK 8 alatt a <span class="programkod">getUnsafe</span> feltételvizsgálata így néz ki:
  </p>
  <pre class="programkod">
    <span class="java_keyword">if</span> (!VM.isSystemDomainLoader(arg.getClassLoader()))</pre>
  <p class="bekezd">
    De ez ne bizonytalanítson el minket, az <span class="programkod">isSystemDomainLoader</span> ugyanúgy csak egy nullvizsgálatot végez.
  </p>
  <div class="keretes">
    <p>
      A Java esetén használható sokféle osztálybetöltő (classloader) közül a bootstrap osztálybetöltő az egyetlen, ami még nem Java osztályként, hanem natív kódként van implementálva. Ez
      tölti be a JVM indulásakor az összes kódot, ami szükséges az alapvető Java runtime funkcionalitáshoz. Egy osztálynál az <span class="programkod">Osztaly.class.getClassLoader()</span>
      hívás adja vissza az azt betöltő classloader-t. A HotSpot esetén ez null-t ad vissza, ha az osztályt a bootstrap classloader töltötte be. Ilyen például a <span class="programkod">String</span>,
      <span class="programkod">ArrayList</span>, <span class="programkod">System</span>, stb. Saját <span class="programkod">Vadallat</span> osztályunk viszont már nem.
    </p>
    <p>
      A <span class="programkod">Vadallat.class.getClassLoader()</span> már valami ilyesmit fog visszaadni:
    </p>
    <p>sun.misc.Launcher$AppClassLoader@73d16e93</p>
  </div>
  <p class="bekezd">
    A lényeg tehát, hogy nem tudunk csak úgy egyszerűen példányosítani egy <span class="programkod">Unsafe</span>-et. Szerencsére ott van azonban a <span class="programkod">theUnsafe</span>
    mező, amit fel lehet használni arra, hogy kapjunk egy <span class="programkod">Unsafe</span> példányt. Írhatunk egy segédmetódust, ami megcsinálja ezt nekünk reflection-ön keresztül.
    Például egy ilyet:
  </p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">static</span> Unsafe getUnsafe() {
        <span class="java_keyword">try</span> {
            Field f = Unsafe.<span class="java_keyword">class</span>.getDeclaredField(<span class="java_string">&quot;theUnsafe&quot;</span>);
            f.setAccessible(true);
            <span class="java_keyword">return</span> (Unsafe) f.get(<span class="java_keyword">null</span>);
        } <span class="java_keyword">catch</span> (Exception e) {
            <span class="java_comment">/* */</span>
        }
    }</pre>
  <p class="bekezd">Na és mire lehet használni az Unsafe-et? Például ezekre:</p>
  <ul>
    <li>VM manipulálás, például CAS (compare-and-swap) művelet. Ezt a zárolás-mentes hash táblák használják. Az <span class="programkod">Unsafe.compareAndSwapInt</span> metódus JNI-vel
      natív kódot hív, ami speciális utasításokat tartalmaz a CAS-hoz.
    </li>
    <li>inicializálatlan objektumpéldányosítás (az <span class="programkod">allocateInstance</span> metódussal). Ezután a konstruktorhívást már úgy kezelhetjük, mint bármely más
      metódushívást.
    </li>
    <li>vissza tudunk követni egy adatot natív címig. Meg tudjuk szerezni egy objektum memóriacímét és az unsafe get/put metódusokkal műveletet tudunk végezni közvetlenül a mezőin.</li>
    <li>az <span class="programkod">allocateMemory</span> metódussal memóriát foglalhatunk a heap-en kívül. A <span class="programkod">java.nio.ByteBuffer allocateDirect</span>
      metódusa egy <span class="programkod">DirectByteBuffer</span>-t példányosít és annak a konstruktora is ezt hívja. Gyakorlatilag a C malloc függvényének egy wrapperje.
    </li>
    <li>az <span class="programkod">arrayBaseOffset</span> és <span class="programkod">arrayIndexScale</span> metódusok használhatók &quot;arraylet&quot;-ek fejlesztéséhez. Így
      hatékonyan fel lehet szabdalni nagy tömböket kisebb objektumokra, hogy nagy objektumokon korlátozza a scan, update vagy movel valósidejű költségét.
    </li>
    <li>a <span class="programkod">getAddress</span> és <span class="programkod">putAddress</span> metódusok lehetővé teszik, hogy a memóriából közvetlenül duplaszavakat olvassunk vagy
      írjunk
    </li>
    <li>a <span class="programkod">getInt</span>, <span class="programkod">putInt</span> és hasonló metódusok lehetővé teszik, hogy közvetlenül abból a C struktúrából olvassunk és
      írjunk adatokat, ami a Java objektumokat reprezentálja
    </li>
  </ul>
  <h3>Játék a tűzzel</h3>
  <p class="bekezd">
    Nem azért ismertük meg ezeket a titkos praktikákat, hogy kihasználatlanul hagyjuk, úgyhogy nézzünk valami izgalmasat ezek használatával. A tesztek során továbbra is a <span
      class="programkod">Vadallat</span> osztályt használjuk, ezen nézzük meg, hogyan lehet megszerezni egy objektum címét, kilistázni mezőinek szerkezetét, stb.
  </p>
  <p class="bekezd" style="text-decoration: underline;">
    1. trükk: egy <i>osztály</i> memóriacímének megszerzése
  </p>
  <p class="bekezd">Egy Java osztály memóriacímének lekérdezésére nincs egyszerű módszer, ezért piszkos trükkökhöz kell folyamodnunk. Abból viszont kettő is van.</p>
  <p class="bekezd">
    <b>1. megoldás</b>: ahogy láttuk, minden objektum tartalmaz egy _klass nevű mutatót az osztályára (de csak a konkrét osztályra, interface-re vagy absztrakt osztályra nem). Ha egy
    objektum memóriacíme megvan, akkor az osztály címének megszerzése már gyerekjáték. Ez a módszer persze csak olyan osztályoknál használható, amikből lehet példányosítani. Ez az objektum
    fejlécében a második mező (32 bites JVM-nél az objektum memóriacímétől az offszet 4, 64 bites JVM-nél 8). Ennek a kiolvasásához már használhatjuk az Unsafe osztályt.
  </p>
  <p class="bekezd">32 bites JVM esetén:</p>
  <pre class="programkod">        Vadallat vadallatObject = <span class="java_keyword">new</span> Vadallat();
        <span class="java_keyword">int</span> addressOfVadallatClass = unsafe.getInt(vadallatObject, 4L);
  </pre>
  <p class="bekezd">64 bites JVM esetén:</p>
  <pre class="programkod">        Vadallat vadallatObject = <span class="java_keyword">new</span> Vadallat();
        <span class="java_keyword">long</span> addressOfVadallatClass = unsafe.getLong(vadallatObject, 8L);
  </pre>
  <p class="bekezd">64 bites JVM esetén tömörített oop-okkal:</p>
  <pre class="programkod">        Vadallat vadallatObject = <span class="java_keyword">new</span> Vadallat();
        <span class="java_keyword">int</span> addressOfVadallatClass = unsafe.getInt(vadallatObject, 8L);
  </pre>
  <p class="bekezd">
    <b>2. megoldás</b>: ezzel bármilyen osztály (interface, annotáció, absztrakt osztály, enum) címe meghatározható. Az osztályok is tartalmaznak ugyanis saját magukra hivatkozó mutatót.
    Java 7-ben egy osztálydefiníció memóriacíme a következőképp alakul:
  </p>
  <ul>
    <li>32 bites JVM esetén 4 bájt egy 80 bájtos offszettől</li>
    <li>64 bites JVM esetében 8 bájt egy 160 bájtos offszettől</li>
    <li>64 bites JVM esetén tömörített oop-okkal 4 bájt egy 84 bájtos offszettől.</li>
  </ul>
  <p class="bekezd">Ezek az offszetek egyébként a class fájl parser-jének forrásában vannak rejtett mezőként definiálva. A cím meghatározásához szükséges kódrészletek:</p>
  <p class="bekezd">32 bites JVM esetén:</p>
  <pre class="programkod">        <span class="java_keyword">int</span> addressOfVadallatClass = unsafe.getInt(Vadallat.<span class="java_keyword">class</span>, 80L);
  </pre>
  <p class="bekezd">64 bites JVM esetén:</p>
  <pre class="programkod">        <span class="java_keyword">long</span> addressOfVadallatClass = unsafe.getLong(Vadallat.<span class="java_keyword">class</span>, 160L);
  </pre>
  <p class="bekezd">64 bites JVM esetén tömörített oop-okkal:</p>
  <pre class="programkod">        <span class="java_keyword">int</span> addressOfVadallatClass = unsafe.getInt(Vadallat.<span class="java_keyword">class</span>, 84L);
  </pre>
  <p class="bekezd" style="text-decoration: underline;">
    2. trükk: egy <i>objektum</i> memóriacímének megszerzése
  </p>
  <p class="bekezd">
    Egy objektum memóriacímének megszerzése kicsit izgalmasabb, mint az osztályé, erre ugyanis nincs közvetlen Unsafe metódus, de ez rajtunk nem fog ki. A meghatározáshoz felhasználjuk az
    objektumszerkezetről frissen szerzett tudásunkat és egy <span class="programkod">java.lang.Object</span> típusú 1 elemű segédtömböt:
  </p>
  <ol>
    <li>a segédtömb nulladik (és egyetlen) elemének állítsuk be a célobjektumot. Mivel az elem referencia (nem pedig érték) típusú, a címe nullás indexszel fog tárolódni a tömbben.</li>
    <li>ezután szerezzük meg a segédtömb bázis offszetjét. Egy tömb bázis offszetje az elemek kezdőpontjának offszetje a tömb objektum kezdőcíméhez képest.</li>
    <li>nézzük meg a JVM címméretét:
      <ul>
        <li>ha a JVM 32 bites, akkor olvassunk integer értéket &lt;tömb_címe&gt;+&lt;tömb_bázis_offszetje&gt; értékkel az Unsafe osztállyal. Ez a 4 bájtos egész lesz a célobjektum
          címe.</li>
        <li>ha a JVM 64 bites, akkor olvassunk egy long értéket a &lt;tömb_címe&gt;+&lt;tömb_bázis_offszetje&gt; értékkel az Unsafe osztállyal. Ez a 8 bájtos egész lesz a célobjektum
          címe.</li>
      </ul>
    </li>
  </ol>
  <p class="bekezd">32 bites JVM esetén:</p>
  <pre class="programkod">        Object[] helperArray = <span class="java_keyword">new</span> Object[1];
        helperArray[0] = vadallat;
        <span class="java_keyword">long</span> baseOffset = unsafe.arrayBaseOffset(Object[].<span class="java_keyword">class</span>);
        <span class="java_keyword">int</span> addressOfObject = unsafe.getLong(helperArray, baseOffset);</pre>
  <p class="bekezd">64 bites JVM esetén:</p>
  <pre class="programkod">        Object[] helperArray = <span class="java_keyword">new</span> Object[1];
        helperArray[0] = vadallat;
        <span class="java_keyword">long</span> baseOffset = unsafe.arrayBaseOffset(Object[].<span class="java_keyword">class</span>);
        <span class="java_keyword">long</span> addressOfObject = unsafe.getLong(helperArray, baseOffset);</pre>
  <p class="bekezd">A példákban a Vadallat egy példányát használjuk, de ez természetesen bármely másik osztály bármely példánya lehetne!</p>
  <p class="bekezd">
    <b>Méricskélés</b>
  </p>
  <p class="bekezd">A C/C++ nyelvektől eltérően a Java-ban nincs sizeOf operátor, ami megmondaná, hogy a primitív típusok vagy objektumok mennyi helyet fogyasztanak. Pedig ez akár még
    hasznos is lehetne az I/O műveletekhez, memóriakezeléshez, stb. Persze egy ilyen operátornak Java-ban azért nincs igazából értelme, mert a primitív típusok méretét a nyelv
    specifikációja megmondja és a nyelvben nincsenek címaritmetikához használható mutatók.</p>
  <p class="bekezd">Mindenesetre kétféleképpen lehet meghatározni azt, hogy egy osztály mezői mennyi memóriát foglalnak:</p>
  <ul>
    <li>shallow size</li>
    <li>deep size</li>
  </ul>
  <p class="bekezd">A shallow size jelenti az objektum méretét a saját mezőivel, de az általa esetleg tartalmazott objektum referenciákkal nem. Ez utóbbi fogalom a deep size, ami
    kiterjeszti a shallow size-t azon objektumok méretével, amikre az objektum hivatkozik.</p>
  <p class="bekezd">
    <b>sizeOf() függvény</b>
  </p>
  <p class="bekezd">Egy osztály példányának mérete a layout helper mezőben tárolódik, ami a metaadatban a negyedik. Az objektum klass mezője megadja az osztály metaadatok címét, annak
    pedig 64 bites JVM esetén a 24. offszetjén van a layout helper. Ezt kiolvasva megvan a shallow size:</p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">long</span> sizeOf(Object object) {
        <span class="java_keyword">return</span> unsafe.getInt(normalize(unsafe.getLong(object, 8L)) + 24L);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">long</span> normalize(<span class="java_keyword">long</span> value) {
        <span class="java_keyword">if</span> (value &gt;= 0) {
            <span class="java_keyword">return</span> value;
        }
        <span class="java_keyword">return</span> (~0L &gt;&gt;&gt; 64) &amp; value;
    }</pre>
  <p class="bekezd">
    A <span class="programkod">normalize()</span> függvényt azért kell használni, mert a 2<sup>31</sup> és 2<sup>32</sup> közötti címek automatikusan negatív számmá konvertálódnak, vagyis
    komplemens alakban tárolódnak. Nézzük az eredményeket 64 bites JVM-ben:
  </p>
  <pre class="programkod">
<span class="java_keyword">public</span> <span class="java_keyword">class</span> Osallat { } <span class="java_comment">// 16: 8 a mark szó és 8 a klass szó</span> </pre>
  <pre class="programkod">
<span class="java_keyword">public</span> <span class="java_keyword">class</span> Osallat { <span class="java_keyword">int</span> a; } <span class="java_comment">// 24: 16 a fejléc, 4 az int mező és 4 a kitöltés</span> </pre>
  <pre class="programkod">
<span class="java_keyword">public</span> <span class="java_keyword">class</span> Osallat { <span class="java_keyword">int</span> a; <span class="java_keyword">long</span> b; } <span
      class="java_comment">// 32: 16 a fejléc, 8 a long, 4 az int és 4 a kitöltés</span> </pre>
  <p class="bekezd">
    Ez a függvény nem működik tömbökkel, mert ott a layout helper mezőnek más jelentése van, de persze a fentebbi információk tükrében lehet általánosítani a <span class="programkod">sizeOf()</span>
    függvényt úgy, hogy a tömböket is támogassa.
  </p>
  <p class="bekezd">
    <b>Közvetlen memóriakezelés</b>
  </p>
  <p class="bekezd">Az Unsafe lehetővé teszi, hogy közvetlenül foglaljunk és szabadítsunk fel memóriát az allocateMemory és a freeMemory metódusokkal. A lefoglalt memória nincs a GC
    hatálya alatt és nem korlátozza a maximum JVM heap méret sem. Ez a lehetőség egyébként megvan a NIO csomag off-heap puffereivel is, de az Unsafe esetén az az érdekes, hogy lehetségessé
    válik leképezni szabványos Java referenciákat off-heap memóriaterületre:</p>
  <pre class="programkod">    Osallat osallat = <span class="java_keyword">new</span> Osallat();<span class="java_comment">// ez lesz a kísérleti egerünk</span>
    osallat.a = 200;
    <span class="java_keyword">long</span> size = sizeOf(osallat);
    <span class="java_keyword">long</span> offheapPointer = getUnsafe().allocateMemory(size);
    getUnsafe().copyMemory(osallat, <span class="java_comment">// forrásobjektum</span>
        0, <span class="java_comment">// a forrásoffszet nulla - a teljes objektumot másolni kell</span>
        <span class="java_keyword">null, </span><span class="java_comment">// a cél abszolút címmel van megadva, tehát a célobjektum null</span>
        offheapPointer, <span class="java_comment">// célcím</span>
        size);<span class="java_comment">// a tesztobjektumunk át lesz másolva a heap-en kívülre</span>

    Pointer p = <span class="java_keyword">new</span> Pointer();<span class="java_comment">// a Pointer csak egy handler, ami valamely objektum címét tárolja</span>
    <span class="java_keyword">long</span> pointerOffset = getUnsafe().objectFieldOffset(Pointer.<span class="java_keyword">class</span>.getDeclaredField(<span class="java_string">"pointer"</span>));
    getUnsafe().putLong(p, pointerOffset, offheapPointer);
        <span class="java_comment">// a mutatót a tesztobjektum heap-en kívüli másolatára állítjuk</span>

    osallat.a = 100;<span class="java_comment"> // átírjuk az eredeti objektumban lévő x értékét</span>
    System.out.println(((Osallat) p.pointer).a);<span class="java_comment"> // 200-at fog kiírni</span>
  </pre>
  <p class="bekezd">Tehát még valódi objektumokat is lehetséges manuálisan lefoglalni és felszabadítani, nem csak bájtpuffereket. Persze nagy kérdés, hogy mit csinál a GC ilyen trükkök
    után. Saját tesztjeimből az a tapasztalatom, hogy - természetesen - csak a heap-en lefoglalt területtel törődik, a többit már a programnak kell felszabadítania.</p>
  <p class="bekezd">
    <b>Öröklés final osztályból és a void*</b>
  </p>
  <p class="bekezd">
    Tegyük fel, hogy van egy metódusunk, ami egy sztringet vár paraméterként, viszont jó lenne átadni neki a String által nem átvitt adatot is. Java-ban ennek két szokványos módja van: a
    plusz infót thread-local változóba rakjuk vagy pedig statikus mezőt használunk. Nos az Unsafe behoz még két plusz lehetőséget: átadjuk az információ címét sztringként illetve a plusz
    infó osztályát a String-ből származtathatjuk. Ebből az első megközelítés hasonló az előző részben látottakhoz - csak át kell adni a plusz információ címét a Pointert használva, a hívott
    metódusban pedig létre kell hozni egy új <span class="programkod">Pointer</span>-t, ami arra mutat. Tehát bármilyen paraméter, amin keresztül át tudunk adni címet, használható a C <span
      class="programkod">void*</span> megoldásához hasonlóan. A második módszerhez először nézzük a következő kódrészletet, ami jónak tűnik, de persze futásidőben kiköp nekünk egy <span
      class="programkod">ClassCastException</span> kivételt:
  </p>
  <pre class="programkod">    <span class="java_keyword">public</span> <span class="java_keyword">class</span> Csomag {
        <span class="java_keyword">public</span> <span class="java_keyword">int</span> titok;
    }

    ...
   
    Csomag csomag = <span class="java_keyword">new</span> Csomag();
    csomag.titok = 777;

    String message = (String) (Object) csomag;<span class="java_comment">// ClassCastException</span>
    handler(message);

    ....
        
    <span class="java_keyword">static</span> <span class="java_keyword">void</span> handler(String message) {
        System.out.println(((Csomag) (Object) message).titok);
    }</pre>
  <p class="bekezd">
    Ahhoz, hogy ez működjön, módosítani kell a <span class="programkod">Csomag</span> osztályt úgy, hogy szimulálja az egyébként final String-ből való származást. Az ősosztályok listája egy
    osztályszerkezetben a primary supers tömbben van, ami 64 bites JVM esetén az 56. bájttól kezdődik. Itt elsőként az objektumra hivatkozó mutató van, másodikként pedig magára a <span
      class="programkod">Csomag</span>-ra hivatkozó mutató (64. bájt), mivel a <span class="programkod">Csomag</span>-ot közvetlenül az <span class="programkod">Object</span> osztályból
    örököltettük. Elég hozzáadni a következő kódot a Csomagot Stringgé cast-oló kód elé:
  </p>
  <pre class="programkod">    <span class="java_keyword">long</span> csomagClassAddress = normalize(unsafe.getLong(csomag, 8L));
    <span class="java_keyword">long</span> stringClassAddress = normalize(unsafe.getLong(<span class="java_string">""</span>, 8L));
    unsafe.putAddress(csomagClassAddress + 64, stringClassAddress);<span class="java_comment">// a Csomag őse immár a String</span>
  </pre>
  <p class="bekezd">Láss csodát: a cast (de nekem nagyon tetszik a magyar típuskényszerítés szó is) most már jól működik. Persze ez az átalakítás nagyon csúnya és szembeköpi a Java
    nyelvi megkötéseit. Egy óvatosabb megközelítéshez még két lépés kell:</p>
  <ol>
    <li>a <span class="programkod">Csomag</span> osztályban az 56-os pozíció egy mutatót tartalmaz magára a <span class="programkod">Csomag</span> osztályra, tehát ezt a mutatót el
      kell tolni a 64-es pozícióra, nem elég azt simán csak felülírni a <span class="programkod">String</span> osztályra.
    </li>
    <li>mivel a <span class="programkod">Csomag</span> most már a <span class="programkod">String</span>-ből származik, a <span class="programkod">String</span>-ből a <span
      class="programkod">final</span> jelölőt el kell távolítani.
    </li>
  </ol>
  <p class="bekezd">
    <b>Konklúzió</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">sun.misc.Unsafe</span> majdnem korlátlan lehetőségeket biztosít a VM futásidejű adatszerkezeteinek módosításához és felfedezéséhez. Bár magához a Java nyelvű
    fejlesztéshez nem igazán használható és nem ajánlott, az Unsafe remek eszköz akárkinek, aki tanulmányozni akarja a HotSpot VM-et C++ kód debuggolás nélkül.
  </p>
  <h2>Fantomok a nyelvben</h2>
  <p class="bekezd">
    Most már közelediünk a szemétgyűjtő belvilágához, de még mielőtt nyakig merülnénk benne, érdemes egy olyan nyelvi tulajdonságot megismerni, ami már régóta a Java része, mégis kevesen
    tudnak róla. A Java 1.2-es változata 1998 decemberében jelent meg és elég jelentős mérföldkőnek számított a nyelv történetében; ha az újdonságok számát csak mennyiségi tekintetben is
    nézzük: a platformban lévő osztályok száma megháromszorozódott a korábbi verzióhoz képest. Ami minket itt ezek közül érdekel, az egy kis rész, mégpedig a <span class="programkod">java.lang.ref</span>
    csomagból. Ez (azóta is) öt osztályt tartalmaz:
  </p>
  <ul>
    <li><b>PhantomReference</b></li>
    <li><b>Reference</b> (absztrakt)</li>
    <li><b>ReferenceQueue</b></li>
    <li><b>SoftReference</b></li>
    <li><b>WeakReference</b></li>
  </ul>
  <p class="bekezd">
    A három különféle Reference alosztály a <span class="programkod">Reference</span> absztrakt osztályból származik és egy másik objektumra való referencia huncut kezelését teszik
    lehetővé. A kívánt referenciát a leszármazott osztályok konstruktorának lehet átadni és utólag nem módosítható. A <span class="programkod">Reference</span> ősosztálynak négy metódusa
    van:
  </p>
  <ul>
    <li><b>clear()</b>: törli a referenciát</li>
    <li><b>get()</b>: visszaadja a hivatkozott objektumot (vagy nem)</li>
    <li><b>enqueue()</b>: ezt a metódust mi is meghívhatjuk, de valószínűleg a JVM fogja megtenni. A puha, gyenge és fantom referenciák esetében, amikor egy hivatkozott objektumot
      összegyűjtött és ledarált a GC, akkor a JVM meghívja az objektumra hivatkozó <span class="programkod">Reference</span> objektum <span class="programkod">enqueue()</span> metódusát. És
      ilyenkor a referenciát korábban hordozó objektum bele fog kerülni egy <span class="programkod">ReferenceQueue</span>-ba (abba, amit létrehozáskor átadtunk a konstruktornak, legalábbis
      ha tettünk ilyet). Ilyen módon kaphatunk értesítést arról, hogy egy korábban létrehozott objektumot a rendszer begyűjtött és ledarált.</li>
    <li><b>isEnqueued()</b>: megmondja, hogy a <span class="programkod">Reference</span> objektum be lett-e már téve egy <span class="programkod">ReferenceQueue</span>-ba</li>
  </ul>
  <p class="bekezd">Szemétgyűjtővel foglalkozó cikk nem lehet teljes a különböző típusú referenciák ismertetése nélkül, ezt itt sem úszhatjuk meg, nézzük sorba tehát, mik is ezek.</p>
  <p class="bekezd">
    <b>Erős Pista - helyett erős referencia</b>
  </p>
  <p class="bekezd">
    Erős referenciára a <span class="programkod">java.lang.ref</span> nem tartalmaz külön alosztályt, de erre nincs is szükség, ugyanis ezt nap mint nap használja minden háziasszony, akarom
    mondani Java programozó. Ez pedig nem más, mint a szokványos Java referencia, mint például a következő:
  </p>
  <pre class="programkod">    StringBuffer buffer = <span class="java_keyword">new</span> StringBuffer();</pre>
  <p class="bekezd">
    Ez létrehoz egy új <span class="programkod">StringBuffer</span> objektumot és egy erős referenciát tárol hozzá a buffer változóban. Ezeket a referenciákat nem a spenót, hanem az teszi
    erőssé, ahogyan szemétgyűjtés esetén viselkednek. Ha egy objektumot el lehet érni a GC gyökérből erős referenciák láncán (strongly referenced - erősen elérhető), akkor nem pucolható ki
    szemétgyűjtéskor. (A GC gyökérről később még sokat fogunk hallani. Itt most elég annyi, hogy a GC gyökér a szemétgyűjtés kiindulópontja; minden, a program által aktuálisan használt
    referencia a GC gyökérből valamilyen útvonalon elérhető.) Amikor a szakirodalom referenciáról beszél, akkor általában az erős referenciát értik alatta. Ezért ebben a cikkben - hacsak
    nem egyértelmű - én is úgy teszek, mintha ez szakirodalom lenne. Az alábbi példában egy nem túl izgalmas dolog történik: addig toljuk a cuccot, amíg lehet - erős referenciákkal.
  </p>
  <pre class="programkod">    <span class="java_keyword">import</span> java.util.LinkedList;
    <span class="java_keyword">import</span> java.util.List;

    <span class="java_keyword">public</span> <span class="java_keyword">class</span> StrongTest {

        <span class="java_keyword">private</span> <span class="java_keyword">byte</span>[] getData() {
            <span class="java_keyword">return</span> <span class="java_keyword">new</span> <span class="java_keyword">byte</span>[1024*1024];
        }

        <span class="java_keyword">public</span> <span class="java_keyword">void</span> testStrongReferences() <span class="java_keyword">throws</span> Exception {
            List&lt;<span class="java_keyword">byte</span>[]&gt; bigStore=<span class="java_keyword">new</span> LinkedList&lt;&gt;();
            <span class="java_keyword">while</span>(true){
                bigStore.add(getData());
            }
        }

        <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Exception {
            <span class="java_keyword">new</span> StrongTest().testStrongReferences();
        }
    }</pre>
  <p class="bekezd">
    <b>Amikor az erős referencia túl erős</b>
  </p>
  <p class="bekezd">
    Tegyük fel, hogy egy alkalmazás olyan osztályokat használ, amikből nem lehet tovább származtatni. Ezek lehetnek egyszerűen final kulcsszóval is jelölve vagy lehet valami bonyolultabb
    oka is, mint például egy ismeretlen implementációval rendelkező gyártófüggvény (factory method) által visszaadott interface. Legyen példaként egy grafikus alkalmazásunk és abban egy <span
      class="programkod">Widget</span> osztályunk, amit valamilyen okból nem praktikus vagy nem lehetséges kiterjeszteni új funkcionalitás hozzáadásához. Mi van akkor, amikor egy ilyen
    objektumról kiegészítő információt kell tárolnunk? Például szükséges lehet minden <span class="programkod">Widget</span> sorszámát nyomon követni, de a <span class="programkod">Widget</span>
    osztálynak nincs sorszám mezője, és mivel a Widgetből nem lehet leszármaztatni, nem is adhatunk hozzá ilyet. Minket persze nem lehet ilyen könnyen elrettenteni, hát mire való a <span
      class="programkod">HashMap</span>?
  </p>
  <pre class="programkod">    serialNumberMap.put(widget, widgetSerialNumber);</pre>
  <p class="bekezd">
    Sima ügy! Legalábbis látszólag, ugyanis az erős referencia majdnem biztosan problémákat fog okozni. 100% bizonyossággal tudnunk kell ugyanis, hogy egy adott <span class="programkod">Widget</span>
    sorozatszámára mikor nincs többé szükség és ilyenkor el kell távolítani a map-ből, mert különben memóriaszivárgást kapunk (ha nem távolítjuk el a <span class="programkod">Widget</span>-et,
    amikor kellene) vagy pedig hiányzó sorszámokkal találkozunk (ha eltávolítunk olyan <span class="programkod">Widget</span>-eket, amiket még mindig használ valami). Ezek pont olyan
    problémák, amik szemétgyűjtés nélküli nyelvekben merülnek fel, pedig a Java nem ilyen.
  </p>
  <p class="bekezd">Egy másik szokványos probléma az erős referenciákkal a gyorsítótárazás, különösen olyan nagy méretű adatoknál, mint a képek. Tegyük fel, hogy van egy alkalmazásunk,
    ami a felhasználók által feltöltött képekkel foglalkozik, mint például egy weboldal tervező eszköz. Ezeket a képeket gyorsítótárazni szeretnénk, mert a lemezről nagyon költséges lenne
    mindig felolvasni és el akarjuk kerülni a lehetőségét annak, hogy egyszerre két másolatunk legyen egy képből a memóriában. Egy kép-gyorsítótár megoldja a problémát, viszont a szokványos
    erős referenciáknál a referencia kikényszeríti, hogy a kép mindig a memóriában maradjon, emiatt pedig a programozónak kell valahogy eldöntenie, hogy egy képnek mikor nem kell tovább a
    memóriában lennie és mikor lehet eltávolítani a cache-ből, hogy a szemétgyűjtő megkaparinthassa. Vagyis manuálisan kell reprodukálni a szemétgyűjtő működését. Na itt jönnek be a gyenge
    referenciák.</p>
  <p class="bekezd">
    <b>Gyenge referencia</b>
  </p>
  <p class="bekezd">Egy gyenge referencia (weak reference) olyan referencia, ami nem elég erős annak kikényszerítéséhez, hogy egy objektum a memóriában maradjon. Ez lehetővé teszi, hogy
    kihasználjuk a szemétgyűjtő azon képességét, hogy meghatározza, egy objektumot használ-e még valami. Így ezt nem nekünk kell megtenni. Így lehet gyenge referenciát csinálni:</p>
  <pre class="programkod">    WeakReference&lt;Widget&gt; weakWidget = <span class="java_keyword">new</span> WeakReference&lt;Widget&gt;(widget);</pre>
  <p class="bekezd">
    A kódban a <span class="programkod">weakWidget.get()</span> hívással tudjuk lekérdezni a tulajdonképpeni widget objektumot. A gyenge referencia nem elég erős hozzá, hogy meggátolja a
    szemétgyűjtést, tehát ha már nincs másik erős referencia a widget-re, vagyis erős referenciával senki más nem használja, akkor a <span class="programkod">weakWidget.get() null</span>-t
    ad vissza. A fenti widget-sorozatszám probléma megoldásához a legegyszerűbb a beépített <span class="programkod">WeakHashMap</span> osztály használata. A <span class="programkod">WeakHashMap</span>
    pont úgy működik, mint a <span class="programkod">HashMap</span>, kivéve, hogy a kulcsok (nem az értékek!) gyenge referenciát használnak. Ha egy <span class="programkod">WeakHashMap</span>
    kulcsa szemétté válik, akkor a bejegyzést teljes egészében eltávolítja a JVM. Ez egyből megoldja a korábbi problémákat és nem igényel más módosítást azon kívül, hogy lecseréljük a <span
      class="programkod">HashMap</span>-et <span class="programkod">WeakHashMap</span>-re. Ha követjük a kódolási konvenciókat, vagy a map-jeinkre a <span class="programkod">Map</span>
    interface-en keresztül hivatkozunk, további kódmódosítás nem is szükséges, hátradőlhetünk. Illetve várjunk még egy picit azzal a hátradőléssel:
  </p>
  <p class="bekezd">
    <b>Referenciasorok</b>
  </p>
  <p class="bekezd">
    Ha egy <span class="programkod">WeakReference</span> objektum többé nem hivatkozik semmire, mert azt már megette a szemétgyűjtő, akkor igazából ő maga is fölöslegessé válik. A
    programunknak tehát időről időre érdemes kitakarítania a nem funkcionáló gyenge referenciákat. (Tehát erős referenciák helyett most már gyenge referenciákat kell ugyanúgy
    piszkálgatunk...) Egy <span class="programkod">WeakHashMap</span>-nek például el kell távolítania a haszontalan bejegyzéseket, hogy ne csak egy folyamatosan növekvő, halott <span
      class="programkod">WeakReference</span>-ekből álló collection-né váljon. Ehhez nyújt segítséget a referenciasor (<span class="programkod">ReferenceQueue</span>). A <span
      class="programkod">ReferenceQueue</span> osztály könnyűvé teszi ezeknek a halott referenciáknak a nyomon követését. Ha egy gyenge referenciának a konstruktorában megadunk egy
    referenciasort akkor miután az általa hivatkozott objektum elérhetetlenné válik, bekerül a sorba. Ezután bizonyos időközönként fel tudjuk dolgozni a <span class="programkod">ReferenceQueue</span>-t
    és el tudunk végezni mindenféle tisztogatást, ami a halott referenciákhoz szükséges. Igazából a <span class="programkod">WeakHashMap</span> is úgy működik, hogy a kulcs elérése előtt
    leellenőrzi a saját <span class="programkod">ReferenceQueue</span>-jában, hogy nincs-e érvénytelen gyenge referencia, és ha van akkor eltávolítja azt.
  </p>
  <p class="bekezd">
    <b>A gyengeség fokai</b>
  </p>
  <p class="bekezd">Egészen eddig csak erős és gyenge referenciákkal foglalkoztunk, pedig a referencia erősségnek négy különféle fokát különböztetjük meg (sorban a legerősebbtől a
    leggyengébbig): erős, puha, gyenge és fantom. Nézzük a fennmaradt kettőt!</p>
  <p class="bekezd">
    <b>Puha referencia (soft reference)</b>
  </p>
  <p class="bekezd">
    Egy puha referencia ugyanolyan, mint egy gyenge, viszont kevésbé lelkesen dobja el a hivatkozott objektumot. Egy objektum, ami csak gyengén elérhető (a regerősebb rá hivatkozó
    referencia <span class="programkod">WeakReference</span>) a következő szemétgyűjtésnél el lesz dobva, de egy objektum, ami puhán elérhető (softly reachable, vagyis <span
      class="programkod">SoftReference</span> hivatkozik rá legerősebben) általában még velünk lehet egy darabig. A <span class="programkod">SoftReference</span>-ek nem szükségképpen
    viselkednek máshogy, mint a <span class="programkod">WeakReference</span>-ek, de a gyakorlatban a puhán elérhető objektumokat általában addig tartja meg a GC, amíg a memóriából
    bőségében vagyunk. Ezen tulajdonságuk miatt szokták ezeket gyorsítótárak építéséhez választani. Ilyen például a fent említett kép-cache, ahol így a szemétgyűjtőre tudjuk hagyni, hogy
    foglalkozzon vele, mennyire elérhetőek az objektumok. A <span class="programkod">get()</span> metódus puha és a gyenge referencia esetében is null értéket ad vissza, ha az objektum be
    lett gyűjtve és már nem létezik.
  </p>
  <p class="bekezd">
    Egyébként van egy parancssori kapcsoló is, amivel szabályozhatjuk a GC viselkedését a puha referenciákkal kapcsolatban: <span class="programkod">-XX:SoftRefLRUPolicyMSPerMB=N</span>. Ez
    beállítja azt az N időtartamot (ezredmásodpercben), amíg egy puha referenciát a GC életben tart az utolsó rá való hivatkozás óta. Alapértelmezett értéke egy másodperc a heap szabad
    megabájtjainként. (Vagyis akkor pucolódik ki, ha életkora átlépte a <span class="programkod">SzabadMéretMBokban * SoftRefLRUPolicyMSPerMB</span> értéket.) Ez a paraméter a JVM típusától
    függően kissé eltérően értelmezett:
  </p>
  <ul>
    <li>kliens VM esetén a szabad MB-ok számra az aktuális heap méret szerint értelmezett</li>
    <li>szerver VM esetén a szabad MB-ok száma a maximális lehetséges heap méret szerint értelmezett</li>
  </ul>
  <p class="bekezd">
    A különbség miatt a kliens VM a heap méretének növelése helyett inkább a puha referenciák kidobását részesíti előnyben, míg a szerver VM inkább növeli a heap méretét (ha tudja) és csak
    utána dobálja ki a referenciákat (vagyis az általuk hivatkozott objektumokat). Ez utóbbi esetben a <span class="programkod">-Xmx</span> paraméternek jelentős hatása van arra, milyen
    gyorsan szemétgyűjtőződnek a puha referenciák.
  </p>
  <p class="bekezd">Bár kézenfekvő a puha referenciának gyorsítótárként való használata, azért érdemes ezt adott esetben átgondolni, mert nem árt, ha a cache elemeinek élettartamát nem
    bízzuk a véletlenre és nem hagyjuk, hogy a cache az elérhető memória határáig tudjon terjeszkedni. Célszerűbb valamilyen jól megválasztott gyorsítótárazási stratégiát választani.</p>
  <p class="bekezd">
    Az erős referenciát használó példában egy <span class="programkod">java.lang.OutOfMemoryError: Java heap space</span> kivételt kapunk. Azzal futásidőben már nem nagyon fogunk tudni mit
    kezdeni, a hajunkra kenhetjük. Ha viszont puha referenciát használunk, akkor még lesz lehetőségünk kezelni a helyzetet. Az alábbi példában a bigStore erős referenciát egy puha
    referencián keresztül érjük el.
  </p>
  <pre class="programkod">    <span class="java_keyword">import</span> java.util.LinkedList;
    <span class="java_keyword">import</span> java.util.List;
    <span class="java_keyword">import</span> java.lang.ref.SoftReference;

    <span class="java_keyword">public</span> <span class="java_keyword">class</span> SoftTest {

    <span class="java_keyword">private</span> <span class="java_keyword">byte</span>[] getData() {
        <span class="java_keyword">return</span> <span class="java_keyword">new</span> <span class="java_keyword">byte</span>[1024*1024];
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> testSoftReferences() <span class="java_keyword">throws</span> Exception {
        SoftReference&lt;List&lt;<span class="java_keyword">byte</span>[]&gt;&gt; sRef = <span class="java_keyword">new</span> SoftReference&lt;List&lt;<span class="java_keyword">byte</span>[]>>(<span
      class="java_keyword">new</span> LinkedList&lt;<span class="java_keyword">byte</span>[]&gt;());
        <span class="java_keyword">byte</span>[] data;
        List&lt;<span class="java_keyword">byte</span>[]&gt; bigStore;
        <span class="java_keyword">while</span>(true){
            data=getData();
            bigStore=sRef.get();
            <span class="java_keyword">if</span>(bigStore==<span class="java_keyword">null</span>){
                <span class="java_keyword">return</span>; <span class="java_comment">// így jártál, nincs elég heap</span>
            }<span class="java_keyword">else</span>{
                bigStore.add(data);
            }
            bigStore=<span class="java_keyword">null</span>;
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Exception {
        <span class="java_keyword">new</span> SoftTest().testSoftReferences();
    }
}</pre>
  <p class="bekezd">A puha referencia használatához két dolgot kellett átalakítani:</p>
  <ol>
    <li>null-ozni kell a bigStore változót, mert a GC csak akkor tudja kidobni a láncolt listát, ha arra nem hivatkozik erős referencia</li>
    <li>az új adat bekérését át kell mozgatni oda, ahol a bigStore értéke null</li>
  </ol>
  <p class="bekezd">Így már nem jelentkezik OutOfMemoryError. A puha referencia megmondja, hogy mit lehet kidobni és csak azt kell nézni, hogy megkapjuk-e a listát. Ha nem, akkor
    esetleg megjeleníthetünk egy hibaüzenetet, hogy most épp nincs elég memória, próbáld máskor.</p>
  <p class="bekezd">
    <b>Fantom referenciák</b>
  </p>
  <p class="bekezd">
    A fantom referencia eltér az előzőektől. A saját objektumával annyira gyenge már a kapcsolata, hogy azt vissza se tudjuk szerezni: a <span class="programkod">get()</span> metódus mindig
    null-t ad. A <span class="programkod">PhantomReference</span> arra való, hogy a programunk értesítést kapjon arról, amikor egy objektumot a rendszer begyűjt. A fantom referencia
    konstruktorában kötelezően meg kell adni egy referencia sort. A különbség a <span class="programkod">WeakReference</span>-hez képest az, hogy mikor történik meg a sorbaállítás. A <span
      class="programkod">WeakReference</span>-ek sorba állítódnak, amint az objektum, amire mutatnak gyengén elérhető lesz. Ez még a finalize vagy szemétgyűjtés előtt van, elméletben
    ilyenkor az objektum még akár fel is éleszthető egy nem szokványos <span class="programkod">finalize()</span> metódussal (bár ekkor a <span class="programkod">WeakReference</span> már
    halott maradna). A <span class="programkod">PhantomReference</span>-ek pont azelőtt kerülnek be a sorba, mielőtt az objektum fizikailag is kikerül a memóriából. A <span
      class="programkod">get()</span> metódus azért ad vissza mindig null-t, hogy meggátolja, hogy véletlenül újra lehessen éleszteni egy majdnem halott objektumot.
  </p>
  <p class="bekezd">
    Mire jó a fantom referencia? Egyrészt lehetővé teszi, hogy pontosan megfigyeljük, egy objektum mikor törlődik a memóriából. Sőt, valójában erre ez az egyetlen mód. Ez néhány nagyon
    specifikus esetben jöhet jól, mint például nagy képek manipulálásánál: ha biztosan tudjuk, hogy egy képet szemétgyűjtőzni kell, akkor meg tudjuk várni, míg az ténylegesen megtörténik,
    mielőtt megpróbálnánk betölteni a következő képet és így kevésbé valószínű, hogy OutOfMemory hibát kapunk. Másodszor pedig a <span class="programkod">PhantomReference</span>
    használatával elkerülhető egy alapvető hiba a <span class="programkod">finalize()</span> metódusnál: a <span class="programkod">finalize()</span> metódusban ugyanis újra fel lehet
    támasztani az objektumokat azzal, hogy új erős referenciát hozunk létre rájuk. A <span class="programkod">PhantomReference</span> használatával ez lehetetlen - amikor egy
    PhantomReference bekerül a sorba, már végképp nincs lehetőség rá, hogy mutatót kapjunk a már halott objektumra.
  </p>
  <pre class="programkod">
<span class="java_keyword">import</span> java.lang.ref.PhantomReference;
<span class="java_keyword">import</span> java.lang.ref.Reference;
<span class="java_keyword">import</span> java.lang.ref.ReferenceQueue;
<span class="java_keyword">import</span> java.util.HashSet;
<span class="java_keyword">import</span> java.util.Set;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> PhantomTest {

    <span class="java_keyword">private</span> <span class="java_keyword">byte</span>[] getData() {
        <span class="java_keyword">return</span> <span class="java_keyword">new</span> <span class="java_keyword">byte</span>[1024 * 1024];
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> testPhantomReferences() {
        Set&lt;PhantomReference&lt;<span class="java_keyword">byte</span>[]&gt;&gt; references = <span class="java_keyword">new</span> HashSet&lt;PhantomReference&lt;<span
      class="java_keyword">byte</span>[]&gt;&gt;();
        ReferenceQueue&lt;<span class="java_keyword">byte</span>[]&gt; queue = <span class="java_keyword">new</span> ReferenceQueue&lt;<span class="java_keyword">byte</span>[]&gt;();
        Reference&lt;? <span class="java_keyword">extends</span> <span class="java_keyword">byte</span>[]&gt; tmp;
        <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0; i &lt; 10000; i++) {
            PhantomReference&lt;<span class="java_keyword">byte</span>[]&gt; pRef = <span class="java_keyword">new</span> PhantomReference&lt;<span class="java_keyword">byte</span>[]&gt;(getData(), queue);
            System.out.println(i + <span class="java_string">". ref created: "</span> + pRef);
            references.add(pRef);
            <span class="java_keyword">while</span> ((tmp = queue.poll()) != <span class="java_keyword">null</span>) {
                System.out.println(<span class="java_string">"Ref collected: "</span> + tmp);
                references.remove(tmp);
            }
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">new</span> PhantomTest().testPhantomReferences();
    }

}</pre>
  <h2>A szemétgyűjtés alapjai</h2>
  <p class="bekezd">A rendkívül hosszúra nyúlt bevezetés után most már végre lássuk, miről is szól a GC! Először el kell oszlatni egy alapvető félreértést: sokan azt hiszik, a
    szemétgyűjtés összeszedi és megsemmisíti a szükségtelen objektumokat. Valójában ennek pont az ellenkezőjét teszi! A még élő objektumokat követi figyelemmel és minden mást szemétnek
    minősít. Ahogy majd látni fogjuk, ez az alapvető félreértés sok teljesítménybeli problémához tud vezetni.</p>
  <p class="bekezd">A szemétgyűjtő az ún. heap memórián működik. A legtöbb esetben az operációs rendszer előre lefoglalja a heap-et, hogy majd a továbbiakban azt a JVM kezelje, míg a
    program fut. Ennek két fontos következménye van:</p>
  <ul>
    <li>az objektumlétrehozás gyorsabb, mert szükségtelen az operációs rendszerrel történő globális szinkronizálás minden egyes objektum esetén. Egy foglalás egyszerűen leharap egy kis
      részletet a memóriatömbből és az offszet mutató előre állítja. A következő foglalás ezen az offszeten kezdődik és a tömb következő részét harapja le.</li>
    <li>amikor egy objektumra többé nincs szükség, akkor a szemétgyűjtő visszaveszi a hozzá tartozó memóriát és újrafelhasználja a jövőbeli objektumfoglalásokhoz. Ez azt jelenti, hogy
      nincs explicit törlés és az operációs rendszernek nincs memória visszaadva.</li>
  </ul>
  <p class="bekezd">Minden objektum a heap területen jön létre. Minden, a fejlesztő által használt entitás itt kezelődik, beleértve az osztály objektumokat, statikus változókat és még
    magát a kódot is. A JVM élőnek tart egy objektumot, amíg van rá hivatkozás, vagyis az alkalmazás kódja által valamilyen objektum-láncolaton (úton) keresztül elérhető. Amikor egy
    objektumra már nincs többé hivatkozás, vagyis az alkalmazás kódja által nem elérhető, a szemétgyűjtő eltávolítja azt és újrahasznosítja a hozzá lefoglalt memóriát. Olyan egyszerű,
    ahogyan hangzik, de felmerül egy kérdés: mi az első referencia azon az úton, ahol elérhető az objektum?</p>
  <p class="bekezd">
    <b>Szemétgyűjtési gyökerek</b>
  </p>
  <p class="bekezd">Minden objektum objektumok fastruktúra-szerű szerkezetén át érhető el. Minden objektumfának lennie kell egy vagy több gyökérobjektumának. Amíg az alkalmazás eléri
    ezeket a gyökereket, az egész fa elérhető. De mikor tekinthetőek ezek a gyökérobjektumok elérhetőnek? A speciális, szemétgyűjtő-gyökérelemeknek nevezett adatszerkezetek (GC root) mindig
    elérhetőek és így minden olyan objektum is, aminek van szemétgyűjtő gyökéreleme a saját gyökerénél.</p>
  <p class="kozep">
    <img alt="gcroot" src="/informatika/essze/garbage/gcroot.jpg" />
  </p>
  <p class="bekezd">Java esetén négyféle GC gyökérelem van:</p>
  <ul>
    <li>a helyi változókat egy szál verme tarja meg élőnek. Ez nem valódi objektum referencia és ezért nem látható máshonnan. A lokális változók definíció szerint GC gyökérelemek.</li>
    <li>az aktív Java szálak mindig élő objektumoknak tekintendőek, ezek tehát GC gyökérelemek. Ez a tény különösen fontos a thread local változók esetén.</li>
    <li>a statikus változókra azok osztálya hivatkozik. Ez a tény de facto GC gyökérelemekké teszi ezeket. Ugyanakkor maguk az osztályok is szerepelnek a szemétgyűjtésben, ez pedig
      eltávolíthatja az összes hivatkozott statikus változót. Ennek külön jelentősége van amikor alkalmazásszervereket, OSGi konténereket vagy általános osztálybetöltőket használunk.</li>
    <li>a JNI referenciák Java objektumok, amiket natív kód hozott létre egy JNI hívás részeként. Az így létrehozott objektumok külön bánásmódot igényelnek, hiszen a JVM nem tud róla,
      hogy ezekre van-e hivatkozás natív kódból vagy sem. Ezek az elemek a GC gyökérelem eléggé speciális típusát jelentik.</li>
  </ul>
  <p class="bekezd">Egy egyszerű Java alkalmazásnak tehát a következő GC gyökérelemei vannak:</p>
  <ul>
    <li>lokális változók a fő metódusban</li>
    <li>a fő szál</li>
    <li>a fő osztály statikus változói</li>
  </ul>
  <p class="bekezd">
    <b>Szemét összeszedése és kisöprése</b>
  </p>
  <p class="bekezd">A szemétgyűjtést a fejlesztés megkönnyítése mellett arra tervezték, hogy megszüntesse a klasszikus memóriaszivárgás okát: a memóriában lévő elérhetetlen, de még nem
    törölt objektumokat. Viszont ez csak a memóriaszivárgás szokványos formájára érvényes. Simán lehetséges ugyanis, hogy vannak olyan nem használt objektumaink, amiket továbbra is
    elérhetne az alkalmazás, mert a fejlesztő elfelejtette ezeket elérhetetlenné tenni. Ezekkel pedig a szemétgyűjtő sem tud mit kezdeni. Sőt, az ilyen logikai memóriaszivárgásokat
    szoftverrel sem lehet felderíteni. Még a legjobb szoftverek is csak megjelölhetnek gyanús objektumokat. (Tegyük fel például, hogy egy dinamikus méretű listát tömbbel ábrázolunk. Mivel a
    tömb mérete fix, egy változó jelzi, hogy a tömb épp hány elemét használjuk ki. Ha töröljük az utolsó elemet, a működés szempontjából elegendő csak ennek a változónak az értékét
    csökkenteni. Ekkor azonban a tömb változón felüli eleme még megtartja a referenciát az adott objektumra, ezért a szemétgyűjtő nem tudja kidobni. Emiatt kell null-ra állítanunk a tömb
    megfelelő elemét.)</p>
  <p class="kozep">
    <img alt="gcroot2" src="/informatika/essze/garbage/gcroot2.jpg" />
  </p>
  <p class="bekezd">Ahhoz, hogy meghatározza, mely objektumok nincsenek használatban, a GC egy ún. megjelöl- és takarít (mark-and-sweep) algoritmust futtat. Ez egy egyszerű, kétlépéses
    folyamat:</p>
  <ol>
    <li>az algoritmus bejárja az összes objektumreferenciát a GC gyökerektől kezdődően és minden megtalált objektumot élőnek jelöl (mark)</li>
    <li>az összes olyan heap memória, ahol nincs megjelölt objektum, újrafelhasználható. Ez egyszerűen szabadnak lesz jelölve, innen kitakaríthatók a nem használt objektumok.</li>
  </ol>
  <h3>A szemétgyűjtés hatása az alkalmazások teljesítményére</h3>
  <p class="bekezd">Kiderült tehát, hogy a szemétgyűjtő teljesítményét valójában nem a halott, hanem az élő objektumok száma határozza meg. Minél több objektum hal meg, annál gyorsabb
    lesz a szemétgyűjtés. Ha a heap-en minden objektum kisöpörhető lenne, akkor a GC szinte azonnal lefutna. Ráadásul a szemétgyűjtőnek fel kell függesztenie a teljes alkalmazás futását,
    hogy az objektumfák integritását biztosítani tudja. Minél több élő objektumot talált, annál hosszabb ideig tart a felfüggesztés, aminek közvetett hatása van a válaszidőre és az
    áteresztőképességre. Ez a szemétgyűjtés alapvető tétele. Sok szálból álló alkalmazások esetén ez gyorsan skálázódási problémákhoz vezethet. A szemétgyűjtők működésével kapcsolatos
    alapvető fogalmak:</p>
  <ul>
    <li>GC pluszmunka (overhead): mennyi időt tölt a CPU a GC futtatásával az összes időhöz képest</li>
    <li>állási idő (pause time): mennyi ideig áll az alkalmazás, míg a GC fut</li>
    <li>szeméggyűjtő mechanizmus futtatásának gyakorisága (frequency): milyen gyakran fut a GC</li>
    <li>memóriaigény (footprint): az alkalmazás memóriaigénye, pl. a heap mérete</li>
    <li>reakcióidő (promptness): az idő aközött, amikor az objektum begyűjthetővé válik, és aközött, hogy a memória újra felhasználhatóvá válik</li>
    <li>áteresztőképesség (throughput): mennyi munkát tud elvégezni az alkalmazás egy adott időtartam alatt. Az áteresztőképességre kritikus alkalmazások számára a nagyobb megállási
      idők elfogadhatóak. Mivel a nagy áteresztőképességű alkalmazásokra vonatkozó mérések hosszabb időre vonatkoznak, a gyors válaszidő nem szempont. Az áteresztőképesség tárgyalását
      például a következő módokon tehetjük:
      <ul>
        <li>adott idő alatt befejezett tranzakciók száma</li>
        <li>egy kötegelt program által egy óra alatt elvégzett feladatok száma</li>
        <li>egy óra alatt elvégzett adatbázislekérdezések száma</li>
      </ul>
    </li>
    <li>válaszidő (responsive): az alkalmazás milyen gyorsan képes reagálni egy kérésre. A válaszidő alacsonyan tartására fókuszáló alkalmazások esetén a nagy GC-megállási idők nem
      elfogadhatóak. Itt az a legfontosabb, hogy egy kérésre rövid idő alatt válaszoljunk. A válaszidő a következő esetekben kerülhet például képbe:
      <ul>
        <li>milyen gyorsan reagál egy desktop felület egy eseményre</li>
        <li>milyen gyorsan ad vissza egy webhely egy oldalt</li>
        <li>milyen gyorsan fut le egy adatbázislekérdezés</li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">Az alábbi ábra egy Oracle GC tuningolás cikkből való és jól szemlélteti a GC leállások teljesítménybeli hatását a CPU-k növekedésének arányában.</p>
  <p class="kozep">
    <img alt="gcthroughput" src="/informatika/essze/garbage/gcthroughput.png" />
  </p>
  <p class="bekezd">Az ábra szemlélteti a GC megállások hatását a többszálú alkalmazások áteresztőképességére. Az adatok ideális rendszerre vonatkoznak, ami a GC kivételével tökéletesen
    skálázható. A piros vonal egy olyan alkalmazás, ami idejének csak 1%-át tölti a GC-vel egy egyprocesszoros rendszerben. Ha ezt az alkalmazást 32 processzoros rendszerre visszük át,
    akkor már 80% alá esik az áteresztőképesség. Ha a GC időt 10%-ra növeljük (ami azért nem a világ vége egy egyprocesszoros rendszerben), az 32 processzoros rendszerben már csak 20%
    áteresztőképességet eredményez. Ekkora hatása van annak, hogy egyszerre 32 végrehajtószálat felfüggesztünk!</p>
  <p class="bekezd">A GC megállási idő csökkentésnek két általánosan használt módja van:</p>
  <ol>
    <li>a mark-sweep algoritmus megfelelő konfigurálása</li>
    <li>a megjelölendő objektumok számának csökkentése</li>
  </ol>
  <p class="bekezd">De mielőtt elmélyednénk a GC-stratégiákban és teljesítménynövelésekben, meg kell értenünk valamit a memóriatöredezettségről, ami szintén befolyásolja a megállási
    időt és az alkalmazás teljesítményét.</p>
  <h3>A memória töredezettsége</h3>
  <p class="bekezd">Új objektum létrehozásakor a JVM automatikusan lefoglal akkora memóriát a heap-en, ahová befér az új objektum. Az ismétlődő foglalás és felszabadítás viszont - minő
    rettenet - memóriatöredezettséghez vezet, ami hasonló a lemez töredezettségéhez és két problémát okoz:</p>
  <ul>
    <li>csökkent foglalási sebesség: a JVM a szabad memóriát blokkméret által szervezett listákban kezeli. Új objektum létrehozásához végigmegy a listán, hogy megtaláljon és
      lefoglaljon egy optimális méretű blokkot. A töredezettség lelassítja a foglalási folyamatot és így az alkalmazást.</li>
    <li>foglalási hibák: ezek akkor jönnek, amikor a töredezettség olyan naggyá válik, hogy a JVM már képtelen lefoglalni elegendően nagy blokknyi memóriát egy új objektumnak</li>
  </ul>
  <p class="bekezd">A JVM ezen problémáknál nem az operációs rendszerre támaszkodik, hanem saját maga próbálja megoldani. Mégpedig az ún. compaction végrehajtásával egy sikeres GC végén
    (alábbi ábra). Ez a folyamat eléggé hasonlít egy merevlemez töredezettségmentesítéséhez.</p>
  <p class="kozep">
    <img alt="compaction" src="/informatika/essze/garbage/compaction.jpg" />
  </p>
  <p class="bekezd">Amikor a heap az ismétlődő foglalások és szemétgyűjtések következtében töredezetté válik, a GC végrehajt egy tömörítési lépést: egyszerűen elmozgatja az összes élő
    objektumot a heap egyik végére. Ez szépen összeilleszti az objektumokat és eltünteti a lyukakat. Innentől kezdve az objektumok újból teljes sebességgel foglalhatók le és a nagy
    objektumok létrehozásakor jelentkező probléma ki van küszöbölve. Ennek az egész bulinak persze még hosszabb GC időtartam a hátulütője és mivel a legtöbb JVM, köztük a HotSpot is
    felfüggeszti az alkalmazás végrehajtását a tömörítés időtartamára, a teljesítménybeli hatása jelentős lehet.</p>
  <p class="bekezd">
    A legalapvetőbb szemétgyűjtési stratégia tehát a fentebb megismert három lépésből álló <b>mark-sweep-compact</b>:
  </p>
  <ul>
    <li>mark: megjelölés</li>
    <li>sweep: kitakarítás</li>
    <li>compact: tömörítés</li>
  </ul>
  <p class="bekezd">
    <b>A tömörítés negatív hatásának csökkentése</b>
  </p>
  <p class="bekezd">A modern szemétgyűjtők a tömörítési folyamatukat párhuzamosan hajtják végre, kihasználva ezzel több processzort. Viszont majdnem mind fel kell, hogy függessze az
    alkalmazás futtatását ezen folyamat során. A sok gigabájt memóriát használó JVM-ek akár több másodpercre megállíthatják a program futását. Ennek elkerülésére a JVM-ek bevezetnek
    paramétereket, amikkel meg lehet adni, hogy a memóriát kisebb, inkrementális lépésekben tömörítse egy nagy blokk helyett. Például:</p>
  <ul>
    <li>tömörítés ne menjen minden GC ciklusban, hanem csak akkor, ha bizonyos százaléknyi töredezettséget elért a memória (például a szabad memória több, mint 50%-a nem egybefüggő)</li>
    <li>be lehet konfigurálni céltöredezettséget. Minden tömörítése helyett a szemétgyűjtő csak addig tömörít, amíg a szabad terület egy adott százaléka érhető el egybefüggő
      területként.</li>
  </ul>
  <h3>Szemétgyűjtési idő csökkentése</h3>
  <p class="bekezd">Két általános módja van a szemétgyűjtéssel eltelt idő csökkentésének:</p>
  <ul>
    <li>a szemétgyűjtés is hasznosítani tud több processzort és futtatható párhuzamosan is. Bár az alkalmazás szálai ezalatt teljes mértékben fel vannak függesztve, a szemétgyűjtést
      mégis töredék idő alatt el lehet végezni, hatékonyan csökkentve ezzel a felfüggesztés idejét.</li>
    <li>egy másik megközelítés futni hagyja az alkalmazást és a szemétgyűjtést az alkalmazás végrehajtásával párhuzamosan végzi</li>
  </ul>
  <p class="bekezd">Ez a két logikai megoldás vezetett el a soros, párhuzamos és konkurens szemétgyűjtési stratégiák kifejlesztéséhez, amik az alapját jelentik minden Java szemétgyűjtő
    implementációnak. (A szemétgyűjtő mechanizmus megvalósítása egyébként nem a szabvány része, ezért különböző JVM-gyártók különbözőképpen implementálhatják azokat.) Fontos megjegyezni,
    hogy a párhuzamos fogalom nem ugyanaz, mint a konkurens. De ez az alábbi ábráról is leolvasható. A GC terminológiájában ez két teljesen különböző dolog: a párhuzamos magára a GC
    algoritmusra, a konkurens pedig a GC lefutására utal. (Megjegyzem, a cikkhez készült nyersanyagok fordítása közben eleinte én is szinonímaként használtam ezeket a szavakat, míg rá nem
    jöttem, hogy nem ugyanarról van szó.)</p>
  <p class="kozep">
    <img alt="colltypes" src="/informatika/essze/garbage/colltypes.jpg" />
  </p>
  <p class="bekezd">A különböző szemétgyűjtési algoritmusok közötti különbségek akkor lesznek a legtisztábbak, amikor a szemétgyűjtési megállásokat hasonlítjuk össze. A soros gyűjtő
    (serial collector) felfüggeszti az alkalmazást és a mark-sweep algoritmust egyetlen szálon futtatja. Ez a szemétgyűjtés legegyszerűbb és legrégibb formája. A párhuzamos gyűjtő (parallel
    collector) több szálat használ a munka elvégzéséhez, így több mag esetén csökkenhet a megállás ideje. A konkurens gyűjtő (concurrent collector) a munka nagy részét az alkalmazás
    futásával párhuzamosan (de ezután ezt már így hívjuk: konkurensen) végzi el és csak nagyon rövid időre kell felfüggesztenie annak a futását. Ez nagy előnyt jelent a válaszidőre, de a
    megoldás persze nem hátrányok nélküli.</p>
  <h2>Generációs ellentétek</h2>
  <p class="bekezd">A szemétgyűjtés teljes kiiktatása nélkül csak egy biztos módja van a szemétgyűjtés gyorsításának: biztosítani kell, hogy ennek során a lehető legkevesebb objektum
    legyen elérhető. Minél kevesebb az élő objektum, annál kevesebbet kell megjelölni. Ez a megfontolás állt a generációs heap bevezetése mögött.</p>
  <p class="bekezd">A generációs szemétgyűjtő előnye arra a megfigyelésre alapul, hogy a legtöbb program nagyon rövid életű objektumokat használ (legtöbbjüket átmeneti adattárolásra).
    Azzal, hogy elkülöníti az újonnan létrehozott objektumokat egyfajta objektum-bölcsödébe (angol terminológiában nursery), a generációs szemétgyűjtő több dolgot elér. Egyrészt mivel az új
    objektumok létrehozása itt folyamatosan, egyfajta verem-szerű módon történik, a memóriafoglalás rendkívül gyors lehet, mert egyszerűen csak annyiból áll, hogy egy mutatót meg kell
    növelni és egy ellenőrzést kell végezni arra vonatkozóan, hogy betelt-e a bölcsöde. Másodszor pedig amikor a bölcsöde túlcsordulása megtörtént, az ott lévő objektumok legtöbbje már
    eldobható, mert semmi nem használja. Ez pedig lehetővé teszi a szemétgyűjtőnek, hogy azt a néhány objektumot, ami megmaradt, átmozgassa máshová, a nem használt objektumoknál pedig
    semmiféle helyreállítási munkára nincs szükség.</p>
  <p class="bekezd">A JVM a heap területet két nagy részre osztja: egy young és egy old generációra, amelyeket különböző stratégiával lehet szemétgyűjtőzni.</p>
  <p class="bekezd">Az objektumokat a JVM általában a young területen hozza létre. Amikor az objektum már túlélt néhány GC ciklust, akkor átkerül az old generációba. (Néhány nagyon nagy
    objektum esetén lehet kivétel, de ezt majd később látni fogjuk.) Miután az alkalmazás befejezte a kezdeti indulási fázist (sok alkalmazás az indítás során foglal le gyorsítótárakat és
    egyéb állandóan használatos objektumokat), a legtöbb lefoglalt objektum nem éli túl első vagy második GC ciklusát. Az élő objektumok mennyisége, amiket minden egyes ciklusban figyelembe
    kell venni, stabil és relatív kis mennyiségű lehet.</p>
  <p class="bekezd">Az old generációban történő foglalások esetén az a jó, ha ritkák maradnak. Egy ideális világban ilyenek egyáltalán nem is történnek meg a kezdeti indítási fázis
    után. Ha az old generáció nem növekszik (nem nő túl a szabad területén), akkor ott egyáltalán nincs is szükség szemétgyűjtésre. Lesznek használatlan objektumok az old generációban, de
    amíg a memóriára nincs szükség, nincs ok arra, hogy újrahasznosítsuk az általuk foglalt területet.</p>
  <p class="bekezd">Ahhoz, hogy ez a generációs megközelítés működjön, a young generációnak elég nagynak kell lenni, hogy biztosítsa, hogy minden átmeneti objektum be is fejezi ott az
    életét. Mivel a legtöbb alkalmazásban az átmeneti objektumok száma az alkalmazás terheltségétől függ, a young generáció optimális mérete terhelésfüggő. A young generáció méretezése
    (generation-sizing) ezért az egyik legfontosabb teendő a csúcsteljesítmény eléréséhez. Sajnos néha nem lehetséges optimális állapotot elérni, hogy az összes objektum a young
    generációban múljon ki. Az old generációnak ezért gyakran konkurens szemétgyűjtésre van szüksége. A konkurens szemétgyűjtő egy minimálisan növekvő old generációval együtt biztosítja,
    hogy az elkerülhetetlen teljes megállási események nagyon rövid ideig tartanak és megjósolhatóak lesznek. (A teljes megállási esemény - stop-the-world event - nagyon fontos fogalom,
    érdemes megjegyezni. A konkurens szemétgyűjtési fázisokon kívül minden más GC ilyen stop the world event alatt fut. Ilyenkor a JVM teljes egészében felfüggeszti az alkalmazás futását: a
    GC-hez szükséges szálakon kívül minden más megáll. A teljes megállási esemény is egy safe point-ban következik be.)</p>
  <p class="bekezd">Viszont ha sok új objektum keletkezik a young generációban a GC ciklusok kezdetén, és a GC ciklusokat az objektumok csak egy kis része éli túl, az a szokványos GC
    stratégiákkal nagyfokú töredezettséghez vezet. Szabad listák használata jó opció lenne, ha nem lassítaná le az új objektumokhoz szükséges memóriafoglalásokat. Azt is megtehetnénk, hogy
    teljes tömörítést végzünk minden egyes alkalommal, de ennek meg rossz hatása van a megállási időre. A legtöbb JVM ezek helyett egy copy collection-nek nevezett stratégiát implementál a
    young generációban.</p>
  <p class="bekezd">
    <b>Amikor a másolás gyorsabb, mint a megjelölés</b>
  </p>
  <p class="bekezd">
    A <b>mark-copy</b> elvet alkalmazó szemétgyűjtő felosztja a heap-et két (vagy több) területre amelyek közül csak egyet használ új objektumok létrehozására. Amikor ez a terület betelik,
    minden élő objektumot átmásol a második területre, aztán az első területet egyszerűen üresnek nyilvánítja.
  </p>
  <p class="kozep">
    <img alt="markand" src="/informatika/essze/garbage/markand.jpg" />
  </p>
  <p class="kozep_kepalairas">Ahelyett, hogy kisöpörné a szemetet és tömörítené a heap-et, a mark-copy egyszerűen átmásolja az élő objektumokat valahová máshová, a régi területet pedig
    üresnek jelöli.</p>
  <p class="bekezd">Itt nincs töredezettség és ezért nincs szükség üres listára és tömörítésre sem. A foglalás mindig gyors, a GC algoritmus pedig egyszerű. Ez a stratégia viszont csak
    akkor hatékony, ha a legtöbb objektum a szemétgyűjtésig bevégzi az életét. Ha a young generáció túl kicsi, az objektumok idő előtt az old generációba kerülnek. Ha a young generáció túl
    nagy, akkor túl sok objektum marad élő és a GC ciklus túl sokáig fog tartani. A közhiedelemmel ellentétben ezek a young-generációs GC-k, amiket gyakran minor-GC-nek hívnak, tele vannak
    teljes leállási eseménnyel. Ezeknek még súlyosabb negatív hatása lehet a válaszidőre, mint az alkalmankénti old-generációs GC-nek.</p>
  <p class="bekezd">A generációs heap tehát nem ad minden szemétgyűjtési problémára megoldást. Az optimális konfiguráció sokszor egy kompromisszum egy megfelelően méretezett young
    generációval a hoszú minor GC-k elkerülésée és egy konkurens GC-vel az old generációban, hogy kezelni tudják a túl korán oda került objektumokat.</p>
  <div class="keretes">
    <h3>A generációmentes heap kérdése</h3>
    <p>Az Oracle HotSpot kizárólag generációs heap-et használ, azonban a JRockit nemgenerációs heap-et is támogatott, az IBM szerint pedig a generációs heap kis memóriánál úri
      huncutság, a WebSphere alapértelmezetten nemgenerációs heap-et használ és azt ajánlják, hogy 100 MB-nál kisebb heap esetén mindig nemgenerációs heap-et használjunk. Egy generációs GC
      és a hozzá kapcsolódó copy collection-nek van némi plusz erőforrásigénye CPU és memória terén, ezért ez reális meglátásnak tűnik.</p>
    <p>Ha egy alkalmazást áteresztőképességre optimalizáltak és az átmeneti objektumok száma kicsi, akkor egy nemgenerációs GC-nek is megvan a maga előnye. Egy teljes párhuzamos GC jobb
      kompromisszum CPU használatban, ha nem érdekel minket egyetlen tranzakció válaszideje. Másrészről ha az átmeneti objektumok száma relatív kicsi, egy konkurens GC egy nemgenerációs
      heap-en is megcsinálja a munkát kevesebb megállási idővel, mint egy generációs GC. De persze pontos választ mindig csak átfogó teljesítményteszt tud adni.</p>
  </div>
  <p class="bekezd">
    <b>A foglalási teljesítmény növelése</b>
  </p>
  <p class="bekezd">A foglalási sebességre (új objektumok létrehozása) két dolognak van negatív hatása: a töredezettség és a konkurencia. A töredezettségről már volt szó, a konkurencia
    problémája pedig ott kezdődik, hogy a JVM-ben minden szál megosztottan használja a memóriát és minden memóriafoglalást szinkronizálni kell. Amikor sok szál próbál meg párhuzamosan
    foglalni, a helyzet gyorsan eldurvulhat. A megoldás a thread-local foglalás.</p>
  <p class="bekezd">Ebben az esetben mindegyik szál kap egy kicsi, de kizárólagos memóriadarabot, ahol szinkronizálás nélkül tudnak objektumokat foglalni. Ez növeli a párhuzamosságot és
    az alkalmazásvégrehajtási sebességet. (Ez nem keverendő össze a thread-local változóknak szánt heap területtel!) Egy egyszerű thread-local heap (TLH) még akkor is kicsi lehet, amikor
    már sok szálhoz tartozik. A TLH nem egy speciális heap terület, hanem általában a young generáció része, ami persze okozhat problémákat. Egy generációs heap-nek TLH-val nagyobb young
    generációra van szüksége, mint TLH nélkül. Ugyanolyan számú objektum egyszerűen több helyet foglal. Másrészről egy nemgenerációs heap aktív TLH-val valószínűleg sokkal töredezettebbé
    válik és sokkal gyakoribb tömörítést igényel.</p>
  <h3>Forróponti generációk</h3>
  <p class="bekezd">Az Oracle Java 7 heap területének felosztása az alábbi módon néz ki. Mindegyik területhez megadom az annak méretét vezérlő paramétert is.</p>
  <p class="kozep">
    <img alt="java7heap" src="/informatika/essze/garbage/java7heap.jpg" />
  </p>
  <p class="bekezd">
    <b>Young generation</b>: minden új objektum itt kezdi az életét. Angol terminológiában &quot;nursery&quot;, vagyis bölcsöde néven is emlegetik. Ha ez a terület betelik, egy ún. minor GC
    indul el. Egy elérhetetlen objektumokkal teli young generation-t nagyon gyorsan GC-zni lehet a már fentebb leírt elvek miatt. A néhány túlélő objektum pedig folyamatosan öregszik, ahogy
    egyre több GC-t túlél és ezeket végül az old generation-be lehet mozgatni.
  </p>
  <p class="bekezd">
    <b>Old generation</b>: a hosszú ideig túlélő objektumok tárolási helye. Általában egy küszöbérték van beállítva a young generáció objektumainak és amikor ezt az életkort elérik,
    átkerülnek az old generation-be. Persze végül az old generation-t is szemétgyűjtőzni kell, ezt nevezik major GC-nek. Ez egy teljes megállási esemény. A major GC gyakran sokkal lassabb,
    mert az összes élő objektumon végigmegy, ezért a reszponzív alkalmazások esetén ezek előfordulását érdemes minél inkább lecsökkenteni. A teljes megállási esemény időtartama attól is
    függ, milyen szemétgyűjtési stratégiát választottunk az old generation-höz.
  </p>
  <p class="bekezd">
    <b>Permanent Generation</b>: a JVM által igényelt metaadatokat tartalmazza, amelyek leírják az alkalmazás által használt osztályokat és metódusokat. Ezt a JVM tölti fel futásidőben. A
    Java SE osztálykönyvtárak is itt foglalnak helyet. A permanent generation arra rendeltetett, hogy hatékonyabbá tegye a szemétgyűjtési folyamatot. A futás legnagyobb részében ezek az
    objektumok állandóan bent kell, hogy legyenek (permanent) a memóriában és nem kell hozzájuk szemétgyűjtés. A HotSpot képes azáltal növelni a szemétgyűjtés teljesítményét, hogy az
    osztály objektumokat és konstansokat a permanent generációra teszi, így figyelmen kívül lehet hagyni ezeket a szokványos GC ciklusokban. Az alkalmazásszerverek, OSGi konténerek és
    dinamikusasn generált kód elterjedésével viszont megváltoztak a játékszabályok és a valaha állandónak tekintett objektumok már egyáltalán nem olyan állandóak. A permanent generation nem
    ezek figyelembe vételével lett megtervezve. A mai alkalmazásszerverek nagy mennyiségű osztályt, gyakran 100000-nél is többet tudnak betölteni, ami nagyon gyosan ki tud akasztani
    bármilyen konfigurációt. Az osztályokat mindenesetre ki lehet innen dobálni, ha a JVM úgy találja, hogy többé már nincs szükség rájuk, az általuk elfoglalt helyre viszont igen. A teljes
    GC ezért a permanent generation-t is magába foglalja. (Java 7 előtt a HotSpot-nál az interned sztringek is itt tárolódtak, azonban a 7-es Java-ban azok már átkerültek a young és old
    generációba.)
  </p>
  <p class="bekezd">
    <b>Virtuális területek</b>: a JVM memóriaméretének konfigurálásakor minden fő területre be lehet állítani kezdeti értéket (ennyi memóriát foglal le induláskor a JVM) és maximális
    értéket (ennyi memóriát foglal le legfeljebb a JVM). A kettő közötti különbség a virtuális, vagyis a pillanatnyilag kihasználatlan terület.
  </p>
  <div class="keretes">
    <h3>Sztring internalizálás</h3>
    <p>
      A sztring internalizálás biztosítja, hogy a különböző sztring literálokból és sztring értékű konstansokból mindig csak egy példány tárolódjon a memóriában. Tehát ha van egy listánk,
      amiben a &quot;kisvakond&quot; ezerszer szerepel, az internalizálással ez a sztring valójában csak egyszer fog a memóriában tárolódni. A<span class="programkod"> String</span>
      osztálynak van egy publikus <span class="programkod">intern()</span> metódusa, ami visszaadja a sztring objektum kanonikus reprezentációját. A <span class="programkod">String</span>
      osztály belsőleg tartalmaz egy sztring pool-t, amiben a sztring literálok automatikusan internalizálódnak. Az <span class="programkod">intern()</span> metódus megnézi, hogy az a
      sztring benne van-e a pool-ban. Ha igen, akkor visszaadja annak a referenciáját, egyébként pedig hozzáadja az új sztringet, majd visszaadja az új referenciát.
    </p>
    <p>
      Az <span class="programkod">intern()</span> metódus segít két sztring objektum összehasonlításában az<span class="programkod"> ==</span> operátorral úgy, hogy a már létező sztring
      literálok pool-ját használja. Ez pedig gyorsabb, mint az <span class="programkod">equals()</span> metódus. A pool tehát egyrészt tárhely-megtakarítást, másrészt pedig teljesítménybeli
      javulást jelent. (A Java programozóknak általában azt tanácsolják, hogy az <span class="programkod">equals()</span> metódust használják <span class="programkod">==</span> helyett két
      sztring azonosságának vizsgálatára, hiszen az <span class="programkod">==</span> referenciákat, az <span class="programkod">equals()</span> pedig a tartalmat vizsgálja.)
    </p>
    <p>
      A Java minden sztringet alapértelmezetten internalizál, tehát expliciten csak akkor kell használni az <span class="programkod">intern()</span> metódust, amikor nem konstansról van
      szó. Íme egy példa:
    </p>
    <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InternTest {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        String s1 = <span class="java_string">"Test"</span>;
        String s2 = <span class="java_string">"Test"</span>;
        String s3 = <span class="java_keyword">new</span> String(<span class="java_string">"Test"</span>);
        String s4 = s3.intern();
        System.out.println(s1 == s2);
        System.out.println(s2 == s3);
        System.out.println(s3 == s4);
        System.out.println(s1 == s3);
        System.out.println(s1 == s4);
        System.out.println(s1.equals(s2));
        System.out.println(s2.equals(s3));
        System.out.println(s3.equals(s4));
        System.out.println(s1.equals(s4));
        System.out.println(s1.equals(s3));
    }
}</pre>
    <p>A program kimenete:</p>
    <pre class="programkod">true
false
false
false
true
true
true
true
true
true
</pre>
  </div>
  <p class="bekezd">A fentiekben már szó esett a különböző területeken lefutó szemétgyűjtőkről, ezek összefoglalva:</p>
  <ul>
    <li><b>minor GC</b>: a young generáción futó szemétgyűjtés</li>
    <li><b>major GC</b>: az old generáción futó szemétgyűjtés</li>
    <li><b>full GC</b>: mindkét generáción és a permanens generáción is futó szemétgyűjtés</li>
  </ul>
  <p class="bekezd">A munka mennyiségéből adódóan a full GC tart a legtovább, hiszen a teljes heap-et szemétgyűjtőzi, ráadásul ezt egy teljes megállási esemény alatt csinálja, vagyis az
    alkalmazás elég hosszú ideig felfüggesztésre kerül, ami akár több másodperc, sőt extrém esetben perc is lehet.</p>
  <p class="bekezd">
    A heap méretének beállítására a következő JVM paraméterek alkalmasak (az X-szel kezdődő JVM paraméterek egyébként nem szabványosnak számítanak és nem is minden VM esetén támogatottak,
    de a HotSpot-ban azért bízvást építhetünk rájuk. A paraméterekről bővebben <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html">itt lehet</a> olvasni):
  </p>
  <ul>
    <li><b>-Xmx</b>: a heap maximális mérete (pl.: -Xmx4G)</li>
    <li><b>-Xms</b>: a heap kezdeti mérete (pl.: -Xmx2G)</li>
    <li><b>-Xmn</b>: a young generáció mérete (pl.: -Xmx512M)</li>
    <li><b>-XX:NewSize=<i>N</i></b>: ugyanaz, mint a -Xmn (pl.: -XX:NewSize=512M)</li>
    <li><b>-XX:SurvivorRatio=<i>N</i></b>: a survivor területek mérete. A -XX:SurvivorRatio=6 például egy survivor és az éden arányát 1:6-ra állítja be, így egy survivor terület mérete
      a young generáció méretének 1/8-a lesz. Ha a survivor területek túl kicsik, akkor a copy collection átnyúlik közvetlenül az old generációba.</li>
    <li><b>-XX:MaxPermSize=<i>N</i></b>: a permanent generation maximális mérete (pl.: -XX:MaxPermSize=128M)</li>
    <li><b>-XX:PermSize=<i>N</i></b>: a permanent generation kezdeti mérete (pl.: -XX:PermSize=64M)</li>
    <li><b>-XX:NewRatio=<i>N</i></b>: a young és old generáció méretarányának beállítása. Alapértelmezettt értéke 2.</li>
  </ul>
  <p class="bekezd">
    <b><i>Meta</i>fizika</b>
  </p>
  <p class="bekezd">
    A Java 8 HotSpot virtuális gépének fejlesztésekor az egyik fő szempont az volt, hogy a permanens generációt megszüntessék. Ez a terület alkalmazásszerverek esetén a folyamatos
    alkalmazás-újradeployolások során hajlandó volt betelni (részben a rosszul megírt osztálybetöltők miatt), és <span class="programkod">java.lang.OutOfMemoryError: PermGen space</span>
    kivétellel megörvendeztetni a fejlesztőt. Optimális méretét pedig nehéz volt előre meghatározni, valamint a hírek szerint néhány GC-t érintő fejlesztést sem lehetett volna a permanens
    generációval megcsinálni.
  </p>
  <p class="bekezd">
    Az osztályok metaadatai a Java 8-tólkezdődően ezért immár nem a heap-en, hanem a natív memóriában helyezkednek el egy <b>metaspace</b> nevű területen. A heap-ről eltűntek az osztály
    metaadatokat definiáló adatszerkezetek is (klassKlass). A metaspace mérete alapesetben nem konfigurált, vagyis dinamikusan, az igényeknek megfelelően képes nőni. Az osztály metaadatok
    mennyiségét tehát immár csak a memória mérete korlátozza, bár adott esetben természetesen a metaspace méretét is lehet korlátozni. Egyes adatok a megszűnt permanens generációból a
    heap-re kerültek, így néhány alkalmazás esetén nagyobb heap-pel kell számolni Java 8 esetén a 7-hez képest. A metaspace is GC-zett terület: a betelése váltja ki a GC-t. A heap egyébként
    a young és old generációk tekintetében változatlan maradt a Java 7-hez képest.
  </p>
  <p class="bekezd">Jól hangzik a permgen kivezetése, de azért vegyük észre, hogy az osztálybetöltő memóriaszivárgását a metaspace sem oldotta meg, sőt az alapértelmezetten nem
    korlátozott mérete miatt a hatását kitolta, vagyis még nehezebb észrevenni. Amikor nagyon intenzív metaspace GC-zést tapasztalunk, akkor sejthető, hogy ilyen hiba áll a háttérben.</p>
  <p class="bekezd">
    Java 8-cal a paraméterek közül a permanent generáció méretezése kikerült, de bejött a metaspace opcionális méretezése. (A Java 8 paraméterekről bővebben <a
      onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html">itt lehet</a> olvasni)
  </p>
  <ul>
    <li><b>-XX:MaxMetaspaceSize</b>: a metaspace memóriaterület maximális mérete, alapértelmezésben ez nem korlátozott, csak az operációs rendszer által kiosztható memória mérete szab
      határt.</li>
  </ul>
  <p class="bekezd">
    <b>Metafizikai telítettség</b>
  </p>
  <p class="bekezd">
    Hogy a metaspace is milyen gyönyörűen be tud telni, annak pedig az alábbiakban állítok bizonyítékot. (A példát <a onclick="window.open(this.href);return false;"
      href="https://dzone.com/articles/java-8-permgen-metaspace">ezen az oldalon</a> található mintából írtam át.) Jó persze a példa itt speciálisan konstruált, hogy lássuk a lényeget, de
    valódi alkalmazások is tudnak hasonlóan viselkedni.
  </p>
  <p class="bekezd">A példához már tudjuk, hogy a Java-ban minden objektum hivatkozik a saját osztályára, emellett pedig azt is tudni kell, hogy minden osztály hivatkozik a saját
    osztálybetöltőjére (classloader), ami szintén egy osztály. Mivel az osztálydefiníciók a permanens generációban vagy a metaspace-en helyezkednek el, valahogyan el kell érni, hogy elég
    sok osztálydefiníciónk legyen.</p>
  <p class="bekezd">Ehhez a Java dinamikus proxy nevű komponensét fogjuk felhasználni.</p>
  <div class="keretes">
    <h3>Dinamikus proxy-k</h3>
    <p>
      A Java reflection keretrendszernek van egy olyan lehetősége, amivel futásidőben dinamikusan lehet interfészeket implementálni. Ezt dinamikus proxynak hívják és a <span
        class="programkod">java.lang.reflect.Proxy </span>osztály segíti benne az egyszeri programozót.
    </p>
    <p>
      <b>Proxy-k létrehozása</b>
    </p>
    <p>
      Dinamikus proxykat a <span class="programkod">Proxy.newProxyInstance()</span> metódussal lehet varázsolni. Ez három paramétert vár:
    </p>
    <ol>
      <li>egy <span class="programkod">ClassLoader</span>, ami &quot;betölti&quot; a dinamikus proxy osztályt. Erre azért van szükség, mert egy osztály nem lóghat csak úgy a levegőben,
        minden Java osztálynak kell, hogy legyen osztálybetöltője.
      </li>
      <li>egy interfésztömb, amiket a proxy implementálni szeretne</li>
      <li>egy <span class="programkod">InvocationHandler</span>, aminek a proxy-n lévő minden hívás továbbítódik
      </li>
    </ol>
    <p>Íme egy példa:</p>
    <pre class="programkod">InterfaceA tmp = (InterfaceA) Proxy.newProxyInstance(classLoader,
    <span class="java_keyword">new</span> Class&lt;?&gt;[] { InterfaceA.<span class="java_keyword">class</span> }, 
    <span class="java_keyword">new</span> InterfaceAInvocationHandler(<span class="java_keyword">new</span> InterfaceAImpl()));</pre>
    <p>
      (Az <span class="programkod">InvocationHandler</span>-nek nem kötelező implementációt átadni, de a példánkban használni fogjuk.)
    </p>
    <p>
      Miután a kód lefut, a tmp változóban ott lesz az <span class="programkod">InterfaceA</span> dinamikus implementációjának referenciája. Metódushívások pedig továbbítódnak a <span
        class="programkod">new InterfaceAInvocationHandler()</span> példánynak, ami az <span class="programkod">InvocationHandler</span> egy implementációja. Ez az interfész a következőképp
      néz ki:
    </p>
    <pre class="programkod">
<span class="java_keyword">public interface</span> InvocationHandler{
    Object invoke(Object proxy, Method method, Object[] args)
    <span class="java_keyword">throws</span> Throwable;
}</pre>
    <p>
      Az implementációban az <span class="programkod">invoke</span> metódus csinálja a dinamikus megvalósítást. Az átadott proxy paraméter az interfészt megvalósító dinamikus proxy
      objektum, de erre a megvalósításnak nem feltétlenül van szüksége. A <span class="programkod">method</span> paraméterben van az, hogy a proxy-n milyen metódus hívódott meg, az <span
        class="programkod">Object[]</span> tömb pedig tartalmazza az átadott paramétereket, amikor a metódust meghívták.
    </p>
    <p>A dinamikus proxykat többféle célra használják:</p>
    <ul>
      <li>adatbázis kapcsolat- és tranzakciókezelés</li>
      <li>unit tesztekhez dinamikus mock objektumok létrehozása</li>
      <li>AOP-szerű metódus elfogás</li>
    </ul>
  </div>
  <p class="bekezd">A teszthez az alábbi osztályokra lesz szükség. Elsőként van egy interfészünk:</p>
  <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> InterfaceA {
    <span class="java_keyword">void</span> method(String input);
}
</pre>
  <p class="bekezd">Aztán egy ezt megvalósító osztályunk:</p>
  <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InterfaceAImpl <span class="java_keyword">implements</span> InterfaceA {

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method(String name) {
        System.out.println(name);
    }
}
</pre>
  <p class="bekezd">
    Az <span class="programkod">InvocationHandler</span> implementációnk:
  </p>
  <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">import</span> java.lang.reflect.InvocationHandler;
<span class="java_keyword">import</span> java.lang.reflect.Method;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InterfaceAInvocationHandler <span class="java_keyword">implements</span> InvocationHandler {

    <span class="java_keyword">private</span> Object classAImpl;<span class="java_comment">// ez akkor kell, ha meg akarjuk hívni rajta a metódust</span>

    <span class="java_keyword">public</span> InterfaceAInvocationHandler(Object impl) {
        <span class="java_keyword">this</span>.classAImpl = impl;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> Object invoke(Object proxy, Method method, Object[] args) <span class="java_keyword">throws</span> Throwable {
        String name = method.getName();
        <span class="java_keyword">if</span> (Class.<span class="java_keyword">class</span> == method.getDeclaringClass()) {
            <span class="java_comment">// Object-hez tartozó funkciók dinamikus megvalósítása</span>
            <span class="java_keyword">if</span> (<span class="java_string">"equals"</span>.equals(name)) {
                <span class="java_keyword">return</span> proxy == args[0];
            } <span class="java_keyword">else</span> <span class="java_keyword">if</span> (<span class="java_string">"hashCode"</span>.equals(name)) {
                <span class="java_keyword">return</span> System.identityHashCode(proxy);
            } <span class="java_keyword">else</span> <span class="java_keyword">if</span> (<span class="java_string">"toString"</span>.equals(name)) {
                <span class="java_keyword">return</span> proxy.getClass().getName() + <span class="java_string">"@"</span> +
                    Integer.toHexString(System.identityHashCode(proxy)) +
                    <span class="java_string">", with InvocationHandler "</span> + <span class="java_keyword">this</span>;
            } <span class="java_keyword">else</span> {
                <span class="java_keyword">throw</span> <span class="java_keyword">new</span> IllegalStateException(String.valueOf(method));
            }
        } <span class="java_keyword">else</span> <span class="java_keyword">if</span> (InterfaceA.<span class="java_keyword">class</span> == method.getDeclaringClass()) {
            <span class="java_comment">// InterfaceA-hoz tartozó funkciók dinamikus megvalósítása</span>
            <span class="java_keyword">if</span> (<span class="java_string">"method"</span>.equals(name)) {
                System.out.println(<span class="java_string">"Given: "</span> + args[0]);
            }
        }
        <span class="java_comment">// Meghívjuk a metódust, mert az jó</span>
        <span class="java_keyword">return</span> method.invoke(classAImpl, args);
    }
}
</pre>
  <p class="bekezd">A memóriaszivárgást pedig így csináljuk:</p>
  <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">import</span> java.io.IOException;
<span class="java_keyword">import</span> java.lang.reflect.Proxy;
<span class="java_keyword">import</span> java.net.URL;
<span class="java_keyword">import</span> java.net.URLClassLoader;
<span class="java_keyword">import</span> java.util.HashMap;
<span class="java_keyword">import</span> java.util.Map;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ClassMetadataLeakSimulator {

    <span class="java_comment">// ebbe tesszük a hivatkozásokat a class metaadatokra</span>
    <span class="java_keyword">private</span> <span class="java_keyword">static</span> Map&lt;String, InterfaceA&gt; classMap = <span class="java_keyword">new</span> HashMap&lt;String, InterfaceA&gt;();
    <span class="java_comment">// ennyi osztálybetöltőt próbálunk létrehozni</span>
    <span class="java_keyword">private</span> <span class="java_keyword">final</span> <span class="java_keyword">static</span> <span class="java_keyword">int</span> ITERATIONS = 50000;

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        System.out.println(<span class="java_string">"Class metadata leak simulator. Press Enter to start the magic!"</span>);
        <span class="java_keyword">try</span> {
            System.in.read();
        } <span class="java_keyword">catch</span> (IOException e) {
        }
        <span class="java_keyword">try</span> {
            <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0; i &lt; ITERATIONS; i++) {
                String leakClassloaderJAR = <span class="java_string">"file:"</span> + i + <span class="java_string">".jar"</span>;
                URL[] leakClassloaderURL = <span class="java_keyword">new</span> URL[] { <span class="java_keyword">new</span> URL(leakClassloaderJAR) };
                <span class="java_comment">// Létrehozunk egy új ClassLoader példányt:</span>
                URLClassLoader classLoader = <span class="java_keyword">new</span> URLClassLoader(leakClassloaderURL);
                <span class="java_comment">// Létrehozunk egy új proxy példányt:</span>
                InterfaceA tmp = (InterfaceA) Proxy.newProxyInstance(classLoader,
                    <span class="java_keyword">new</span> Class&lt;?&gt;[] { InterfaceA.<span class="java_keyword">class</span> }, <span class="java_keyword">new</span> InterfaceAInvocationHandler
                        <span class="java_keyword">new</span> InterfaceAImpl()));
                <span class="java_comment">// Hozzáadjuk a proxy példányt a szivárogtató hashmap-ünkhöz</span>
                classMap.put(leakClassloaderJAR, tmp);
            }
        } <span class="java_keyword">catch</span> (Throwable ex) {
            System.out.println(<span class="java_string">"ERROR: "</span> + ex);
        }
        System.out.println(<span class="java_string">"Done!"</span>);
    }
}</pre>
  <p class="bekezd">
    A példában folyamatosan új osztálybetöltő osztálydefiníciókat hozunk létre, ezzel próbára téve a metaspace (permgen) tűrőképességét. Az <span class="programkod">URLClassLoader</span>
    osztály eredetileg URL-ekről történő osztálybetöltőként működik, bár helyi fájlokból is képes a munkát elvégezni. Itt nem használjuk ki ezt a lehetőségét, csupán azért van rá szükség,
    hogy új osztálydefinícióként jelenjenek meg a metaspace-en. A program megpróbál adott számú új <span class="programkod">URLClassLoader</span> osztálydefiníciót létrehozni, hacsak közben
    el nem fogy a rendelkezésre álló memória. Mivel az osztály hivatkozik a betöltő osztályára, a hashmap-be gyűjtött proxy példányokon keresztül ezek mind elérhetőek maradnak, így a GC nem
    tudja őket kipucolni.
  </p>
  <p class="bekezd">A program futásának eredménye:</p>
  <p class="bekezd" style="text-decoration: underline;">1.7-es Java esetén 128 MB permgen területtel a következő paraméterekkel:</p>
  <pre class="programkod">-XX:MaxPermSize=128m -XX:+HeapDumpOnOutOfMemoryError -Xmx512M -Xloggc:gclog.log -XX:+PrintGCDetails</pre>
  <p class="bekezd">
    <img alt="pgen1" class="jobb" src="/informatika/essze/garbage/pgen1.jpg" />Amint látható a VisualVM diagnosztikai eszköz ábráján, a permgen terület szépen lassan betelt. A
    tulajdonképpeni méretet előbb megpróbálta növelni (vagyis a virtuális területet csökkenteni), de hamar elérte a maximumot. Ezután pedig némi, többnyire sikertelen GC-zést követően (ami
    akkor már szinte a teljes processzoridőt elfoglalta) kaptunk egy <span class="programkod">java.lang.OutOfMemoryError</span>-t. A heap dump elemzéséből kiderült, hogy a permanent
    generation 34071 URLClassLoader osztályt volt képes befogadni, mielőtt megadta magát.
  </p>
  <p class="bekezd" style="text-decoration: underline;">1.8-as Java esetén alapértelmezett (korlátlan) metaspace területtel a következő paraméterekkel:</p>
  <pre class="programkod">-XX:+HeapDumpOnOutOfMemoryError -Xmx512M -Xloggc:gclog.log -XX:+PrintGCDetails</pre>
  <p class="bekezd">A GC logokból és az ábrából is látható, hogy a metaspace terület dinamikusan növekedett az igényeknek megfelelően 15,5 MB-ról 302 MB-ig, a program pedig le tudott
    futni, mert simán volt ennyi szabad memória.</p>
  <p class="kozep">
    <img alt="pgen2" src="/informatika/essze/garbage/pgen2.jpg" />
  </p>
  <p class="bekezd" style="text-decoration: underline;">1.8-as Java esetén 128 MB metaspace területtel a következő paraméterekkel:</p>
  <pre class="programkod">-XX:MaxMetaspaceSize=128M -XX:+HeapDumpOnOutOfMemoryError -Xmx512M 
  -Xloggc:gclog.log -XX:+PrintGCDetails</pre>
  <p class="bekezd">
    Ez a megoldás egy <span class="programkod">java.lang.OutOfMemoryError: Metaspace</span> hibaüzenettel örvendeztet meg minket. A dump alapján 20300 <span class="programkod">URLClassLoader</span>
    osztálydefiníció után adta meg magát. A GC logból és a diagramról is látszik, hogy a hibát már akkor megkaptuk, amikor a JVM nem tudta tovább növelni a fenntartott metaspace területet,
    ehhez nem kellett, hogy a teljes beteljen, mint a permgen területnél.
  </p>
  <p class="kozep">
    <img alt="pgen3" src="/informatika/essze/garbage/pgen3.jpg" />
  </p>
  <h2>Szemét gyűjtő</h2>
  <p class="bekezd">Most, hogy megismertük a heap felosztását, lássuk végre, hogyan működik az objektumfoglalás és öregedés egy GC során.</p>
  <p class="bekezd">1. minden új objektum az éden területen jön létre. Mindkét survivor terület üresen indul.</p>
  <p class="kozep">
    <img alt="aging1" src="/informatika/essze/garbage/aging1.jpg" />
  </p>
  <p class="bekezd">2. amikor az éden terület betelik, kiváltódik egy minor GC (valójában majd akkor, amikor a következő objektum foglalását már nem lehet elvégezni)</p>
  <p class="kozep">
    <img alt="aging2" src="/informatika/essze/garbage/aging2.jpg" />
  </p>
  <p class="bekezd">3. A hivatkozott objektumok átkerülnek a nulladik survivor területre. A nem hivatkozott objektumok törlődnek, amikor az éden területet kiüríti a GC. Egy objektum
    másolása természetesen sokkal költségesebb, mint egyszerű megjelölése, ezért az éden a legnagyobb a három young generációs területből. Az objektumok többsége még fiatalkorában kimúlik.
    Nagy éden biztosítja, hogy ezek az objektumok nem fogják túlélni az első GC ciklust és így egyáltalán nem kell majd őket másolgatni.</p>
  <p class="kozep">
    <img alt="aging3" src="/informatika/essze/garbage/aging3.jpg" />
  </p>
  <p class="bekezd">4. a következő minor GC újra elindul az éden területen. A nem hivatkozott objektumok törlődnek, a hivatkozott objektumok átkerülnek egy survivor területre, ebben az
    esetben viszont az elsőre (S1). Az utolsó minor GC-nél S0-ra átkerült objektumok is átkerülnek az S1-re, eközben az életkor információjuk inkrementálódik. Amikor minden túlélő objektum
    átkerült az S1-re, az S0 és az éden is törlődik. Figyeljük meg, hogy most már különböző életkorú objektumaink vannak a survivor területen.</p>
  <p class="kozep">
    <img alt="aging4" src="/informatika/essze/garbage/aging4.jpg" />
  </p>
  <p class="bekezd">5. a következő minor GC-nél ugyanez a folyamat ismétlődik a survivor területek felcserélésével. A hivatkozott objektumok az S0 területre kerülnek és egyet megint
    idősödnek. Az éden és az S1 kiürül.</p>
  <p class="kozep">
    <img alt="aging5" src="/informatika/essze/garbage/aging5.jpg" />
  </p>
  <p class="bekezd">6. eljött a továbblépés (promotion) ideje. Egy minor GC után, amikor egy bizonyos életkort (aging threshold) elértek az objektumok (a példában ez 8, de
    konfigurálható), átkerülnek az old generációba. (Erre tenured területként is szoktak hivatkozni.)</p>
  <p class="kozep">
    <img alt="aging6" src="/informatika/essze/garbage/aging6.jpg" />
  </p>
  <p class="bekezd">7. ahogy a minor GC-k folyamatosan bekövetkeznek, objektumok folyamatosan kerülnek át az old generációba. Persze simán lehetséges, hogy egyes objektumok túl korán
    átkerülnek ide. A területek mérete és aránya nagy befolyással van a foglalási sebességre, GC hatékonyságra és gyakoriságára és teljesen az alkalmazás viselkedésétől függ. Az ideális
    megoldást csak megfelelő architektúrális ismeretekkel és sok teszteléssel lehet megtalálni.</p>
  <p class="kozep">
    <img alt="aging7" src="/informatika/essze/garbage/aging7.jpg" />
  </p>
  <p class="bekezd">8. Végül egy major GC lefut az old generáción, ami kiüríti és tömöríti azt a területet</p>
  <p class="bekezd">
    Ezt az egész működést remekül lehet szemléltetni egy, az egyébként minden JDK-ban megtalálható Java VisualVM nevű eszközzel. Ez a jvisualvm.exe fájllal indítható, azonban ma már saját <a
      onclick="window.open(this.href);return false;" href="https://visualvm.github.io/download.html">github projektje</a> is van. A Visual VM-hez tartozik egy Visual GC nevű plugin, amivel
    szépen grafikus felületen követhetjük a GC és a heap állapotát. A JDK demók között található Java 2D nevű programot monitorozva kaptam az alábbi diagramot a Visual GC-ben:
  </p>
  <p class="kozep">
    <img alt="vgc1" src="/informatika/essze/garbage/vgc1.jpg" />
  </p>
  <p class="bekezd">
    Ez az ábra szerintem az eddigiek alapján sok magyarázatot már nem igényel. A Histogram nevű diagram százalékosan mutatja, hogy a young generáció objektumai hány szemétgyűjtést éltek túl
    (a Histogram-hoz a tesztprogram JVM-jét <span class="programkod">-XX:+UseParNewGC</span> paraméterrel kell indítani). A tenuring threshold azt jelenti, hogy 15 szemétgyűjtés után kerül
    egy objektum az old generation-be. A Spaces diagramon a háttér világosabb és sötétebb szürke négyzetekkel van behálózva. A sötétszürke a ténylegesen lefoglalt (utilized, commited)
    memóriát, míg a világosabb szürke a JVM által lefoglalható, de még nem lefoglalt (uncommited) memóriát jelzi. A konkrét értékek látszanak a Graphs részben is a következő formátumban: <b>(maximális,
      lefoglalt): használt</b>.
  </p>
  <p class="bekezd">Látszik, hogy az édenben lévő objektumok összmérete folyamatosan nő, míg le nem fut egy szemétgyűjtés (zöld tüske), ekkor az éden kiürül. Ezzel egy időben azt is
    látjuk, hogy a túlélő objektumok az egyik survivorből átkerülnek a másikba. Ha az egyik survivor betelne, az ide kerülendő objektumok automatikusan az old generationbe kerülnek
    átmásolásra. Ezt a hibajelenséget premature promotionnek nevezik. Amikor emiatt betelik az old generation is, és le kell futtatni a GC-t azt promotion failure-nek hívják. Ha elfogy a
    memória, OutOfMemoryError-t kapunk. Ezt azonban a JVM már csak akkor dobja, ha a GC lefutott és ezután sincs szabad memória.</p>
  <p class="bekezd">Eddig még nem említettem az alábbi két furfangos ötletet, amik közül az első a GC, a második a memóriafoglalás gyorsítására használatos.</p>
  <p class="bekezd">
    <b>Kártyajáték</b>
  </p>
  <p class="bekezd">Ha nem szeretnénk bejárni az egész heap-et a használt objektumok meghatározásához, akkor a GC-nek tudnia kell, hogy a heap nem átvizsgált területén szerepel-e
    referencia átvizsgált területen lévő objektumokra. A generációs szemétgyűjtés esetén tipikus példa, hogy az old generáció átvizsgálása nélkül szeretnénk szemétgyűjtőzni a young
    generációt. Az ezt lehetővé tévő adatszerkezetet card table-nek hívják. Ez gyakorlatilag egy bájttömb, amiben minden bájtot egy kártyának (card) hívnak. Egy kártya a heap egy bizonyos
    címtartományának felel meg. A HotSpot-ban ennek a címtartománynak a mérete 512 bájt, ezt hívják kártyalapnak (card page). Amikor egy old generációban lévő objektum valamely referencia
    mezőjét valami beállítja, akkor egy ún. write barrier kód (általában egy shift and store utasítás) beállítja piszkosnak (dirty) az ahhoz a címtartományhoz tartozó kártyát. Minor GC-k
    esetén a mark fázisban ezért csak azokat az old generációs területeket kell átvizsgálni, amelyek esetén piszkos a hozzájuk tartozó kártya. Az itt lévő objektumok szolgálhatnak további
    GC root-ként a young területhez.</p>
  <p class="bekezd">
    <b>TLAB</b>
  </p>
  <p class="bekezd">A HotSpot egy speciális technikát is alkalmaz a memóriafoglalások gyorsítására. Ennek az ismertetését azért nem korábban tárgyaltam, mert most a szemétgyűjtés
    alapjainak ismeretében már jobban látható az előnye. Az objektumok nem csak úgy találomra kerülnek a memóriába létrehozáskor, hanem egy ún. pointer bump allocation nevű módszer segít
    ebben. Ez gyakorlatilag egy mutatót jelent, ami megmondja, hogy hová lehet tenni a következő objektumot a memóriában. A HotSpot ezt mindig ide teszi, majd növeli a mutató értékét. Ha
    például a mutatónk értéke 16, és létre akarunk hozni egy 32 bájtos objektumot, akkor ez a módszer visszaadja a 16-ot referenciaként, ott kezdődhet az új objektum, a mutatót pedig 48-ra
    állítja. Új objektum létrehozásakor csak annyit kell vizsgálni, hogy az éden terület tetejéig van-e még elég hely. Ha igen, létrehozzuk, ha nem, akkor lefut a minor GC, ami mindig
    kiüríti az éden területet, tehát probléma nélkül kezdődhet újra a lefoglalás szépen sorban. Ez nagyon gyors objektumlétrehozást tesz lehetővé.</p>
  <p class="bekezd">
    Többszálú programoknál azonban eléggé hamar probléma jelentkezhet, hiszen a mutatót szinkronizálni kellene a szálak között, ami jelentős teljesítménybeli visszaesést jelentene. Ezért a
    JVM egy ún. Thread-Local Allocation Buffer (TLAB) nevű technikát alkalmaz. Ez azt jelenti, hogy minden szál megkap egy kis saját memóriaterületet az édenben, ahol már pointer bump
    allocation módszerrel foglalhatja magának az objektumok tárterületét mindenféle lockolástól mentesen. Csak akkor kell szálbiztos módon dolgozni, amikor a kiosztott TLAB betelik és újat
    kér a szál. Ennek a technikának köszönhetően a HotSpot-ban egy <span class="programkod">new Object()</span> művelet az idő legnagyobb részében mindössze 10 gépi utasítást igényel. (A
    TLAB-ot ki lehet kapcsolni a <span class="programkod">-XX:-UseTLAB</span> kapcsolóval, de ezután megnézhetjük a többszálú programunk teljesítményét. Igazi feketeövesek viszont a <span
      class="programkod">-XX:TLABSize=N</span> paraméterrel saját maguk is beállíthatják a TLAB méretét. )
  </p>
  <p class="bekezd">Így már látjuk, hogy a generációs heap hogy segíti a gyors memóriafoglalást, bár azon az áron, hogy az élő objektumokat a GC-nek mozgatnia kell. Ha nem kellene
    mozgatnia, akkor viszont a memóriafoglalás lenne lassabb. Sajnos nincs ingyenebéd.</p>
  <div class="keretes">
    <p>Azon elvetemültek számára, akiket érdekel az assembly programozás, picit bővebben is kivesézem a card marking, vagyis kártya megjelölés folyamatát. Az alábbi kódrészletek szerint
      működik az 1.6-7-8-as JVM. Ez a kód egy teljesen szokványos setFoo(Object bar) metódusnak felel meg.</p>
    <pre class="programkod">; rsi a 'this' cím
; rdx a paraméter, referencia a bar-hoz
; JDK6:
mov    QWORD PTR [rsi+0x20],rdx  ; this.foo = bar
mov    r10,rsi                   ; r10 = rsi = this
shr    r10,0x9                   ; r10 = r10 >> 9;
mov    r11,0x7ebdfcff7f00        ; r11 a card table bázisa, például
                                 ; byte[] CARD_TABLE
mov    BYTE PTR [r11+r10*1],0x0  ; beállítjuk a 'this' kártyát piszkosra:
                                 ; CARD_TABLE[this cím >> 9] = 0

; JDK7(ugyanaz):
mov    QWORD PTR [rsi+0x20],rdx
mov    r10,rsi
shr    r10,0x9
mov    r11,0x7f6d852d7000
mov    BYTE PTR [r11+r10*1],0x0

; JDK8:
mov    QWORD PTR [rsi+0x20],rdx
shr    rsi,0x9                   ; az okosabb JIT észrevette, 
                                 ; hogy az RSI-t később nem használjuk,
                                 ; úgyhogy helyben megcsinálja a shift műveletet
mov    rdi,0x7f2d42817000
mov    BYTE PTR [rsi+rdi*1],0x0</pre>
    <p>Tehát egy referencia beállítása mellé némi egyéb plusz munka járul, ami gyakorlatilag erre való (a &gt;&gt; 9 művelet konvertálja a memóriacímet kártyaindexszé):</p>
    <pre class="programkod">CARD_TABLE[this address &gt;&gt; 9]=0;</pre>
    <p>Ez persze a primitív típusú mezők egyszerű beállításához képest jelentős többletköltség, viszont szükség van rá a megfelelő memóriakezeléshez. Így a youg generáció
      szemétgyűjtőzése esetén csökken az old generáció átvizsgálási ideje.</p>
    <p>Bár ez már igazán nem tartozik a cikk által bemutatni kívánt témakörhöz, nem tudom megállni, hogy a card table apropóján beszámoljak egy érdekes párhuzamos programozási
      problémáról.</p>
    <p>
      A card table elképzelés használhatónak bizonyult, ugyanakkor kiderült, hogy masszívan párhuzamosított környezetekben teljesítménybeli hátránya lehet. Erről egy Oracle-nél dolgozó
      kutató számolt be és mivel nem titok, én is megosztom. (Párhuzamos programozásban ezt a problémát false sharing-nak nevezik.) Tegyük fel, hogy a processzorunkban a cache vonal mérete
      64 bájt, ami a modern processzorokban szokványosnak számít. Ez azt jelenti, hogy 64 kártya osztozik egy cache vonalon (64*512=32 KB). Így aztán ugyanarra a 32 KB-os területre eső, de
      különböző szálak által tárolt referenciák miatt mindig ugyanazon card table alatt lévő cache vonalba történik írás. Ez eléggé gyakori write invalidálást és cache koherencia forgalmat
      eredményez, ami pedig csökkenti a teljesítményt és rossz hatással van a skálázódásra is. Ez a hatás pedig érdekes módon még nagyobb is egy teljes, átmozgatást végző GC <i>után</i>. A
      szálak ugyanis a TLAB-ok miatt hajlamosak eltérő címtartományokba foglalódni, viszont egy teljes GC után a fennmaradó objektumok sokkal szorosabban fognak egymás mellett
      elhelyezkedni, vagyis jobban előjön a fent említett probléma. Ráadásul a legtöbb card table-be való tárolás redundáns, hiszen az adott kártyát már valami más korábban valószínűleg
      piszkosra állította. Ez egy egyszerű megoldást sugallt: a barrierben egyszerű feltétel nélküli tárolás helyett elsőként megvizsgáljuk a kártya állapotát és csak akkor tároljuk az új
      értéket, ha még tiszta. Ez persze kicsit növeli a barriert és egy feltételes elágazást tesz bele, de elkerüli a problémát.
    </p>
    <p>
      Ennek a módosításnak a bekapcsolására a JDK 7-be bekerült egy <span class="programkod">-XX:+UseCondCardMark</span> parancssori opció. Ezzel a fenti kód így módosul:
    </p>
    <pre class="programkod">; rsi a 'this' cím
; rdx a paraméter, referencia a bar-hoz
; JDK7:
0x7fc4a1071d5c: mov    r10,rsi                   ; r10 = this                   
0x7fc4a1071d5f: shr    r10,0x9                   ; r10 = r10 >> 9
0x7fc4a1071d63: mov    r11,0x7f7cb98f7000        ; r11 = CARD_TABLE
0x7fc4a1071d6d: add    r11,r10                   ; r11 = CARD_TABLE + (this >> 9)
0x7fc4a1071d70: movsx  r8d,BYTE PTR [r11]        ; r8d = CARD_TABLE[this >> 9]
0x7fc4a1071d74: test   r8d,r8d
0x7fc4a1071d77: je     0x7fc4a1071d7d            ; if(CARD_TABLE[this >> 9] == 0)
                                                 ;   goto 0x7fc4a1071d7d
0x7fc4a1071d79: mov    BYTE PTR [r11],0x0        ; CARD_TABLE[this >> 9] = 0
0x7fc4a1071d7d: mov    QWORD PTR [rsi+0x20],rdx  ; this.foo = bar</pre>
    <p>Ami nagyjából annyit tesz, hogy:</p>
    <pre class="programkod">if (CARD_TABLE [this address >> 9] != 0) CARD_TABLE [this address >> 9] = 0;</pre>
    <p>Ez egy picit több kód, de elkerüli a potenciálisan konkurens írásokat a card table-be.</p>
    <p>G1 esetén a card marking egy picit bonyolódik, bár ennek megértéséhez még szükség lesz a később tárgyalandó G1 szemétgyűjtő ismeretére is:</p>
    <pre class="programkod">movsx  edi,BYTE PTR [r15+0x2d0] ; GC flag beolvasása
cmp    edi,0x0                  ; if (flag != 0)
jne    0x00000001066fc601       ; GOTO OldValBarrier
Label WRITE:
mov    QWORD PTR [rsi+0x20],rdx ; this.foo = bar
mov    rdi,rsi                  ; rdi = this 
xor    rdi,rdx                  ; rdi = this XOR bar 
shr    rdi,0x14                 ; rdi = (this XOR bar) &gt;&gt; 20
cmp    rdi,0x0                  ; ha this és bar nem ugyanaz a generáció 
jne    0x00000001066fc616       ; GOTO NewValBarrier
Label EXIT:
; ...

Label OldValBarrier:
mov    rdi,QWORD PTR [rsi+0x20] 
cmp    rdi,0x0                  ; if(this.foo == null) 
je     0x00000001066fc5dd       ; GOTO WRITE
mov    QWORD PTR [rsp],rdi      ; rdi paraméterként beállítása
call   0x000000010664bca0       ;   {runtime_call}
jmp    0x00000001066fc5dd       ; GOTO WRITE
Label NewValBarrier:
cmp    rdx,0x0                  ; bar == null
je     0x00000001066fc5f5       ; GOTO EXIT
mov    QWORD PTR [rsp],rsi
call   0x000000010664bda0       ;   {runtime_call}
jmp    0x00000001066fc5f5       ; GOTO exit;</pre>
    <p>Ez nagyjából erről szól:</p>
    <pre class="programkod">oop oldFooVal = this.foo;
if (GC.isMarking != 0 &amp;&amp; oldFooVal != null){
  g1_wb_pre(oldFooVal);
}
this.foo = bar;
if ((this ^ bar) &gt;&gt; 20) != 0 &amp;&amp; bar != null) {
  g1_wb_post(this); 
}</pre>
    <p>A hívások akkor jelentenek pluszmunkát, ha nem vagyunk elég szerencsések és</p>
    <ul>
      <li>akkor írunk referenciát, amikor épp card marking van folyamatban (és a régi érték nem null)</li>
      <li>a célobjektum &quot;régebbi&quot;, mint az új érték (és az új érték nem null). A (SRC^TGT&gt;&gt;20!=0) pedig egy nem generációk közötti, hanem régiók közötti vizsgálat.</li>
    </ul>
    <p>Látható tehát, hogy a referenciák írása olyan pluszmunkát jelent, ami a primitív típusoknál nem jelentkezik. Öröm az ürömben, hogy ez csak az írást érinti, az alkalmazások által
      sokkal gyakrabban végzett olvasást nem.</p>
  </div>
  <h2>Szemétgyűjtési lehetőségek a HotSpot-ban</h2>
  <p class="bekezd">Ebben a fejezetben végre eljött az, amire mindig is vártunk: megismerjük a HotSpot szemétgyűjtő mechanizmusait:</p>
  <ul>
    <li>Serial Collector</li>
    <li>Parallel Collector</li>
    <li>Parallel Compacting Collector</li>
    <li>Concurrent Mark-Sweep (CMS) Collector</li>
    <li>G1 (Java 7 update 4 óta)</li>
  </ul>
  <p class="bekezd">Az egyes JVM-ek esetén eltérő lehet az alapértelmezett GC: ez a JVM verziójától, típusától (szerver vagy kliens) és az adott platformtól is függ. Szerver JVM-nél
    Java 5 és 6 esetén általában a parallel collector az alapértelmezett, kliens JVM-nél pedig a serial collector, de például SPARC vagy IA-64 és x86-64 esetén már kliens gépeknél is a
    parallel collector. A sokféle különböző lehetőség miatt erről én táblázatot itt nem közlök.</p>
  <p class="bekezd">A serial collector a fentebb leírt módon működik, azaz a túlélő objektumok a survivorre, majd az old generation-re kerülnek a mark-copy szemétgyűjtési módszerrel. Az
    old generation és a permanent generation szemétgyűjtése pedig a mark-sweep-compact algoritmussal történik. Ez biztosítja a pointer bump allocation foglalási stratégia működését is. A
    szemétgyűjtés egy szálon történik és a JVM az alkalmazást teljesen leállítja a safepoint-okban, amíg az folyik. (GC logokban és egyes netes leírásokban a young generációs serial
    collector DefNew néven jelenik meg, én is így fogok rá hivatkozni a későbbiekben.)</p>
  <p class="bekezd">A serial collector általában jó választás kliensoldali alkalmazásokhoz, akár egy 64 megás heap esetén is viszonylag ritka és rövid (&lt; 0,5 mp) leállásokkal jár. Ez
    grafikus felhasználói felülettel rendelkező, egy felhasználót kiszolgáló alkalmazások esetén megfelelő. Akkor is jól jöhet, ha több JVM osztozik egy processzoron, hiszen ekkor úgysem
    tud párhuzamosan futni a szemétgyűjtés a processzorok kihasználtsága miatt. Több processzor, nagy terhelés, intenzív memóriahasználat és sok párhuzamos felhasználó esetén viszont már
    nagyban ronthatja az alkalmazásunk teljesítményét.</p>
  <h3>Parallel Collector</h3>
  <p class="bekezd">
    A parallel collector alapvetően annyival másabb mint a serial collector, hogy a young generation szemétgyűjtése nem egy, hanem annyi szálon fut, ahány magot ki tud használni. Maga az
    alapelv viszont nem változott: ugyanúgy megállítja a többi szálat, és mark-copy algoritmust használ. Az old generation szemétgyűjtése megegyezik a serial collector szemétgyűjtésével,
    ami a mark-sweep-compact algoritmus. A szálak alapértelmezett száma parancssori kapcsolóval is módosítható: <span class="programkod">-XX:ParallelGCThreads=&lt;szálak száma&gt;</span>.
    Egy maggal rendelkező gépen az Oracle dokumentációja szerint akkor is a serial collector fog futni, ha a JVM-et expliciten parallel collector bekapcsolásával indították.
  </p>
  <p class="bekezd">A parallel colectort throughput collectornak is nevezik, mivel több magot is használni tud, hogy felgyorsítsa az alkalmazás áteresztőképességét (throughput). Az
    Oracle olyan alkalmazásokhoz ajánlja, ahol nagy mennyiségű munkát kell elvégezni, de hosszabb megállások az old generáció szemétgyűjtésénél elfogadhatóak. Ilyen például kötegelt
    feldolgozások vagy nagy számú adatbázis-lekérdezés.</p>
  <p class="bekezd">Ennek a GC-nek két altípusa van:</p>
  <p class="bekezd">
    <span class="programkod">-XX:+UseParallelGC</span>: többszálú young generációs szemétgyűjtés és egyszálú old generációs szemétgyűjtés-tömörítés
  </p>
  <p class="bekezd">
    <span class="programkod">-XX:+UseParallelOldGC</span>: mind a young, mind az old generációs GC többszálú, a tömörítéssel egyetemben
  </p>
  <p class="bekezd">Többszálú young generációs szemétgyűjtésből valójában kétféle is létezik:</p>
  <ul>
    <li><b>parallel scavenge</b>: a serial és parallel old generációs szemétgyűjtőkhöz tervezve</li>
    <li><b>ParNew</b>: a CMS old generációs szemétgyűjtőhöz tervezve</li>
  </ul>
  <h3>Concurrent Mark-Sweep (CMS) Collector</h3>
  <p class="bekezd">
    <img alt="cms" class="jobb" src="/informatika/essze/garbage/cms.jpg" />A CMS collector-t olyan alkalmazások számára fejlesztették ki, ahol fontos a rövidebb GC-idő, viszont már az
    alkalmazás futása mellett is jut a GC számára némi erőforrás. A young generation minor szemétgyűjtése itt is ugyanúgy működik, mint a Parallel Collector esetében, a változás az old
    generation szemétgyűjtésében van, amit a jobb oldali - már ismerős - ábra mutat be. Ez négy fázisból áll:
  </p>
  <ul>
    <li><b>initial mark</b>: a szemétgyűjtő teljes leállási esemény alatt megjelöli a GC root-okból közvetlenül elérhető objektumokat. Ilyenek például az alkalmazás thread vermében és
      regiszterekben lévő objektumreferenciák, statikus objektumok, ezen kívül a heap egyéb helyéről (pl. a young generáció) közvetlenül elérhető objektumok. Az initial mark végeztével az
      alkalmazás szálai újraindulnak.</li>
    <li><b>concurrent marking phase/pre-cleaning</b>: az alkalmazás futásával egy időben bejelöli a további elérhető objektumokat (vagyis eközben az alkalmazás akár új objektumokat is
      létrehozhat)</li>
    <li><b>remarking</b>: ismét teljes megállási esemény, a szemétgyűjtő bejárja az előző fázis közben módosult objektumokat, ezzel véglegesíti az élő objektumok bejelölését. A
      remarking végeztével az alkalmazás szálai újraindulnak.</li>
    <li><b>concurrent sweeping</b>: a szemét eltávolítása az alkalmazás futásával párhuzamosan. Az elérhetetlen objektumok egy szabad listába kerülnek. Ezután egy szálon, de az
      alkalmazás futásával konkurensen átméretezi a heap-et és felkészíti a támogató adatstruktúrákat a következő gyűjtési ciklusra.</li>
  </ul>
  <p class="bekezd">A két teljes megállási esemény alatti mark fázis többszálú és a konkurens fázisok is lehetnek ilyenek.</p>
  <p class="bekezd">
    Mivel az alkalmazás és a szemétgyűjtő konkurens módon fut egy major GC esetén, a GC szál által még használatban lévőnek tartott objektumok a GC végére elképzelhető, hogy fölöslegessé
    válnak. Ilyen, már nem használt objektumokat, amiket mégsem pucolt ki, <i>floating garbage</i>-nek hívják. Ezek mennyisége függ a konkurens működés időtartamától és az alkalmazás általi
    referenciafrissítések gyakoriságától. A floating garbage objektumokat a következő szemétgyűjtési ciklus fogja kidobálni. Ez az ára a rövidebb teljes megállási eseményeknek. Emiatt
    érdemes kb 20%-kal felülbecsülni az old generation méretét.
  </p>
  <p class="bekezd">
    A CMS collector ún. non-compacting szemétgyűjtő, azaz tömörítést nem végez, ez pedig töredezettséghez vezet. Ez egyrészt megnehezíti a kezelést, hiszen nem egy mutatót kell karban
    tartani, hanem egy listában kell nyilvántartani a szabad területeket. A többi szemétgyűjtővel ellentétben a CMS nem akkor fut le, mikor betelik a heap, hanem hamarabb, hogy még képes
    legyen lefutni. Ha azonban a már nem használt terület felszabadítása még nem teljes, de az old generáció már betelt vagy pedig egy új foglalás már nem sikerült a rendelkezésre álló
    helyen, akkor az alkalmazás teljesen leállítódik és a jól ismert mark-sweep-compact algoritmus fut le. Ezt az esetet <i>concurrent mode failure</i>-nek hívják és azt jelzi, hogy a CMS
    paraméterezésén valamit érdemes változtatni.
  </p>
  <p class="bekezd">A CMS elindulásának többféle módja is van.</p>
  <p class="bekezd">Egyrészt a CMS becsléseket végez róla, hogy a tenured generáció mikor fog betelni és hogy egy ciklus mennyi ideig tartana. Ezeket a dinamikusan frissített
    előrejelzéseket használva akkor kezd el futni, amikor még feltételezhető, hogy a befejezése még a tenured generáció betelése előtt megtörténik. Ezek számítása során némi felülbecsléssel
    él, mert a concurrent mode failure elég költséges tud lenni.</p>
  <p class="bekezd">
    A CMS másrészt akkor is elindul, ha a tenured generáció kihasználtsága egy bizonyos százalékot elér (<i>initiating occupancy</i>). Ez a küszöbérték alapértelmezetten 92%, de Java
    kiadásról kiadásra változhat és a <span class="programkod">-XX:CMSInitiatingOccupancyFraction=&lt;N&gt;</span> parancssori paraméterrel is megváltoztatható, ahol N a százalék, vagyis 1
    és 100 közötti érték.
  </p>
  <p class="bekezd">A young és tenured generáció GC megállásai függetlenek egymástól, de nem indulnak egyszerre, bár létrejöhetnek gyors egymásutánban. Az egyik generáció GC-jét tehát
    azonnal is követheti a másiké, ami egy hosszabb megállásnak tűnhet. Ennek elkerülésére a CMS a remark megállást megpróbálja durván félúton az előző és a következő young generációs
    megállások közé időzíteni. Ez az ütemezés nem vonatkozik az initial mark megállásra, ami általában sokkal rövidebb, mint a remark. Az old generáció konkurens fázisai közben viszont már
    indulhat young generációs szemétgyűjtés.</p>
  <div class="keretes">
    <h3>Incremental mode</h3>
    <p>
      Java 7-ig volt a CMS-ben egy ún. <i>incremental mode</i> nevű megoldás is. Bár ez még a Java 8-ban is megvan, de már nem támogatott, mivel hosszú távon kivezetésre kerül. Láttuk, hogy
      a GC konkurens módban egy vagy több processzort használhat. Nos az incremental mode a hosszú konkurens szakaszok hatásának csökkentését szolgálja azzal, hogy időszakosan megállítja a
      konkurens fázist és visszaadja a processzort az alkalmazásnak. Ezt i-cms-nek is hívják, és a konkurens módban végzett feladatot tehát elosztja kisebb időszeletekre, amiket a young
      generációs szemétgyűjtések közé időzít. Ez a funkció akkor hasznos, amikor az alkalmazás, aminek a CMS által biztosított alacsony leállási időre van szüksége olyan gépeken fut,
      amelyekben kevés (1 vagy 2) processzor van.
    </p>
    <p>
      Az i-cms mód egy ún <i>duty cycle</i> nevű értékkel vezérli, mennyi munkát lehet a CMS szemétgyűjtőnek elvégezni, mielőtt vissza kell adnia a processzort az alkalmazásnak. A duty
      cycle a young generációs szemétgyűjtések közötti idő azon százaléka, ameddig a CMS-nek engedélyezett a futás. Az i-cms mód automatikusan kiszámolja a duty cycle-t az alkalmazás
      viselkedése alapján (ez az ajánlott módszer, amit <i>automatic pacing</i>-nek hívnak), de igény szerint parancssorból is megadható.
    </p>
    <p>Az i-cms parancssori opciói</p>
    <table cellpadding="0" cellspacing="0" width="95%" class="datatable">
      <thead class="datatable">
        <tr>
          <th class="normal"><b>Opció</b></th>
          <th class="normal"><b>Leírás</b></th>
          <th class="normal"><b>Alapérték JSE 5 és korábbinál</b></th>
          <th class="normal"><b>Alapérték JSE 6 és későbbinél</b></th>
        </tr>
      </thead>
      <tbody>
        <tr class="tr1">
          <td>-XX:+CMSIncrementalMode</td>
          <td>Engedélyezi az incremental mode-ot. Ehhez a CMS-t is engedélyezni kell a <span class="programkod">-XX:+UseConcMarkSweepGC</span> kapcsolóval.
          </td>
          <td>kikapcsolva</td>
          <td>kikapcsolva</td>
        </tr>
        <tr class="tr2">
          <td>-XX:+CMSIncrementalPacing</td>
          <td>Engedélyezi az automatic pacing funkciót. A duty cycle automatikusan beállításra kerül a JVM által gyűjtött futási statisztika alapján.</td>
          <td>kikapcsolva</td>
          <td>kikapcsolva</td>
        </tr>
        <tr class="tr1">
          <td>-XX:CMSIncrementalDutyCycle=&lt;N&gt;</td>
          <td>A minor GC-k közötti idő százaléka (0-100), amíg a CMS futhat. Ha a CMSIncrementalPacing engedélyezve van, akkor ez csak a kezdeti érték.</td>
          <td>50</td>
          <td>10</td>
        </tr>
        <tr class="tr2">
          <td>-XX:CMSIncrementalDutyCycleMin=&lt;N&gt;</td>
          <td>A duty cycle alsó határa százalékban, amikor a CMSIncrementalPacing engedélyezve van.</td>
          <td>10</td>
          <td>0</td>
        </tr>
        <tr class="tr1">
          <td>-XX:CMSIncrementalSafetyFactor=&lt;N&gt;</td>
          <td>Az óvatosság százaléka a duty cycle számításához.</td>
          <td>10</td>
          <td>10</td>
        </tr>
        <tr class="tr2">
          <td>-XX:CMSIncrementalOffset=&lt;N&gt;</td>
          <td>Annak a százaléka, amennyivel a duty cycle jobbra van léptetve (shift) a minor GC-k közötti időben.</td>
          <td>0</td>
          <td>0</td>
        </tr>
        <tr class="tr1">
          <td>-XX:CMSExpAvgFactor=&lt;N&gt;</td>
          <td>A CMS exponenciális átlag GC statisztikáinak számításához használt súlyozás százaléka.</td>
          <td>25</td>
          <td>25</td>
        </tr>
      </tbody>
    </table>
    <p>Az Oracle az i-cms finomhangolásához a CMSIncrementalSafetyFactor és a CMSIncrementalDutyCycleMin érték növelését javasolja, valamint az incremental pacing kikapcsolását és fix
      duty cycle használatát.</p>
  </div>
  <h3>Garbage first (G1) collector</h3>
  <p class="bekezd">A garbage first (G1) szemétgyűjtőt szerverekhez tervezték és alapvetően többprocesszoros, nagy memóriával rendelkező gépekhez szánták. Az volt az alapvető cél, hogy
    növeljék a JVM-en futó alkalmazások áteresztőképességét és csökkentsék a szemétgyűjtőkre jellemző teljes leállási eseményeket, egyben megjósolhatóvá téve azok hosszát. Mivel az Oracle
    szerint a G1 alapjaiban jobb megoldás, mint a CMS, hosszú távon szeretnék azt kiváltani. A kettő közötti egyik nagy különbség, hogy a G1 tömöríti a szabad helyeket, ami kiküszöböli a
    töredezettséggel kapcsolatos problémákat. A G1 emellett a CMS-hez képest jobban megjósolható leállási időket is biztosít és lehetővé teszi a felhasználónak, hogy megadjon kívánt
    leállási időkeretet.</p>
  <p class="bekezd">A G1 a következő alkalmazások számára hasznos:</p>
  <ul>
    <li>tudnak futni a GC mellett párhuzamosan, akárcsak a CMS esetén</li>
    <li>hosszú GC idők nélkül tömöríthetőek a szabad területek</li>
    <li>sokkal megjósolhatóbb GC leállási időket igényelnek</li>
    <li>nem akarnak sokat feláldozni az áteresztőképességől</li>
    <li>nem szükséges nekik sokkal nagyobb Java heap</li>
  </ul>
  <p class="bekezd">A G1-et a Java 7-hez fejlesztették ki, de később visszaportolták Java 6-ra is. A Java 9-nél pedig már ez az alapértelmezett. A G1 elsődleges célja az volt, hogy
    megoldást adjon azon felhasználóknak, akik olyan alkalmazásokat futtatnak, amik nagy heap területet igényelnek korlátozott GC késleltetéssel. Amennyiben régebbi GC-t használunk és nem
    tapasztalunk hosszú GC megállásokat, akkor nyugodtan maradhatunk a réginél. A friss JDK-k nem igénylik a G1-re váltást. A G1-nél a heap felépítése némileg eltér a korábbiakhoz képest:</p>
  <p class="kozep">
    <img alt="g1_1" src="/informatika/essze/garbage/g1_1.jpg" />
  </p>
  <p class="bekezd">A heap itt azonos méretű ún. régiókra van osztva, ezek közül mindegyik a virtuális memória egy összefüggő területét foglalja magában. Bizonyos régióhalmazok a
    korábban megismert szemétgyűjtőknél látott szerepeket kapnak (éden, survivor, old), azonban immár nincs adott méretük, ami nagyobb rugalmasságot biztosít a memóriahasználatban.
    (Survivor-ből itt a működés miatt már nincs külön S0 és S1.) A G1 sok tekintetben a CMS-hez hasonlóan működik. Ez is egy konkurens marking fázisban határozza meg, mely objektumok vannak
    használatban. Mikor a mark fázis kész, a G1 már tudja, hogy melyek a nagyrészt üres régiók. Elsőként ezeket pucolja ki, ami általában nagy szabad területet eredményez. Emiatt a módszer
    miatt hívják ezt a típust garbage first-nek. Ahogy a név sugallja, a G1 azokra a heap területekre koncentrálja a szemétgyűjtést és a tömörítést, amelyek valószínűleg tele vannak
    kidobható objektumokkal. A G1 egy ún. megállás-előrejelzési modellt (pause prediction model) használ, hogy megfeleljen a felhasználó által adott megállási időigénynek és annyi régiót
    választ ki a szemétgyűjtésre, amennyit a megadott állási idő alatt fel tud dolgozni.</p>
  <p class="bekezd">A G1 által újrafelhasználásra szánt régiókat ún. evakuálással üríti ki: egy vagy több régióból átmásolja az objektumokat egy újba, ez a folyamat pedig egyszerre
    szabadítja fel és tömöríti a memóriát. Többprocesszoros rendszereknél ez párhuzamos feldolgozással történik, hogy csökkenjen a leállási idő és nőjön az áteresztőképesség. Így aztán
    szemétgyűjtésenként a G1 folyamatosan csökkenti a töredezettséget, mindezt a felhasználó által megadott állási idők alatt. Ez mindegyik korábban tárgyalt módszernél fejlettebb: a CMS
    nem végez tömörítést, a Parallel Collection pedig csak teljes heap tömörítést végez, ami igen nagy állási időket eredményezhet. (A CMS esetén is előfordulhat tömörítés közvetetten: ha
    már olyan nagyon töredezett az old generáció, hogy full GC-re van szükség, mert a promotálni, vagyis oda áthelyezni kívánt objektum már nem fér el másképp. A full GC-k a G1-nél is
    egyszálúak, viszont az alkalmazás beállításainak megfelelő finomhangolásával elkerülhetőek.)</p>
  <p class="bekezd">Fontos megjegyezni, hogy a G1 nem valósidejű szemétgyűjtő. Nagy valószínűséggel megfelel ugyan a meghatározott állási időknek, de nem teljesen. A korábbi
    szemétgyűjtések tapasztalatai alapján végez becslést, hogy mennyi régiót lehet szemétgyűjtőzni a felhasználó által megadott idő alatt. Ezáltal meglehetősen pontos képe van arról, hogy
    mennyi költséget jelent a régiók szemétgyűjtőzése és ezt a modellt használja, hogy meghatározza, melyik és mennyi régiót szemétgyűjtőzzön, hogy benne maradjon az adott megállási
    időkeretben.</p>
  <p class="bekezd">
    <b>A G1 szemétgyűjtés lépésről lépése</b>
  </p>
  <p class="bekezd">
    <i>1. G1 heap</i>
  </p>
  <p class="bekezd">A heap a G1-nél egybefüggő memóriaterület, ami több fix méretű régóra van felosztva. A régióméretet és azok mennyiségét a JVM induláskor határozza meg. A cél az,
    hogy ne legyen több 2048 régiónál (de mindenképpen kettő hatvány legyen), amelyek mérete 1-32 MB közötti lehet a megadott heapmérettől függően.</p>
  <p class="bekezd">
    <i>2. G1 heap objektumfoglalás</i>
  </p>
  <p class="bekezd">A régiók valójában az éden, survivor és old generáció területek logikai reprezentációi.</p>
  <p class="kozep">
    <img alt="g1_1" src="/informatika/essze/garbage/g1_1.jpg" />
  </p>
  <p class="bekezd">A színek jelzik, hogy melyik régió milyen területnek felel meg. Az élő objektumok evakuálódnak (másolódnak vagy mozgatódnak) egyik régióból a másikba. A régiókat úgy
    tervezték, hogy párhuzamosan lehessen őket szemétgyűjtőzni az alkalmazás többi szálának leállításával vagy anélkül is. A képen látható, hogy a régiók édenként, survivorként és old
    generációként foglalhatóak le. Valójában van egy negyedik típusú objektum is, amit humongous régiónak neveznek. Ezeket a régiókat arra tervezték, hogy olyan objektumokat tároljanak,
    amelyek egy szabvány régiónak legalább 50%-át elfoglalják (vagy egy szabvány régiónál akár nagyobbak is lehetnek). Ezek folyamatosan egymás után következő régiókban tárolódnak. (Ha egy
    objektum nagyobb, mint egy régió, akkor a G1 esetén automatikusan az old generációban jön létre.) Egy plusz típusú régió pedig a heap nem használt területeinek jelzésére szolgál.</p>
  <p class="bekezd">
    <i>3. Young generáció a G1-ben</i>
  </p>
  <p class="bekezd">A heap nagyjából 2048 régióra oszlik. A minimális méret 1 MB, a maximális pedig 32 MB, de egy JVM-nél futás közben minden régió azonos méretű. A kék régiók tárolják
    az old generációs objektumokat, a zöldek meg a young generációsakat.</p>
  <p class="kozep">
    <img alt="g1_2" src="/informatika/essze/garbage/g1_2.jpg" />
  </p>
  <p class="bekezd">Ahogy a mellékelt ábra is mutatja, a régióknak nem szükséges folyamatosan elhelyezkedniük, mint a régebbi GC-k esetén. (A képeket az Oracle hivatalos oktatóanyagából
    vettem. A képre egy négyzethálót is oda kell képzelni, amelyek a régiókat határolják, a foglalt színek a régiók belső foglalását jelzik, nem pedig egy-egy régiót.)</p>
  <p class="bekezd">
    <i>4. Young szemétgyűjtés a G1-ben</i>
  </p>
  <p class="bekezd">Az élő objektumok evakuálódnak egy vagy több survivor régiókba, miközben nő az élettartamuk is. Amelyek a megfelelő életkort (aging threshold) elérik, az old
    generációs régiókba kerülnek (promote).</p>
  <p class="kozep">
    <img alt="g1_3" src="/informatika/essze/garbage/g1_3.jpg" />
  </p>
  <p class="bekezd">Ez a lépés egy teljes megállási esemény. A következő young szemétgyűjtőhöz kiszámítódik az éden és survivor mérete, amihez a G1 nyilvántartást is vezet. Olyan
    dolgok, mint a megállási idő, szintén figyelembe lesznek véve a számítás során. Ez a megközelítés egyszerűvé teszi a régiók átméretezését, szükség szerint nagyobbak vagy kisebbek is
    lehetnek.</p>
  <p class="bekezd">
    <i>5. Young szemétgyűjtés vége a G1-ben</i>
  </p>
  <p class="bekezd">Az élő objektumok evakuálódnak a survivor vagy old generációs régiókba.</p>
  <p class="kozep">
    <img alt="g1_4" src="/informatika/essze/garbage/g1_4.jpg" />
  </p>
  <p class="bekezd">A G1 esetén azt látjuk, hogy több GC szál dolgozik egyszerre, mint például a CMS esetén. A szálaknak itt ugyanis némi plusz munkájuk is van még: folyamatosan karban
    kell tartani a remembered sets és collection sets nevű adatstruktúrákat.</p>
  <p class="bekezd">
    <b>Collection sets (CSets)</b>: ez tárolja azon régiók listáját, amelyeket majd szemétgyűjtőzni lehet. Ennek során egy CSet-ben minden régió élő adata evakuálásra kerül
    (másolás/mozgatás). Egy CSet régiói lehetnek éden, survivor és/vagy old generációbeliek. A CSet-eknek az Oracle szerint kevesebb mint 1% többletköltsége van a JVM memóriaméretére.
  </p>
  <p class="bekezd">
    <b>Remembered sets (RSets)</b>: ezek tartják nyilván az egy régióhoz tartozó objektumreferenciákat. Ez teszi lehetővé a különböző régiók egymástól független szemétgyűjtőzését.
    Régiónként pontosan egy RSet van. Például amikor az A, B és C régiókat vizsgáljuk, tudnunk kell, hogy van-e rájuk referencia a D és E régiókból, hogy meghatározzuk azt, élőek-e. A
    teljes heap és a teljes régiók átvizsgálása elég hosszú lenne. Így a G1-nek a teljes átvizsgálás helyett csak a régióhz tartozó RSet-et kell vizsgálnia. Egy régióhoz tartozó RSet
    listázza a kívülről a régióba hivatkozó referenciákat. Az RSet hasonló, mint a korábbi szemétgyűjtőknél már tárgyalt card table mechanizmus. Az RSet-ek átlagos teljesítménybeli
    többletköltsége az Oracle szerint kevesebb, mint 5%, cserében lehetővé teszik a független szemétgyűjtőzést.
  </p>
  <p class="bekezd">Az alábbi ábrákon látható, hogy minden régiónak (nagy szürke négyzet) van egy RSet-je, amiben fel vannak jegyezve a máshonnan ebbe a régióba mutató referenciák.
    Ezeket a referenciákat kiegészítő GC root-okként lehet kezelni. Megjegyzendő, hogy az old régiókban lévő a konkurens marking fázisban szemétnek nyilvánított objektumok akkor is
    figyelmen kívül lesznek hagyva, ha van hozzájuk kívülről hivatkozó referencia: a hivatkozó objektumok is kidobandóak ebben az esetben.</p>
  <p class="kozep">
    <img alt="rset1" src="/informatika/essze/garbage/rset1.jpg" />
  </p>
  <p class="bekezd">A következő lépés ugyanaz, amit más szemétgyűjtők is csinálnak: több párhuzamos GC szál meghatározza, hogy mely objektumok vannak használatban és melyek nem:</p>
  <p class="kozep">
    <img alt="rset2" src="/informatika/essze/garbage/rset2.jpg" />
  </p>
  <p class="bekezd">Az élő objektumok végül a survivor régiókba kerülnek át (új survivor régió létrehozásával, ha szükséges). Az immár üres régiók felszabadíthatók és új
    objektumfoglalásokra felhasználhatók.</p>
  <p class="kozep">
    <img alt="rset3" src="/informatika/essze/garbage/rset3.jpg" />
  </p>
  <p class="bekezd">Az alkalmazás futása közben két módszer segít karban tartani az RSet-eket:</p>
  <ol>
    <li><b>post-write barrier-ek</b>: a write barrier-ekről már volt szó, a program implicit módon ezzel jelzi a GC-nek, ha megváltoztatott egy referenciát. A post itt annyit tesz,
      hogy a mezőbe írás után fut le a barrier kód. Amennyiben a referencia egyik régióból a másikba mutat, akkor egy ennek megfelelő bejegyzés jelenik meg a célrégió RSet-jében, de nem
      azonnal mint azt majd látni fogjuk.</li>
    <li><b>concurrent refinement szálak</b>: a write barrier-ek által jelentett plusz költség csökkentése érdekében az RSet-ek feltöltése aszinkron módon történik mégpedig ún. log
      pufferekkel. Ezek konkurens feldolgozásáért felelnek a concurrent refinement szálak. A log pufferekről később lesz szó. A concurrent refinement szálak beizzítása lépcsőzetes: eleinte
      csak kis számú szál aktív, aztán ahogy egyre több log puffer megtelik, úgy indul el egyre több szál. A szálak maximális számát a <span class="programkod">-XX:ParallelGCThreads</span>
      parancssori kapcsolóval vezérelhetjük.</li>
  </ol>
  <p class="bekezd">
    <b>Old generáció szemétgyűjtése G1-gyel</b>
  </p>
  <p class="bekezd">Akárcsak a CMS, a G1 is arra lett tervezve, hogy rövid megállásokat okozzon az old generációs objektumok esetén. A G1 az old generáción a következő fázisokban
    működik:</p>
  <ul>
    <li><b>initial mark</b>: teljes megállási esemény. Megjelöli a GC gyökereket és az azokból közvetlenül elérhető objektumokat. A G1 esetén ez egyszerre fut a young GC-vel (illetve
      közvetlenül utána).</li>
    <li><b>root region scanning</b>: a root régiók (G1 esetén ezek survivor régiók) átvizsgálása (scan) az old generációba mutató referenciák után. A hivatkozott objektumokat be is
      jelöli. Ez az alkalmazás futása közben konkurens módon történik. Ennek a fázisnak véget kell érnie, mielőtt egy young GC indul, ugyanis az evakuálhat is régiókat, ami nem tanácsos egy
      scan közben.</li>
    <li><b>concurrent marking</b>: ez a lépés hasonló a CMS-ben látotthoz: élő objektumok keresése a teljes heap-en az alkalmazás futása közben konkurens módon. Ezt a fázist young
      generációs GC-k megszakíthatják. A concurrent marking egy snapshot-at-the-beginning (SATB) nevű algoritmust használ, ami készít egy logikai képet (snapshot) a heap-en lévő élő
      objektumok csoportjáról még a mark fázis kezdetén (innen a neve). Ez a lépés egyszerűen végigmegy az objektumgráfon és bejelöli a megtalált objektumokat ebben a snapshot-ban. A G1-nek
      szüksége van rá, hogy az objektumgráfon az alkalmazás által végzett konkurens módosítások meghagyják az előző referenciákat a GC-nek a megjelölés céljából. Ezt ún. pre-write
      barrier-ekkel érik el. Ezeknek az a funkciója, hogy amikor épp átírunk egy mezőt mikor a concurrent marking aktív, eltárolja az előző referenciát egy ún. log pufferben, amit majd a
      concurrent refinement szálak feldolgoznak.</li>
    <li><b>remark</b>: teljes megállási esemény. A további konkurens update-ek meggátolása érdekében (hogy ne legyen több log puffer) megáll az alkalmazás és az a kis mennyiség, ami
      még megmaradt, feldolgozódik. Ezen kívül feldolgozza a még eddig feldolgozatlan objektumokat, amik még élők voltak mikor a konkurens fázis elindult. Ez a fázis ezen kívül még csinál
      némi egyéb tisztogatást is, például referencia feldolgozást vagy osztály unloading-ot.</li>
    <li><b>cleanup</b>: teljes megállási esemény és konkurens részekkkel. Előkészíti a terepet az evakuálásra:
      <ul>
        <li>számba veszi az élő objektumokat és a teljesen üres régiókat (teljes megállási esemény)</li>
        <li>scrub (kitisztítja) a remembered set-eket (teljes megállási esemény)</li>
        <li>beállítja az üres régiókat és beteszi őket a szabad listába (konkurens)</li>
      </ul></li>
    <li><b>másolás vagy evacuation pause</b>: teljes megállási esemény. Ennek során evakuálódnak az élő objektumok az új régiókba. Ezt a young generációs régiókkal is meg lehet tenni,
      amelyeket [GC pause (young)] jelöl a logban, de lehet mind a young mind az old generációs régiókkal, amelyeket [GC Pause (mixed)] jelöl a logban. Ezután az éden üres.</li>
  </ul>
  <p class="bekezd">A mixed collection egy olyan szemétgyűjtés, ami a young és old generáció tömörítését és evakuálását is elvégzi. (Ekkor a CSet-ben old és young generációs régiók is
    szerepelnek). Egy mixed collection általában több mixed GC ciklus alatt készül el. Amikor megfelelő mennyiségű old régió is szemétgyűjtőzve lett, a G1 visszaáll a csak a young generáció
    GC-zésére, amíg a következő marking ciklus véget nem ér. Ezt a viselkedést számos parancssori beállítással lehet vezérelni, ezekről később lesz szó. Egy mixed collection nem mindig
    követi a cleanup fázist. Amikor például az old generációból konkurens módon fel lehet szabadítani nagy részeket, akkor erre nincs szükség. Tehát lehet számos csak young generációs
    evakuációs megállás a concurrent marking vége és a mixed evakuációs megállás között.</p>
  <p class="bekezd">Ezek alapján a G1 szemétgyűjtés folytatása lépésről lépésre</p>
  <p class="bekezd">
    <i>6. Initial marking</i>
  </p>
  <p class="bekezd">Az initial marking rá van ültetve egy young generációs GC-re. A logokban a következőképp van jelölve: GC pause (young)(inital-mark). Vagy (G1 Evacuation Pause)
    (young) (initial-mark). Ekkor egyben a young terület evakuációja is megtörténik.</p>
  <p class="kozep">
    <img alt="g1_5" src="/informatika/essze/garbage/g1_5.jpg" />
  </p>
  <p class="bekezd">
    <i>7. Concurrent marking</i>
  </p>
  <p class="bekezd">Amennyiben a GC üres régiókat talált (X-szel jelölve), azok a remark fázis után azonnal törlődnek. Emellett azok az információk is itt számolódnak, amelyek az
    életben lévőséget meghatározzák.</p>
  <p class="kozep">
    <img alt="g1_6" src="/informatika/essze/garbage/g1_6.jpg" />
  </p>
  <p class="bekezd">
    <i>8. Remark </i>
  </p>
  <p class="bekezd">Az üres régiók eltávolítása és újrafelhasználása. Életben lévőség meghatározása az összes régióra.</p>
  <p class="kozep">
    <img alt="g1_7" src="/informatika/essze/garbage/g1_7.jpg" />
  </p>
  <p class="bekezd">
    <i>9. Copy/cleanup fázis</i>
  </p>
  <p class="bekezd">A G1 kiválasztja a &quot;legkevésbé életben lévő&quot; régiókat, ezeket lehet a leggyorsabban szemétgyűjtőzni. Ezek a régiók össze lesznek gyűjtve egy young GC-vel
    egyidőben. A logban ezt a következő jelöli: [GC pause (mixed)]. Tehát a young és old generációk egy időben vannak szemétgyűjtőzve.</p>
  <p class="kozep">
    <img alt="g1_8" src="/informatika/essze/garbage/g1_8.jpg" />
  </p>
  <p class="bekezd">
    <i>10. copying/cleanup fázis utáni állapot</i>
  </p>
  <p class="bekezd">A kiválasztott régiók szemétgyűjtőzése és tömörítése megtörtént: a képen sötétkék és sötétzöld régiókkal jelölve.</p>
  <p class="kozep">
    <img alt="g1_9" src="/informatika/essze/garbage/g1_9.jpg" />
  </p>
  <p class="bekezd">Ezzel befejeződött az old generációs GC. Sajnos a G1-nél is vannak olyan esetek, amikor semmi se segít rajtunk, csak egy full GC. Ezek:</p>
  <ul>
    <li><b>concurrent mode failure</b>: a CMS-nél már volt róla szó. Annak a jele, hogy az alkalmazás gyorsabban foglalja az objektumokat, mint ahogy a G1 felszabadítja azokat. Ez a
      hiba akkor jelentkezhet, amikor a G1 épp evakuálást végez, vagyis élő adatokat másol egyik régióból a másikba. Amennyiben szabad (üres) régió nem található az objektumok számára, full
      GC indul.</li>
    <li><b>promotion failure</b>: mixed collection közben betelik az old generáció, még mielőtt a mixed collection elegendő régiót szabadított volna fel</li>
    <li><b>evacuation failure</b>: a young gc futásakor a survivor és az old generáció is megtelik, így az édenből nem lehet hova kimenekíteni az objektumokat</li>
    <li><b>humongous allocation failure</b>: olyan nagy méretű objektumnak nem sikerült helyet foglalni, amely akár több régiót is elfoglalna</li>
  </ul>
  <h3>Speciális szemétgyűjtési stratégiák</h3>
  <p class="bekezd">Vannak helyzetek, amikor a szabványos szemétgyűjtés nem megfelelő. Kedvcsinálóként dióhéjban lássunk két, az előzőektől meglehetősen eltérő módszert: a távoli
    szemétgyűjtőt, ami elosztott objektum referenciákkal foglalkozik és a valósidejű szemétgyűjtőt, ami valós idejű működést garantál. (Vagy legalábbis szeretne.)</p>
  <p class="bekezd">
    <b>Remote garbage collector - távoli szemétgyűjtő</b>
  </p>
  <p class="bekezd">A távoli eljáráshívással (remote method invocation - RMI) úgy lehet egy lokális objektumot (kliensoldali stub) használni, hogy az gyakorlatilag egy másik JVM-ben
    lévő (szerveroldali) másik objektumot reprezentál. Az RMI hívások esetén természetesen a szerveroldali objektumnak is léteznie kell. Tehát az RMI esetén szükség van rá, hogy úgy
    tekintsünk a szerveroldali objektumra, hogy arra a kliensoldali stub-ból referencia hivatkozik. Mivel a szervernek nincs módjában tudnia erről a referenciáról, valamiféle távoli
    szemétgyűjtési megoldásra van szükségünk. Egy ilyenre például:</p>
  <ul>
    <li>amikor a kliens megkap egy stub-ot a szervertől, egy bérletet (lease) kap rá. A szerveroldali objektumra onnantól úgy tekintünk, hogy egy kliens stub hivatkozik rá.</li>
    <li>egy szerveroldali objektumot maga az RMI implementáció tart életben amíg a bérlet le nem jár (ez egy egyszerű timeout)</li>
    <li>a létező kliensoldali stub-ok rendszeres ütemenként jelzik (heartbeat), hogy megújítanák a bérletüket (ezt piszkos hívásoknak - dirty call - hívják). Ezt maga az RMI
      implementáció automatikusan elvégzi.</li>
    <li>a szerveroldal rendszeresen ellenőrzi a bérleteket lejárt bérlet után kutatva</li>
    <li>amikor a bérlet lejár (mert nem létezik többé kliens, ami arra az objektumra hivatkozna), az RMI implementáció egyszerűen sorsára hagyja az objektumot. Ezután ugyanúgy
      szemétgyűjtőzhető, mint bármely másik objektum.</li>
  </ul>
  <p class="bekezd">
    Azon szerveroldali objektumok, amiket már nem használ egy kliens sem, ezáltal túlélhetnek szemétgyűjtéseket (kliens objektumok ilyenkor már nincsenek életben). Egy egyébként inaktív
    kliens fenntarthat egy távoli objektumot hosszú ideig még akkor is, ha az objektum már egyébként készen állna a kisöprésre. Ha a kliens objektum nem vesz részt szemétgyűjtésben, akkor a
    hozzá tartozó szerverobjektum is megmarad. Extrém esetekben ez azt jelenti, hogy sok inaktív kliens rengeteg nem használt szerverobjektumhoz vezet, amiket nem lehet kisöpörni. Ez pedig
    szépen out of memory hibába taszíthatja a <span style="text-decoration: line-through;">világot</span> szervert.
  </p>
  <p class="bekezd">Ennek elkerülésére az elosztott szemétgyűjtő (RMI garbage collector) rendszeres időközönként kikényszerít egy kliensoldali major GC-t (annak minden teljesítménybeli
    negatív kihatásával). Ezt az időközt a GCInterval rendszertulajdonság adja meg. Ugyanez a beállítás megvan szerveroldalon is és ugyanezt csinálja. (A Java 6-ig mindkét beállítás egy
    perces alapértéket tartalmazott, ami nem volt valami jó hatással a teljesítményre. A Java 6-ban a szerveroldali alapértelmezés egy órára módosult.) Ennek a beállításnak igazából
    általában a kliensoldalon van értelme (hogy lehetővé tegye a szervernek a távoli objektumok eltávolítását), de nem egészen világos, hogy miért létezik ez szerveroldalon is. Egy
    szerveroldali távoli objektmumot a szemétgyűjtő kisöpör amikor a bérlet lejár vagy amikor a kliens explicit módon törli azt. Az explicit szemétgyűjtésnek nincs hatása erre, ezért
    ajánlatos ezt a beállítást szerveroldalon minél nagyobbra venni.</p>
  <p class="bekezd">Egyébként ajánlatos, hogy az RMI stateless service interfészekre legyen korlátozva. Mivel ezek az interfészek csak egy példányban léteznek és sosem kell őket
    szemétgyűjtésben kezelni (legalábbis amíg az alkalmazás fut), nincs szükség távoli szemétgyűjtésre. Ha ilyen módon korlátozzuk az RMI-t, akkor a kliensoldali intervallumot is elég
    nagyra tudjuk venni és így eltávolíthatjuk az elosztott szemétgyűjtőt az egyenletünkből.</p>
  <p class="bekezd">
    <b>Valósidejű szemétgyűjtők</b>
  </p>
  <p class="bekezd">A valósidejű rendszerek majdnem azonnali (egyszámjegyű millimásodperces tartományon belüli) végrehajtási sebességet biztosítanak minden egyes feldolgozott kéréshez.
    Ezeknél problémát okozhat a szemétgyűjtés által futásidejű felfüggesztésekhez használt idő, különösképpen azért, mert a GC futtatás gyakorisága és időtartama gyakorlatilag
    megjósolhatatlan. Optimalizálhatunk alacsony megakadási időre, de nem tudunk maximális megakadási időt garantálni. Szerencsére több megoldás is van a problémára.</p>
  <p class="bekezd">A Sun eredetileg specifikált egy Java Real-Time System nevű dolgot (Java RTS) egy speciális valósidejű szemétgyűjtővel, amit Henriksson GC-nek hívnak és megpróbált
    megfelelni a szigorú szálütemezésnek. Ez az algoritmus megpróbálja garantálni, hogy a szemétgyűjtés nem következik be, amíg kritikus szálak (amiket prioritás ad meg) feladatot hajtanak
    végre. De ez az algoritmus sem garantálja, hogy kritikus szálak sosem lesznek felfüggesztve. Ráadásul a Java RTS specifikáció definiál hatókörös és halhatatlan (immortal)
    memóriaterületeket is. Egy hatókört (scope) úgy lehet definiálni, hogy egy adott metódust megjelölünk egy hatókörös memóriaterület kezdetének. Azon metódus végrehajtása során minden
    lefoglalt objektum a hatókörös memóriaterület részének tekintett. Amikor a metódus végrehajtása befejeződött és a hatókörös memóriaterületre nincs többé szükség, minden ott lefoglalt
    objektum törölhetővé válik. Tulajdonképpeni szemétgyűjtés nem történik, a hatókörös memóriaterületen foglalt objektumok felszabadulnak és az összes használt memóriaterület azonnal
    újrahasznosítódik, miután a definiált hatókör végetér.</p>
  <p class="bekezd">A halhatatlan objektumok az immortal memóriaterületen foglalódnak le és sosem vesznek részt a szemétgyűjtésben, ami nagy előny. Viszont ezeknek sosem szabad
    hatókörös objektumokra hivatkozniuk, mert az inkonzisztenciához vezetne, mivel a hatókörös objektum anélkül törlődik, hogy arra hivatkozó referenciaellenőrzés történne.</p>
  <p class="bekezd">Ez a két tulajdonság olyan memóriaszervezési lehetőséget ad a kezünkbe, ami a Java esetén egyébként nem létezik és lehetővé teszi, hogy minimalizáljuk a GC
    megjósolhatatlanságát a válaszidőnkben. A hátránya az, hogy ez nem része a szabványos JDK-nak, tehát némi kódmódosítás szükséges hozzá, nameg az alkalmazás átfogó ismerete. (És ha
    jobban belegondolunk, ezek bevezetésével visszatértünk a szemétgyűjtés nélküli programozáshoz...)</p>
  <p class="bekezd">Az IBM WebSphere és a JRockit is biztosít valósidejű szemétgyűjtőket. Az IBM a sajátját úgy reklámozza, mint ami 1 ms-nál kisebb megállásokat biztosít. A JRockit egy
    determinisztikus szemétgyűjtőt ad, aminél a legnagyobb GC időt be lehet konfigurálni. Vannak egyébként olyan JVM-ek is, mint például az Azul Systems Zing nevű JVM-e, ami úgy próbálja
    megoldani ezt a problémát, hogy teljesen megszünteti a teljes megállási eseményt a szemétgyűjtésből. (És van számos valósidejű Java implementáció is.)</p>
  <h2>Előre a paraméterezés és tesztelés útján</h2>
  <p class="bekezd">Összefoglalva nézzük át, a különböző parancssori beállításokkal milyen szemétgyűjtést lehet beállítani az egyes generációkra. A sor a young generációs, az oszlop
    pedig az old generációs szemétgyűjtőt tartalmazza. Az üresen hagyott cellakonfigurációknak nincs értelme.</p>
  <table cellpadding="0" cellspacing="0" width="95%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b></b></th>
        <th class="normal"><b>Old Gen.</b></th>
        <th class="normal"><b></b></th>
        <th class="normal"><b></b></th>
        <th class="normal"><b></b></th>
        <th class="normal"><b></b></th>
      </tr>
      <tr>
        <th class="normal"><b>Young Gen.</b></th>
        <th class="normal"><b></b></th>
        <th class="normal"><b>DefNew</b></th>
        <th class="normal"><b>ParNew</b></th>
        <th class="normal"><b>Scavenge</b></th>
        <th class="normal"><b>G1</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"><b>Serial</b></td>
        <td style="border-right: 1px solid white;">-XX:+UseSerialGC</td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;">-XX:+UseParallelGC</td>
        <td style="border-right: 1px solid white;"></td>
      </tr>
      <tr class="tr2">
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"><b>Parallel</b></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;">-XX:+UseParallelOldGC</td>
        <td style="border-right: 1px solid white;"></td>
      </tr>
      <tr class="tr1">
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"><b>CMS</b></td>
        <td style="border-right: 1px solid white;">-XX:-UseParNewGC -XX:+UseConcMarkSweepGC<sup>1</sup></td>
        <td style="border-right: 1px solid white;">-XX:+UseConcMarkSweepGC</td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"></td>
      </tr>
      <tr class="tr2">
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"><b>i-CMS</b></td>
        <td style="border-right: 1px solid white;">-XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGC -XX:-UseParNewGC<sup>1</sup></td>
        <td style="border-right: 1px solid white;">-Xincgc<sup>1</sup></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"></td>
      </tr>
      <tr class="tr1">
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"><b>G1</b></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;"></td>
        <td style="border-right: 1px solid white;">-XX:+UseG1GC</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <sup>1</sup>: Java 8-tól deprecated
  </p>
  <div class="keretes">
    <p>Gyakran van szükség rá, hogy egy futó JVM-ről diagnosztikai adatokat kérdezzünk le, például pont azért, hogy megtudjuk, milyen GC-t használ vagy hogy áll a heap. Erre a JDK több
      parancssori eszközt biztosít, ezeket én is használtam a cikk írása közben (a JDK bin könyvtárában csücsülnek):</p>
    <p>
      <b>jps</b> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jps.html">dokumentáció</a>): egyszerű kilistázása a
      gazdagépen futó JVM-eknek. (Távoli gépen futó JVM-eket is el lehet vele érni.) Paraméterek nélkül is hívható, de a következő módon a JVM-eknek átadott paramétereket is megkapjuk:
    </p>
    <pre class="programkod">jps -vVm</pre>
    <p>
      <b>jcmd</b> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jcmd.html">dokumentáció</a>): a jps-hez hasonló, de
      annál bővebb lehetőségeket biztosító eszköz (bár távoli JVM-ekhez attól eltérően nem alkalmas). Paraméterek nélkül hívva szintén kilistázza a futó JVM-eket. Paraméterként meg lehet
      adni neki a kívánt processz azonosítóját majd pedig egy diagnosztikai parancsot. Az adott JVM diagnosztikai parancsait a help paranccsal kérhetjük le. Tehát:
    </p>
    <pre class="programkod">jcmd &lt;PID&gt; help</pre>
    <p>majd pedig - ha például listázott ilyen lehetőséget - a JVM flag-jeinek lekérdezése:</p>
    <pre class="programkod">jcmd &lt;PID&gt; VM.flags</pre>
    <p>Egyszerre csak egy parancs adható át a JVM-nek és egyes parancsokhoz további paraméterek megadása is lehetséges.</p>
    <p>
      <b>jinfo</b> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jinfo.html">dokumentáció</a>): egy nem támogatott,
      de azért meglévő eszköz, aminek a JVM pid-jét kell paraméterként megadni és rendkívül széles körű információkat ad vissza a JVM rendszertulajdonságairól, flag-jeiről.
    </p>
    <p>
      <b>jstat</b> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jstat.html">dokumentáció</a>): szintén nem
      támogatott, de azért meglévő eszköz statisztikai adatok lekérdezésére. Paraméterként egy statisztikai parancsot vár (elég sok mindent tud) és szintén a kívánt szál PID-jét. Ezt a
      dokumentáció lvmid-nek titulálja, mert szintén lehet vele távoli JVM-eket is birizgálni, ekkor host-ot és portot is vár, de helyi gépen egy egyszerű PID-del is megelégszik. Ezzel a
      paranccsal főként a szemétgyűjtő működésével kapcsolatos információkat kérhetjük le, például:
    </p>
    <pre class="programkod">jstat -gcutil &lt;PID&gt;</pre>
    <p>A jstat-nak azt is meg lehet adni, hogy mennyi időközönként (vagy hány alkalommal) mutassa meg a JVM állapotát. Ha ezt 1 másodpercenként szeretnénk megtenni:</p>
    <pre class="programkod">jstat -gcutil &lt;PID&gt; 1s</pre>
    <p>&quot;s&quot; helyett ezredmásodperceket is megadhatunk &quot;ms&quot; jelöléssel.</p>
  </div>
  <p class="bekezd">
    Amint azt bizonyára mindenki jól tudja, a Java-ban vannak szabványos paraméterek, amiket minden JVM megért. Ilyen például a <span class="programkod">-version</span>. Vannak nem
    szabványosak, amelyek egyáltalán nem biztos, hogy minden JVM-ben megvannak. Ezek -X-szel kezdődnek. Ilyen például a HotSpot-ban lévő <span class="programkod">-Xmx</span> a memória
    beállítására. Ezen kívül a -XX-szel kezdődőek speciális futásidejű viselkedéseket vezérelnek. Ezek egy részét : utáni + vagy - segítségével lehet kapcsolni. Ilyenek például a fenti,
    adott GC-típust megadó paraméterek, vagy a <span class="programkod">-XX:+PrintGCDetails</span>, amivel a bővebb GC logolást lehet be- (<span class="programkod">-XX:+PrintGCDetails</span>)
    vagy kikapcsolni (<span class="programkod">-XX:-PrintGCDetails</span>). A szemétgyűjtőre egyetlen szabványos JVM paraméter vonatkozik:
  </p>
  <p class="bekezd">
    <b><span class="programkod">-verbose:gc</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">általános információk megjelenítése minden egyes GC-ről a szabványos kimenetre</p>
  <p class="bekezd">A HotSpot azért ennél már jóval több saját paraméterezést tartalmaz a szemétgyűjtőkkel kapcsolatban:</p>
  <p class="bekezd">
    <b><span class="programkod">-Xloggc:filename</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    a GC logolást egy megadott fájlba irányítja át. Amennyiben ezzel egyszerre a <span class="programkod">-verbose:gc</span> paraméter is meg van adva, azt a JVM figyelmen kívül hagyja.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-Xnoclassgc</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    az osztályok szemétgyűjtőzésének kikapcsolása. Ezzel megtakaríthatunk némi időt a GC megállások során, viszont így az osztály objektumok érintetlenek maradnak és folyamatosan élőnek
    lesznek jelölve. Így számítani kell nagyobb memóriafoglalásra és esetleg még egy <span class="programkod">java.lang.OutOfMemoryError</span> is felbukkanhat a láthatáron.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+DisableExplicitGC</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    letiltja az explicit <span class="programkod">System.gc()</span> hívások feldolgozását. Ezután természetesen továbbra is meg lehet hívni, csak sunyi módon semmi nem fog történni.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+ExplicitGCInvokesConcurrent</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    lehetővé teszi, hogy a <span class="programkod">System.gc()</span> hívás egy konkurens szemétgyűjtést indítson el. Csak a <span class="programkod">-XX:+UseConcMarkSweepGC</span>
    paraméterrel együtt van értelme.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">ugyanaz mint az előző, csak még a nem használt osztályokat is kipucolja.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:InitiatingHeapOccupancyPercent=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    beállítja, hogy a konkurens GC a heap milyen kihasználtságánál induljon el (<i>N</i> százalékban). Ezt a paramétert azon szemétgyűjtők használják, amelyek a konkurens GC ciklust a
    teljes heap és nem csak egy generáció foglaltsága alapján indítják (például a G1). 0 érték megadása folyamatos GC-zést jelent.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:MaxGCPauseMillis=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">beállít egy előirányzott maximumot GC-zés időtartamára (ezredmásodpercekben). Ez csak egy irányelvet ad meg, amihez a JVM megpróbálja
    tartani magát. Alapértelmezetten egyébként nincs ilyen időtartam a JVM-ben.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:MaxHeapFreeRatio=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">beállítja, hogy egy GC után mennyi lehet a heap maximális szabad területe. Amennyiben a szabad terület ezen érték fölé megy, a heap
    leméretezésre kerül (vagyis megnő a virtuális terület). Alapértelmezetten ennek értéke 70%.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:MaxTenuringThreshold=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a tenuring threshold küszöbérték beállítása. Alapértelmezett értéke a parallel collector esetén 15, a CMS esetén 6.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:MinHeapFreeRatio=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">egy GC után a minimális engedélyezett szabad heap terület százalékban. Amennyiben a szabad heap terület ezen érték alá megy, a mérete
    megnövelődik (a virtuális terület csökken). Alapértelmezett értéke 40%.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+ParallelRefProcEnabled</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    azon alkalmazásoknál, amelyek intenzíven használják a <span class="programkod">java.lang.ref.Reference</span> osztályt (vagyis a gyenge referenciákat), a GC sok időt tölthet annak
    eldöntésével, hogy mit lehet a nem erős referenciákból kidobni és mit nem. A remark fázis ezt alapesetben egy szálon teszi. Ez a paraméter engedélyezi ezen referenciák többszálú
    feldolgozását.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintAdaptiveSizePolicy</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">plusz információk kiírása a generációk adaptív, vagyis igény szerinti átméretezéséről.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGC</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">GC logolás bekapcsolása.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCApplicationConcurrentTime</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">kiírja a logba, mennyi idő telt el az utolsó GC miatti megállás óta.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCApplicationStoppedTime</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">kiírja a logba, mennyi ideig tartott a GC megállás.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCDateStamps</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">minden egyes GC eseményhez beírja a logba a dátumot és időt.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCDetails</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">bővebb GC logolás bekapcsolása.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCTaskTimeStamps</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">minden egyes GC szálhoz időbélyeg írása a logba</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintGCTimeStamps</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">időbélyeg írása a logba minden egyes GC eseményhez. Ez relatív időbélyeg, vagyis a JVM indulása óta eltelt időt mutatja.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+PrintTenuringDistribution</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">tenuring információk megjelenítése a logban, vagyis hogy milyen mennyiségű oobjektum van adott életkorban. Ilyesmi lesz az eredménye:</p>
  <pre class="programkod">    - age 1: 28992024 bytes, 28992024 total
    - age 2: 1366864 bytes, 30358888 total
    - age 3: 1425912 bytes, 31784800 total
    ...</pre>
  <p class="bekezd" style="text-indent: 40px;">Az age 1 objektumok a legfiatalabb túlélők, amik épp most kerültek az édenről a survivor területre.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+ScavengeBeforeFullGC</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">minden full GC előtt engedélyez egy minor GC-t. Ez alapértelmezetten be van kapcsolva és az Oracle nyomatékosan javasolja, hogy ne is
    kapcsolja ki senki. Ha lefut a full GC előtt egy minor GC, akkor csökken az old generációból a young generációra hivatkozó referenciák száma.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:StringDeduplicationAgeThreshold=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">amikor a String objektumok elérik a megadott életkort (vagyis ennyiszer élték túl a szemétgyűjtést), alkalmassá válnak a deduplikálás
    elvégzésére. Megjegyzendő viszont, hogy azon sztringek, amelyek már ezt megelőzően az old generációba promotálódtak, mindenképpen részt vesznek a deduplikációban, az életkoruktól
    függetlenül. Alapértelmezett értéke 3.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+UseGCOverheadLimit</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a szemétgyűjtő algoritmusok OutOfMemoryError kivételt dobnak, ha az alkalmazás futásának már túl sok ideje esik a GC-re: ha a teljes idő
    több, mint 98%-a a GC-vel telik és a heap kevesebb, mint 2%-át sikerül ennek során felszabadítani. Ez a tulajdonság meggátolja, hogy az alkalmazások túl sok ideig fussanak anélkül, hogy
    lényegi munkát végeznének, mert a heap túl kicsi. Ezzel a paraméterrel kikapcsolhataó ez a funkció, ha szükséges. CMS esetén a szabály annyiban módosul, hogy a konkurens módon futó rész
    nem számolódik bele a 98%-ba, csak a teljes megállási esemény.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+UseStringDeduplication</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a sztring deduplikálás bekapcsolása. Ennek a használatához a G1 szemétgyűjtőt is be kell kapcsolni.</p>
  <div class="keretes">
    <p>
      Már láttuk, hogy a sok sztring objektum memóriafoglalásának csökkentésére a Java tartalmaz egy internalizálás nevű funkciót. A Java 8-ban emellett megjelent egy sztring deduplikálás
      nevű új tulajdonság, ami előnyt kovácsol abból, hogy a sztringek belsejében <span class="programkod">final</span> típusú karaktertömbök vannak, tehát a JVM tud velük huncutkodni. A
      deduplikálás alapértelmezetten ki van kapcsolva és használatához G1 szemétgyűjtőre is szükség van.
    </p>
    <p>UTF-16-os sztringek esetén minden egyes karakterhez két bájt szükséges. Nem szokatlan, hogy egy program memóriafoglalásának 30%-át a sztringek teszik ki. Ha a deduplikálás be van
      kapcsolva, akkor a szemétgyűjtő egy sztring objektumnál veszi a karaktertömbök hash értékét és gyenge referenciával eltárolja a tömbre hivatkozva. Amikor talál egy másik sztringet
      ugyanolyan hash kóddal, karakterről karakterre összehasonlítja a kettőt. Ha megegyeznek, akkor az egyik sztringet úgy módosítja, hogy a karaktertömbjének referenciája a másik
      sztringre mutasson. Ezután az első karaktertömbre már nem hivatkozik más, így a szemétgyűjtő ki tudja pucolni. Ennek a folyamatnak persze van némi többletköltsége, de szigorú korlátok
      vezérlik. Például ha egy sztringre sokáig nem találni duplikátumokat, akkor többé már nem is keres hozzá a G1.</p>
    <p>Ez a funkció elsőként a Java 8 update 20-ban jelent meg. Tekintsük a következő kódot:</p>
    <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">import</span> java.util.LinkedList;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> Durvasag {

    <span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">final</span> LinkedList&lt;String&gt; lotsOfStrings = <span
        class="java_keyword">new</span> LinkedList&lt;&gt;();

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Exception {
        <span class="java_keyword">int</span> iteration = 0;
        <span class="java_keyword">while</span> (true) {
            <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0; i &lt; 100; i++) {
                <span class="java_keyword">for</span> (<span class="java_keyword">int</span> j = 0; j &lt; 1000; j++) {
                    lotsOfStrings.add(<span class="java_string">"String "</span> + j);
                }
            }
            iteration++;
            System.out.println(<span class="java_string">"Survived iteration: "</span> + iteration);
            Thread.sleep(100);
        }
    }
}</pre>
    <p>
      Futtassuk ezt Java 8 alatt a következő JVM paraméterekkel: <span class="programkod">-Xmx256m -XX:+UseG1GC</span>
    </p>
    <p>
      A program 30 iteráció után <span class="programkod">java.lang.OutOfMemoryError</span> kivétellel ér véget.
    </p>
    <p>
      Most futtassuk a következőképp: <span class="programkod">-Xmx256m -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics</span>
    </p>
    <p>Így már jelentősen tovább fut és 50 iterációig elér. A JVM most ki is írja, hogy mit csinál:</p>
    <pre class="programkod">[GC concurrent-string-deduplication, 8323.1K->0.0B(8323.1K), avg 99.6%,
 0.0209513 secs]
   [Last Exec: 0.0209513 secs, Idle: 0.2464255 secs, Blocked: 0/0.0000000
    secs]
      [Inspected:          213500]
         [Skipped:              0(  0.0%)]
         [Hashed:          213500(100.0%)]
         [Known:                0(  0.0%)]
         [New:             213500(100.0%)   8323.1K]
      [Deduplicated:       213500(100.0%)   8323.1K(100.0%)]
         [Young:               12(  0.0%)    480.0B(  0.0%)]
         [Old:             213488(100.0%)   8322.6K(100.0%)]
   [Total Exec: 4/0.0396959 secs, Idle: 4/0.4761534 secs, Blocked: 0/0.0000000
    secs]
      [Inspected:          404033]
         [Skipped:              0(  0.0%)]
         [Hashed:          403112( 99.8%)]
         [Known:              768(  0.2%)]
         [New:             403265( 99.8%)     15.4M]
      [Deduplicated:       401960( 99.7%)     15.3M( 99.6%)]
         [Young:               21(  0.0%)    832.0B(  0.0%)]
         [Old:             401939(100.0%)     15.3M(100.0%)]
   [Table]
      [Memory Usage: 64.6K]
      [Size: 2048, Min: 1024, Max: 16777216]
      [Entries: 2073, Load: 101.2%, Cached: 0, Added: 2073, Removed: 0]
      [Resize Count: 1, Shrink Threshold: 1365(66.7%), Grow Threshold:
       4096(200.0%)]
      [Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
      [Age Threshold: 3]
   [Queue]
      [Dropped: 0]</pre>
    <p>A log nagyon jó, mert nem nekünk kell összeadni az egyes futások adatait, hanem azok is ott láthatók a Total Exec. részben.</p>
    <p>A logrészlet a deduplikáció negyedik futását mutatja, ami 20 ms-ig tartott és nagyjából 210 ezer sztringet nézett át. Ezek mindegyike új volt, vagyis eddig még nem vizsgált. Ezek
      a számok persze némileg máshogy néznek ki valós alkalmazásokban, ahol a sztringeket többször átadogatják egymásnak a metódusok, így néhányuk ekkorra már szerepel az átnézésben vagy
      már van hashkódja (mint tudjuk, a sztringek hash kódja lusta kiértékeléssel történik, tehát csak akkor amikor elsőként szükség van rá). Itt még mind a 213500 sztring most lett
      elsőként szép magyar kifejezéssel szólva hash-elve.</p>
    <p>A fenti példában minden sztringet deduplikálni (újabb szép magyar szó) lehetett, így 8,1 MB adatot tudott kipucolni a memóriából. A Table rész információt ad a belső
      adminisztrációs tábláról, a Queue pedig megmutatja, hogy mennyi deduplikációs kérés lett eldobva a terhelés miatt; ez a tulajdonság része a többletköltség-csökkentő mechanizmusnak.</p>
    <p>Felmerül a kérdés: hogy viszonyul ez az egész a sztring internalizáláshoz? Nos amint azt bizonyára minden szemfüles olvasó észrevette, nagyon hasonló a kettő, viszont az
      internalizálás ugyanazt a String példányt használja fel, nem csak a karaktertömböt. Az viszont különbség, hogy a futásidőben dinamikusan létrehozott sztringek esetén (például amikor
      adatbázisból olvasunk be sok szöveges adatot), az internalizálás explicit odafigyelést igényel fejlesztés közben, a deduplikálást viszont a háttérben automatikusan elvégzi a G1
      (persze csak ha be van kapcsolva). Mivel mindez aszinkron és a GC-vel konkurens módon, annak futása közben működik, ezért viszonylag kis többletköltsége van. Ha a példaprogramban nem
      lenne ott a sleep, akkor persze túl sok munka esne a szemétgyűjtőre és a deduplikálás egyáltalán nem futna. De ez a probléma lényegében csak a példakódnál jelentkezik, valódi
      alkalmazásoknak általában van némi szabad idejük, hogy ezt a GC elvégezhesse.</p>
  </div>
  <p class="bekezd">
    <b>A specifikusan parallel collector szemétgyűjtőhöz való parancssori kapcsolók a következők:</b>
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:ParallelGCThreads=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a parallel GC által használható szálak száma a young és old generáció szemétgyűjtéséhez. Alapértelmezett értéke attól függ, mennyi mag áll
    a JVM rendelkezésére.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:InitialSurvivorRatio=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    a kezdeti survivor arány. Ezt a méretet a JVM az alkalmazás viselkedése alapján futás közben módosítja, hacsak nincs kikapcsolva az adaptív átméretezés (<span class="programkod">-XX:-UseAdaptiveSizePolicy</span>).
    A következő képlet ajánlott az arány kiszámításához (S a survivor terület, Y a young generáció, R a kezdeti survivor terület hányadosa):
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    <span class="programkod">S=Y/(R+2)</span>
  </p>
  <p class="bekezd" style="text-indent: 40px;">Az egyenletben a 2 jelenti a két survivor területet. Minél nagyobb érték van megadva a kezdeti survivor terület arányának, annál kisebb
    lesz a kezdeti survivor terület mérete. Alapértelmezetten ez az arány 8, tehát 2 MB-os young generációnál 0,2 MB.</p>
  <p class="bekezd">
    <b>A specifikusan CMS-hez való parancssori kapcsolók a következők (az i-CMS-t már korábban tárgyaltam):</b>
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:CMSInitiatingOccupancyFraction=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    megadhatjuk vele, hogy az old generáció hány százalékos telítettségénél (<i>N</i>) induljon el a CMS szemétgyűjtés. Bármely negatív érték (az alapértelmezett -1 is) azt jelenti, hogy a
    <span class="programkod">-XX:CMSTriggerRatio</span> paraméter határozza meg ezt az értéket.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+CMSScavengeBeforeRemark</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">láttuk, hogy a CMS-nek két teljes megállási eseménye van (initial mark és remark). Ezek gyorsabban lefuthatnak, ha nem kell a young és old
    generáció között még külön referenciákat vizsgálniuk, ezért ezzel a paraméterrel bekapcsolhatjuk, hogy a remark fázis előtt fusson le egy minor GC. Ha viszont bekapcsoljuk, akkor ez
    esetben a GC logokban akkor is látunk minor GC-ket, ha az éden terület még nincs is tele. Persze az nem csak emiatt az opció miatt lehet, hanem amiatt is, mert a programunk olyan nagy
    objektumokat szeretne lefoglalni, ami már nem fér el a maradék szabad helyen.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:CMSTriggerRatio=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a MinHeapFreeRatio azon százaléka, amennyi lefoglalt, mielőtt egy CMS szemétgyűjtés elindul.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:ConcGCThreads=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a konkurens GC által használt szálak száma. Alapértelmezett értékét a JVM maga határozza meg az elérhető magok számából.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+UseCMSInitiatingOccupancyOnly</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">annak beállítása, hogy a CMS indítását csak a kihasználtság értéke vezérelje. Alapesetben ez a kapcsoló ki van kapcsolva és más tényezők
    is részt vesznek a CMS indításának meghatározásában.</p>
  <p class="bekezd">
    <b>A specifikusan G1 szemétgyűjtőhöz való parancssori kapcsolók a következők:</b>
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:G1HeapRegionSize=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">
    a régiók méretének beállítása, így nem a JVM-re bízzuk ennek eldöntését. <i>N</i> értéke 1m és 32m között lehet.
  </p>
  <p class="bekezd">
    <b><span class="programkod">-XX:+G1PrintHeapRegions</span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">logolás során kiírja, hogy a G1 mely régiókat foglalta le és szabadította fel.</p>
  <p class="bekezd">
    <b><span class="programkod">-XX:G1ReservePercent=<i>N</i></span></b>
  </p>
  <p class="bekezd" style="text-indent: 40px;">a szabadnak fenntartott memória százalékértékének beállítása. Növelésével lehet csökkenteni annak a rizikóját, hogy az objektumok új
    régiókba másolásakor elfogyjon a memória. Más szóval csökkenthető a promotion failure jelentkezésének valószínűsége. Amikor ezt módosítjuk (alapértelmezetten 10%), a heap méretét is
    módosítsuk ennek megfelelően.</p>
  <h2>Kedves naplóm!</h2>
  <p class="bekezd">A következőkben az összes típusú szemétgyűjtő esetén (a valós idejű és a távoli kivételével) megnézzük, hogy milyen bejegyzéseket írnak a logba és azokat hogyan
    értelmezzük (Java 8 esetén). A logokat az alábbi példaprogram futtatásával próbáltam kicsikarni. A kód elindít két job-ot minden 100 ezredmásodpercben. Mindkét job adott élettartammal
    emulál objektumokat: létrehozza, hagyja őket élni egy adott ideig, aztán megfeledkezik róluk, lehetővé téve, hogy a GC felszabadítsa a memóriát.</p>
  <pre class="programkod">
<span class="java_keyword">package</span> hu.egalizer.gctest;

<span class="java_keyword">import</span> java.util.ArrayDeque;
<span class="java_keyword">import</span> java.util.Deque;
<span class="java_keyword">import</span> java.util.concurrent.Executors;
<span class="java_keyword">import</span> java.util.concurrent.ScheduledExecutorService;
<span class="java_keyword">import</span> java.util.concurrent.TimeUnit;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> GcTester <span class="java_keyword">implements</span> Runnable {

    <span class="java_keyword">private</span> <span class="java_keyword">static</span> ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

    <span class="java_keyword">private</span> Deque&lt;<span class="java_keyword">byte</span>[]&gt; deque;
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> objectSize;
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> queueSize;

    <span class="java_keyword">public</span> GcTester(<span class="java_keyword">int</span> objectSize, <span class="java_keyword">int</span> ttl) {
        <span class="java_keyword">this</span>.deque = <span class="java_keyword">new</span> ArrayDeque&lt;<span class="java_keyword">byte</span>[]&gt;();
        <span class="java_keyword">this</span>.objectSize = objectSize;
        <span class="java_keyword">this</span>.queueSize = ttl * 1000;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> run() {
        <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0; i &lt; 100; i++) {
            deque.add(<span class="java_keyword">new</span> <span class="java_keyword">byte</span>[objectSize]);
            <span class="java_keyword">if</span> (deque.size() &gt; queueSize) {
                deque.poll();
            }
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> InterruptedException {
        executorService.scheduleAtFixedRate(<span class="java_keyword">new</span> GcTester(200 * 1024 * 1024 / 1000, 5),
            0, 100, TimeUnit.MILLISECONDS);
        executorService.scheduleAtFixedRate(<span class="java_keyword">new</span> GcTester(50 * 1024 * 1024 / 1000, 120),
            0, 100, TimeUnit.MILLISECONDS);
        TimeUnit.MINUTES.sleep(10);
        executorService.shutdownNow();
    }
}</pre>
  <p class="bekezd">
    A logokhoz a JVM-et az alábbi paraméterekkel kell indítani: <span class="programkod">-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps</span>
  </p>
  <h3>Serial Collector</h3>
  <p class="bekezd">
    Egy GC log részlet nagyjából a következőképp néz ki (a példaprogramot <span class="programkod">-Xmx512m</span> paraméterrel futtatva):
  </p>
  <div class="programkod">
    <pre>2017-09-05T22:16:44.677+0200: 1.581: [GC (Allocation Failure) 2017-09-05T22:16:44.678+0200: 1.581: [DefNew: 139765K->17429K(157248K), 0.0936400 secs] 362075K->361613K(506816K), 0.0937556 secs] [Times: user=0.08 sys=0.02, real=0.09 secs] 
2017-09-05T22:16:45.177+0200: 2.081: [GC (Allocation Failure) 2017-09-05T22:16:45.177+0200: 2.081: [DefNew: 157184K->157184K(157248K), 0.0000394 secs]2017-09-05T22:16:45.177+0200: 2.081: [Tenured: 344183K->349561K(349568K), 0.0605620 secs] 501368K->500745K(506816K), [Metaspace: 2831K->2831K(1056768K)], 0.0607333 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 
2017-09-05T22:16:45.239+0200: 2.142: [Full GC (Allocation Failure) 2017-09-05T22:16:45.239+0200: 2.142: [Tenured: 349561K->349547K(349568K), 0.0450676 secs] 506685K->506671K(506816K), [Metaspace: 2831K->2831K(1056768K)], 0.0451294 secs] [Times: user=0.05 sys=0.00, real=0.04 secs]</pre>
  </div>
  <p class="bekezd">
    <b>Minor GC</b>
  </p>
  <p class="bekezd">Ebből nézzük az első sort részletesen:</p>
  <pre class="programkod">2017-09-05T22:16:44.677+0200<sup>1</sup>: 1.581<sup>2</sup>: [GC<sup>3</sup> (Allocation Failure<sup>4</sup>) 2017-09-05T22:16:44.678+0200:
    1.581: [DefNew<sup>5</sup>: 139765K->17429K<sup>6</sup>(157248K)<sup>7</sup>, 0.0936400 secs] 362075K->361613K<sup>8</sup>(506816K)<sup>9</sup>,
	0.0937556 secs<sup>10</sup>] [Times: user=0.08 sys=0.02, real=0.09 secs]<sup>11</sup>
  </pre>
  <ol>
    <li><b>2017-09-05T22:16:44.677+0200</b>: a GC ciklus indulásának időpontja</li>
    <li><b>1.581</b>: a GC indulásának időpontja a JVM indulásához képest relatívan másodpercekben számolva. A továbbiakban ezt és az előzőt összevonom a rövidítés érdekében.</li>
    <li><b>GC</b>: a GC típusát megkülönböztető jelzés. Ez minor GC-t jelez.</li>
    <li><b>Allocation Failure</b>: a szemétgyűjtő indulásának oka. Ebben az esetben az az ok, hogy egy új adat nem fér be a young generáció szabad területére.</li>
    <li><b>DefNew</b>: a használt szemétgyűjtő neve. DefNew, vagyis teljes megállási esemény alatt futó, mark-copy egyszálú szemétgyűjtő a young generációban.</li>
    <li><b>139765K->17429K</b>: a young generáció kihasználtsága a szemétgyűjtés előtt és után</li>
    <li><b>(157248K)</b>: a young generáció teljes mérete</li>
    <li><b>362075K->361613K</b>: a teljes használt heap a szemétgyűjtés előtt és után</li>
    <li><b>(506816K)</b>: a teljes elérhető heap (virtuális területek nélkül)</li>
    <li><b>0.0937556 secs</b>: a szemétgyűjtési esemény hossza másodpercekben</li>
    <li><b>[Times: user=0.08 sys=0.02, real=0.09 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának időtartama. Mivel Serial Collector mindig csak egy szálat használ, a real idő a user és sys idők összege</li>
      </ul></li>
  </ol>
  <p class="bekezd">Most már látjuk, mi történik egy serial minor GC esetén a heap-en. A szemétgyűjtés előtt a heap kihasználtsága 362075 KB volt. Ebből a young generáció megevett
    139765 KB-ot, az old generáció pedig ebből kiszámolható: 222310 KB volt. Egy ennél is fontosabb információ a következő számcsoportból látható: miután a szemétgyűjtés lement, a young
    generáció használata 122336 KB-tal csökkent, de a teljes heap csak 462 KB-tal. Ebből ki tudjuk számolni, hogy 121874 KB objektum promotálódott a young generációból az old-ba.</p>
  <p class="bekezd">
    <b>Full GC</b>
  </p>
  <p class="bekezd">
    A következő két sor tartalmazza a Full GC információit. Első látásra itt egy kis zavar van, mert a Full GC sor csak a tenured területtel és a metaspace-szel foglalkozik. Akkor mitől
    Full GC? Nos valójában a Full GC be van ágyazva egy minor GC-be. Amikor egy minor GC sikertelen lesz (mint itt a második sorban), akkor az átvált egy Full GC-be és amikor az lefut,
    akkor számít a szemétgyűjtés sikeresnek. Ez történik ebben a két sorban. Ha a programot <span class="programkod">-XX:+PrintGCDetails</span> helyett <span class="programkod">-XX:+PrintGC</span>
    parancssori opcióval futtatjuk, akkor a második sor meg sem jelenik a logban, csak a harmadik, amely a Full GC-ről tudósít. Lássuk mi történt ebben az esetben:
  </p>
  <pre class="programkod">2017-09-05T22:16:45.177+0200: 2.081<sup>1</sup>: [GC (Allocation Failure) 2017-09-05T22:16:45.177+0200: 2.081:
    [DefNew: 157184K->157184K(157248K), 0.0000394 secs<sup>2</sup>]2017-09-05T22:16:45.177+0200: 2.081:
    [Tenured<sup>3</sup>: 344183K->349561K<sup>4</sup>(349568K)<sup>5</sup>, 0.0605620 secs<sup>6</sup>] 501368K->500745K<sup>7</sup>(506816K<sup>8</sup>),
    [Metaspace: 2831K->2831K(1056768K)<sup>9</sup>], 0.0607333 secs]
    [Times: user=0.06 sys=0.00, real=0.06 secs<sup>10</sup>]</pre>
  <ol>
    <li><b>2017-09-05T22:16:45.177+0200: 2.081</b>: a szemétgyűjtő indulásának két típusú időpontja</li>
    <li><b>DefNew: 157184K->157184K(157248K), 0.0000394 secs</b>: az előző log bejegyzéshez hasonló: allocation failure miatt elindult egy minor GC és a DefNew szemétgyűjtő típus
      lecsökkentette a young generációt 157184 KB-ról 0-ra. Megjegyzendő, hogy egy bug miatt a GC ezt hibásan jelzi és azt mutatja, mintha a young generáció továbbra is teljesen tele lenne.
      Ez az egész 0,0000394 másodpercig tartott.</li>
    <li><b>Tenured</b>: az old generáció szemétgyűjtőzése. A tenured során egyszálú, teljes megállási eseménnyel futó mark-sweep-compact szemétgyűjtő dolgozik a motorháztető alatt.</li>
    <li><b>344183K->349561K</b>: az old generáció kihasználtsága a szemétgyűjtő előtt és után</li>
    <li><b>(349568K)</b>: az old generáció mérete</li>
    <li><b>0.0605620 secs</b>: ennyi ideig tartott kipucolni az old generációt</li>
    <li><b>501368K->500745K</b>: a teljes heap kihasználtsága a young és old generáció szemétgyűjtőzése előtt és után</li>
    <li><b>506816K</b>: a JVM számára elérhető teljes heap</li>
    <li><b>[Metaspace: 2831K->2831K(1056768K)]</b>: az előzőekhez hasonló információ a metaspace szemétgyűjtőzéséről. Ahogy látható, az esemény során nem talált szemetet a
      metaspace-en.</li>
    <li><b>[Times: user=0.06 sys=0.00, real=0.06 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának valós időtartama. Mivel Serial Collector mindig csak egy szálat használ, a real idő a user és sys idők összege.</li>
      </ul></li>
  </ol>
  <p class="bekezd">A logból látszik, hogy a full GC ellenére sem sikerült a heap foglaltságát jelentősen csökkenteni ebben az esetben.</p>
  <h3>Parallel Collector</h3>
  <p class="bekezd">
    A példaprogramot <span class="programkod">-XX:+UseParallelOldGC</span> paraméterrel futtatva lássuk most a párhuzamos szemétgyűjtés jellemző logbejegyzéseit:
  </p>
  <div class="programkod">
    <pre>2017-09-06T12:49:58.309+0200: 6.261: [GC (Allocation Failure) [PSYoungGen: 93696K->40424K(134144K)] 313695K->312615K(483840K), 0.0195111 secs] [Times: user=0.05 sys=0.00, real=0.02 secs] 
2017-09-06T12:49:58.329+0200: 6.281: [Full GC (Ergonomics) [PSYoungGen: 40424K->0K(134144K)] [ParOldGen: 272191K->312551K(349696K)] 312615K->312551K(483840K), [Metaspace: 2836K->2836K(1056768K)], 0.0291154 secs] [Times: user=0.11 sys=0.00, real=0.03 secs]</pre>
  </div>
  <p class="bekezd">
    <b>Minor GC</b>
  </p>
  <p class="bekezd">Ebből nézzük az első sort részletesen:</p>
  <pre class="programkod">2017-09-06T12:49:58.309+0200: 6.261<sup>1</sup>: [GC<sup>2</sup> (Allocation Failure<sup>3</sup>) [PSYoungGen<sup>4</sup>: 93696K->40424K<sup>5</sup>
    (134144K<sup>6</sup>)] 313695K->312615K<sup>7</sup>(483840K<sup>8</sup>), 0.0195111 secs<sup>9</sup>]
    [Times: user=0.05 sys=0.00, real=0.02 secs]<sup>10</sup>
  </pre>
  <ol>
    <li><b>2017-09-06T12:49:58.309+0200: 6.261</b>: a GC ciklus indulásának két típusú időpontja</li>
    <li><b>GC</b>: a GC típusát megkülönböztető jelzés. Ez minor GC-t jelez.</li>
    <li><b>Allocation Failure</b>: a szemétgyűjtő indulásának oka. Ebben az esetben az az ok, hogy egy új adat nem fér be a young generáció szabad területére.</li>
    <li><b>PSYoungGen</b>: a használt szemétgyűjtő típusa: teljes megállási esemény során futó párhuzamos mark-copy collector dolgozik a young generáción (parallel scavenge)</li>
    <li><b>93696K->40424K</b>: a young generáció kihasználtsága a szemétgyűjtés előtt és után</li>
    <li><b>(134144K)</b>: a young generáció teljes mérete</li>
    <li><b>313695K->312615K</b>: a teljes használt heap a szemétgyűjtés előtt és után</li>
    <li><b>(483840K)</b>: a teljes elérhető heap (virtuális területek nélkül)</li>
    <li><b>0.0195111 secs</b>: a szemétgyűjtési esemény hossza másodpercekben</li>
    <li><b>[Times: user=0.05 sys=0.00, real=0.02 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának időtartama. Párhuzamos GC esetén ez nagyjából a (user+sys)/GC által használt szálak száma. Ez esetben 2 szál volt használatban. Mivel
          néhány művelet nem párhuzamosítható, a valós érték mindig túllépi kicsivel az arányt.</li>
      </ul></li>
  </ol>
  <p class="bekezd">Röviden tehát a gyűjtés előtt 313695 KB volt lefoglalva a heap-ből. Ebből a young generáció 134144 KB. Ez azt jelenti, hogy az old generáció 179551 KB volt. A
    szemétgyűjtő után a young generáció használata csökkent 53272 KB-tal, de a teljes heap használat csak 1080 KB-tal csökkent, vagyis 52192 KB promotálódott a young generációból az old
    generációba.</p>
  <p class="bekezd">
    <b>Full GC</b>
  </p>
  <pre class="programkod">2017-09-06T12:49:58.329+0200: 6.281<sup>1</sup>: [Full GC<sup>2</sup> (Ergonomics<sup>3</sup>) [PSYoungGen: 40424K->0K(134144K)]<sup>4</sup>
    [ParOldGen<sup>5</sup>: 272191K->312551K<sup>6</sup>(349696K<sup>7</sup>)] 312615K->312551K<sup>8</sup>(483840K<sup>9</sup>),
    [Metaspace: 2836K->2836K(1056768K)]<sup>10</sup>, 0.0291154 secs<sup>11</sup>]
    [Times: user=0.11 sys=0.00, real=0.03 secs]<sup>12</sup>
  </pre>
  <ol>
    <li><b>2017-09-06T12:49:58.329+0200: 6.281</b>: a szemétgyűjtő indulásának két típusú időpontja. Ha összevetjük az előző logbejegyzéssel, láthatjuk, hogy pontosan a megelőző minor
      GC után indult el.</li>
    <li><b>Full GC</b>: a szemétgyűjtő típusa: full GC, ami a young és old generációt is kipucolja</li>
    <li><b>Ergonomics</b>: a GC indításának oka. Ez most azt jelzi, hogy a JVM belső döntési mechanizmusa úgy gondolta, hogy itt az ideje egy kis szemetet gyűjteni.</li>
    <li><b>[PSYoungGen: 40424K->0K(134144K)]</b>: hasonló az előző bejegyzéshez: egy teljes megállási esemény alatt futó párhuzamos mark-copy szemétgyűjtés futott. A young generáció
      kihasználtsága 40424 KB-ról 0-ra ment, ami szokványos egy Full GC esetén.</li>
    <li><b>ParOldGen</b>: az old generációhoz használt szemétgyűjtő típusa. Ez esetben egy teljes megállási esemény alatt futó párhuzamos mark-sweep-compact collector.</li>
    <li><b>272191K->312551K</b>: az old generáció kihasználtsága a szemétgyűjtő előtt és után</li>
    <li><b>(349696K)</b>: az old generáció mérete</li>
    <li><b>312615K->312551K</b>: a teljes heap kihasználtsága a young és old generáció szemétgyűjtőzése előtt és után</li>
    <li><b>483840K</b>: a JVM számára elérhető teljes heap</li>
    <li><b>[Metaspace: 2836K->2836K(1056768K)]</b>: az előzőekhez hasonló információ a metaspace szemétgyűjtőzéséről. Ahogy látható, az esemény során nem talált szemetet a
      metaspace-en.</li>
    <li><b>0.0291154 secs</b>: a szemétgyűjtési esemény hossza másodpercekben</li>
    <li><b>[Times: user=0.11 sys=0.00, real=0.03 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának valós időtartama. Párhuzamos GC esetén ez nagyjából a (user+sys)/GC által használt szálak száma. Ez esetben 4 szál volt használatban.
          Mivel néhány művelet nem párhuzamosítható, a valós érték mindig túllépi kicsivel az arányt.</li>
      </ul></li>
  </ol>
  <h3>Concurrent Mark Sweep</h3>
  <p class="bekezd">
    A példaprogramot <span class="programkod">-XX:+UseConcMarkSweepGC</span> paraméterrel futtatva lássuk most a CMS szemétgyűjtés jellemző logbejegyzéseit:
  </p>
  <div class="programkod">
    <pre>2017-09-06T21:26:40.870+0200: 1.453: [GC (Allocation Failure) 2017-09-06T21:26:40.870+0200: 1.453: [ParNew: 39239K->4303K(39296K), 0.0354605 secs] 312408K->311387K(388864K), 0.0355703 secs] [Times: user=0.03 sys=0.09, real=0.04 secs]
2017-09-06T21:26:41.074+0200: 1.657: [GC (Allocation Failure) 2017-09-06T21:26:41.074+0200: 1.657: [ParNew: 39084K->39084K(39296K), 0.0000602 secs]2017-09-06T21:26:41.075+0200: 1.657: [CMS: 341794K->349365K(349568K), 0.0948730 secs] 380879K->380704K(388864K), [Metaspace: 2831K->2831K(1056768K)], 0.0975661 secs] [Times: user=0.09 sys=0.00, real=0.10 secs] 
2017-09-06T21:26:41.172+0200: 1.754: [GC (CMS Initial Mark) [1 CMS-initial-mark: 349365K(349568K)] 383368K(506816K), 0.0001412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.172+0200: 1.755: [CMS-concurrent-mark-start]
2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-mark: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.176+0200: 1.759: [GC (CMS Final Remark) [YG occupancy: 48992 K (157248 K)]2017-09-06T21:26:41.176+0200: 1.759: [Rescan (parallel) , 0.0002084 secs]2017-09-06T21:26:41.177+0200: 1.759: [weak refs processing, 0.0000179 secs]2017-09-06T21:26:41.177+0200: 1.759: [class unloading, 0.0001895 secs]2017-09-06T21:26:41.177+0200: 1.759: [scrub symbol table, 0.0003631 secs]2017-09-06T21:26:41.177+0200: 1.760: [scrub string table, 0.0000810 secs][1 CMS-remark: 349365K(349568K)] 398357K(506816K), 0.0009036 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.177+0200: 1.760: [CMS-concurrent-sweep-start]
2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-reset-start]
2017-09-06T21:26:41.178+0200: 1.761: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</pre>
  </div>
  <p class="bekezd">
    <b>Minor GC</b>
  </p>
  <pre class="programkod">2017-09-06T21:26:40.870+0200: 1.453<sup>1</sup>: [GC<sup>2</sup> (Allocation Failure<sup>3</sup>)
    2017-09-06T21:26:40.870+0200: 1.453: [ParNew<sup>4</sup>: 39239K->4303K<sup>5</sup>(39296K<sup>6</sup>), 0.0354605 secs<sup>7</sup>]
    312408K->311387K<sup>8</sup>(388864K<sup>9</sup>), 0.0355703 secs<sup>10</sup>] [Times: user=0.03 sys=0.09, real=0.04 secs]<sup>11</sup> 
  </pre>
  <ol>
    <li><b>2017-09-06T21:26:40.870+0200: 1.453</b>: a GC ciklus indulásának két típusú időpontja</li>
    <li><b>GC</b>: a GC típusát megkülönböztető jelzés. Ez minor GC-t jelez.</li>
    <li><b>Allocation Failure</b>: a szemétgyűjtő indulásának oka. Ebben az esetben az az ok, hogy egy új adat nem fér be a young generáció szabad területére.</li>
    <li><b>ParNew</b>: a használt szemétgyűjtő típusa: teljes megállási eseménynél futó párhuzamos mark-copy a young generáción, amit a CMS-hez terveztek</li>
    <li><b>39239K->4303K</b>: a young generáció kihasználtsága a szemétgyűjtés előtt és után</li>
    <li><b>(39296K)</b>: a young generáció teljes mérete</li>
    <li><b>0.0354605 secs</b>: a szemétgyűjtési esemény hossza másodpercekben cleanup nélkül</li>
    <li><b>312408K->311387K</b>: a teljes használt heap a szemétgyűjtés előtt és után</li>
    <li><b>(388864K)</b>: a teljes elérhető heap (virtuális területek nélkül)</li>
    <li><b>0.0355703 secs</b>: ennyi ideig tartott a GC-nek, hogy a young generáción lévő élő objektumokra megcsinálja a mark és copy műveleteket. Ez tartalmazza a CMS-sel való
      kommunikációs töbletköltséget, az elég régi objektumok promotálását az old generációra és némi végső cleanup műveletet a GC ciklus végén.</li>
    <li><b>[Times: user=0.03 sys=0.09, real=0.04 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának időtartama. Párhuzamos GC esetén ez nagyjából a (user+sys)/GC által használt szálak száma. Ez esetben 1 szál volt használatban.</li>
      </ul></li>
  </ol>
  <p class="bekezd">A fentiekből látható, hogy a szemétgyűjtés előtt a teljes felhasznált heap 312408 KB volt, ebből a young generáció 39239 KB. Ez azt jelenti, hogy az old generáció
    mérete 273169 KB volt. A szemétgyűjtő után a young generáció használata csökkent 34936 KB-tal, de a teljes heap használata csak 1021 KB-tal. Ez azt jelenti, hogy 33915 KB promotálódott
    a young generációból az oldba.</p>
  <p class="bekezd">
    <b>Full GC</b>
  </p>
  <p class="bekezd">Most hogy már belejöttünk a GC logok olvasásába, jöjjön egy némileg eltérő formájú log, csak hogy ne legyen minden olyan egyszerű. Az alábbiakban az old generációs
    CMS hosszú logja látható. Ezen is sorról sorra végig megyünk, viszont most nem nézzük át egyszerre a teljes logot, hanem részletekben haladunk, hogy lássuk a CMS fázisait. A teljes
    bejegyzés mindenesetre a következőképp néz ki:</p>
  <div class="programkod">
    <pre>2017-09-06T21:26:41.172+0200: 1.754: [GC (CMS Initial Mark) [1 CMS-initial-mark: 349365K(349568K)] 383368K(506816K), 0.0001412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.172+0200: 1.755: [CMS-concurrent-mark-start]
2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-mark: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.176+0200: 1.759: [GC (CMS Final Remark) [YG occupancy: 48992 K (157248 K)]2017-09-06T21:26:41.176+0200: 1.759: [Rescan (parallel) , 0.0002084 secs]2017-09-06T21:26:41.177+0200: 1.759: [weak refs processing, 0.0000179 secs]2017-09-06T21:26:41.177+0200: 1.759: [class unloading, 0.0001895 secs]2017-09-06T21:26:41.177+0200: 1.759: [scrub symbol table, 0.0003631 secs]2017-09-06T21:26:41.177+0200: 1.760: [scrub string table, 0.0000810 secs][1 CMS-remark: 349365K(349568K)] 398357K(506816K), 0.0009036 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.177+0200: 1.760: [CMS-concurrent-sweep-start]
2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-reset-start]
2017-09-06T21:26:41.178+0200: 1.761: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</pre>
  </div>
  <p class="bekezd">Azt se feledjük el, hogy young generációs minor GC-k az old generáció konkurens fázisai közben bármikor elindulhatnak. Ez esetben az alábbi eseményeket itt-ott
    megszakítják az előző fejezetben látott minor GC-k.</p>
  <p class="bekezd">
    <i style="text-decoration: underline;">1. fázis: initial mark</i>: az első teljes megállási esemény. Az old generáció összes olyan objektumának megjelölése, ami vagy közvetlen GC root
    vagy pedig a young generációról van hivatkozva.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.172+0200: 1.754<sup>1</sup>: [GC (CMS Initial Mark<sup>2</sup>) [1 CMS-initial-mark: 349365K<sup>3</sup>(349568K<sup>4</sup>)]
    383368K<sup>5</sup>(506816K<sup>6</sup>), 0.0001412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>7</sup>
  </pre>
  <ol>
    <li><b>2017-09-06T21:26:41.172+0200: 1.754</b>: a GC ciklus indulásának két típusú időpontja</li>
    <li><b>CMS Initial Mark</b>: a szemétgyűjtés fázisa: itt most &quot;initial mark&quot;</li>
    <li><b>349365K</b>: az old generáció kihasználtsága</li>
    <li><b>(349568K)</b>: az old generáció számára elérhető teljes heap</li>
    <li><b>383368K</b>: a teljes heap kihasználtsága</li>
    <li><b>(506816K)</b>: az elérhető heap memória mérete</li>
    <li><b>0.0001412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a megállás hossza, ismét csak user, system és real időkben megadva</li>
  </ol>
  <p class="bekezd">
    <i style="text-decoration: underline;">2. fázis: concurrent mark</i>: az old generáció átvizsgálása és az összes élő objektum megjelölése a korábban megjelöltekből kiindulva. Mivel ez a
    fázis konkurens és az alkalmazás menet közben módosíthatja az objektumokat, ezért itt nem biztos, hogy a menet végére minden élő objektum meg lesz jelölve.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.172+0200: 1.755: [CMS-concurrent-mark-start]
2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-mark<sup>1</sup>: 0.003/0.003 secs<sup>2</sup>]
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>3</sup>
  </pre>
  <ol>
    <li><b>CMS-concurrent-mark</b>: a szemétgyűjtés fázisa: &quot;concurrent mark&quot;</li>
    <li><b>0.003/0.003 secs</b>: a fázis időtartama processzoridőben és valós időben</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a &quot;Times&quot; rész konkurens fázisok esetén nem túl sokat mond, mert a konkurens marking kezdetétől indul ugyan, de
      nem a fázis során ténylegesen elvégzett munka idejét mutatja.</li>
  </ol>
  <p class="bekezd">
    <i style="text-decoration: underline;">3. fázis: concurrent preclean</i>: míg a korábbi fázis futott, néhány referenciát az alkalmazás megváltoztatott. Amikor ez történt, a JVM
    megjelölte az ahhoz tartozó kártyát piszkosra. Ebben a fázisban a piszkos kártyákon lévő objektumokon megy végig a GC és az azokból elérhető további objektumokat is feldolgozza. A
    kártyákat ezután helyreállítja és ezen kívül némi egyéb háztartási munkát is megcsinál.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.175+0200: 1.757: [CMS-concurrent-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-preclean<sup>1</sup>: 0.001/0.001 secs<sup>2</sup>] 
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>3</sup>
  </pre>
  <ol>
    <li><b>CMS-concurrent-preclean</b>: a szemétgyűjtés fázisa: &quot;concurrent preclean&quot;</li>
    <li><b>0.001/0.001 secs</b>: a fázis időtartama processzoridőben és valós időben</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a &quot;Times&quot; rész konkurens fázisok esetén nem túl sokat mond, mert a konkurens marking kezdetétől indul ugyan, de
      nem a fázis során ténylegesen elvégzett munka idejét mutatja.</li>
  </ol>
  <p class="bekezd">
    <i style="text-decoration: underline;">4. fázis</i>: concurrent abortable preclean: az utolsó konkurens fázis. Megpróbál még annyi munkát levenni a teljes megállási esemény alatt
    munkálkodó final remark válláról, amennyit lehet. A fázis időtartama nagyon sok szemponttól függ, mert egy ciklusban fut addig, amíg bizonyos feltételek nem teljesülnek (például elég
    iteráció lement már vagy elég hasznos munkát elvégzett, stb.). Ennek a fázisnak jelentős hatása van a következő teljes megállási eseményre.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean-start]
2017-09-06T21:26:41.176+0200: 1.759: [CMS-concurrent-abortable-preclean<sup>1</sup>: 0.000/0.000 secs<sup>2</sup>]
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>3</sup>
  </pre>
  <ol>
    <li><b>CMS-concurrent-abortable-preclean</b>: a szemétgyűjtés fázisa: &quot;concurrent abortable preclean&quot;</li>
    <li><b>0.000/0.000 secs</b>: a fázis időtartama processzoridőben és valós időben. Itt általában olyat láthatunk, hogy a valós idő kisebb, mint a processzoridő, ami azt jelenti,
      hogy némi munkát sikerült párhuzamosan megcsinálni. Van azonban olyan eset is, amikor a processzoridő sokkal kisebb, mint a valós idő: ez akkor fordulhat elő, ha kevés a munka és a
      szálak csak várakoznak. Lényegében megpróbálnak anyi munkát elvégezni, amennyit csak lehet, mielőtt egy teljes megállási eseményt kellene csinálni. Alapértelmezés szerint ez a fázis 5
      másodpercig tarthat.</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a &quot;Times&quot; rész konkurens fázisok esetén nem túl sokat mond, mert a konkurens marking kezdetétől indul ugyan, de
      nem a fázis során ténylegesen elvégzett munka idejét mutatja.</li>
  </ol>
  <p class="bekezd">
    <i style="text-decoration: underline;">5. fázis: final remark</i>: a második teljes megállási esemény. Az a feladata, hogy befejezze az old generáció élő objektumainak megjelölését. A
    CMS általában megpróbálja ezt a fázist akkor futtatni, amikor a young generáció a legüresebb. A log alapján ez egy picit összetettebb lépés, mint az eddigiek.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.176+0200: 1.759<sup>1</sup>: [GC (CMS Final Remark<sup>2</sup>) [YG occupancy: 48992 K (157248 K)]<sup>3</sup>
    2017-09-06T21:26:41.176+0200: 1.759: [Rescan (parallel) , 0.0002084 secs]<sup>4</sup>
    2017-09-06T21:26:41.177+0200: 1.759: [weak refs processing, 0.0000179 secs]<sup>5</sup>
    2017-09-06T21:26:41.177+0200: 1.759: [class unloading, 0.0001895 secs]<sup>6</sup>
    2017-09-06T21:26:41.177+0200: 1.759: [scrub symbol table, 0.0003631 secs]
    2017-09-06T21:26:41.177+0200: 1.760: [scrub string table, 0.0000810 secs]<sup>7</sup>
    [1 CMS-remark: 349365K(349568K)<sup>8</sup>]398357K(506816K)<sup>9</sup>, 0.0009036 secs<sup>10</sup>]
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>11</sup>
  </pre>
  <ol>
    <li><b>2017-09-06T21:26:41.176+0200: 1.759</b>: a GC ciklus indulásának két típusú időpontja</li>
    <li><b>CMS Final Remark</b>: a szemétgyűjtés fázisa: &quot;final remark&quot;</li>
    <li><b>[YG occupancy: 48992 K (157248 K)]</b>: a young generáció aktuális kihasználtsága és kapacitása</li>
    <li><b>[Rescan (parallel) , 0.0002084 secs]</b>: a &quot;Rescan&quot; befejezi az élő objektumok megjelölését, míg az alkalmazás meg van állítva. Ez esetben a rescan párhuzamosan
      futott és 0,0002084 másodpercig tartott.</li>
    <li><b>[weak refs processing, 0.0000179 secs]</b>: az első alfázis, ami a gyenge referenciákat dolgozza fel (a fázis időpontjával és időtartamával)</li>
    <li><b>[class unloading, 0.0001895 secs]</b>: a második alfázis, ami kidobálja a nem használt osztályokat</li>
    <li><b>[scrub symbol table, 0.0003631 secs]</b> és <b>[scrub string table, 0.0000810 secs]</b>: a harmadik és negyedik alfázis, amik kipucolják a metaadatokat és internalizált
      sztringeket tartalmazó szimbólum- és sztringtáblákat</li>
    <li><b>349365K(349568K)</b>: az old generáció kihasználtsága és kapacitása a fázis után</li>
    <li><b>398357K(506816K)</b>: a teljes heap kihasználtsága és kapacitása a fázist követően</li>
    <li><b>0.0009036 secs</b>: a fázis időtartama</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a megállás időtartama user, system és real kategóriák szerinti bontásban</li>
  </ol>
  <p class="bekezd">Az öt marking fázis után minden élő objektum meg lett jelölve és a GC most már kipucolhatja az old generációt.</p>
  <p class="bekezd">
    <i style="text-decoration: underline;">6. fázis: concurrent sweep</i>: a szemét eltávolítása az alkalmazás futásával párhuzamosan.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.177+0200: 1.760: [CMS-concurrent-sweep-start]
2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-sweep<sup>1</sup>: 0.000/0.000 secs<sup>2</sup>]
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>3</sup>
  </pre>
  <ol>
    <li><b>CMS-concurrent-sweep</b>: a szemétgyűjtés fázisa: &quot;concurrent sweep&quot;</li>
    <li><b>0.000/0.000 secs</b>: a fázis időtartama processzoridőben és valós időben</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a &quot;Times&quot; rész konkurens fázisok esetén nem túl sokat mond, mert a konkurens sweep kezdetétől indul ugyan, de nem
      a fázis során ténylegesen elvégzett munka idejét mutatja.</li>
  </ol>
  <p class="bekezd">
    <i style="text-decoration: underline;">7. fázis: concurrent reset</i>: a belső adatszerkezetek beállítása és felkészülés a következő CMS ciklusra.
  </p>
  <pre class="programkod">2017-09-06T21:26:41.178+0200: 1.760: [CMS-concurrent-reset-start]
2017-09-06T21:26:41.178+0200: 1.761: [CMS-concurrent-reset<sup>1</sup>: 0.000/0.000 secs<sup>2</sup>]
    [Times: user=0.00 sys=0.00, real=0.00 secs]<sup>3</sup>
  </pre>
  <ol>
    <li><b>CMS-concurrent-reset</b>: a szemétgyűjtés fázisa: &quot;concurrent reset&quot;</li>
    <li><b>0.000/0.000 secs</b>: a fázis időtartama processzoridőben és valós időben</li>
    <li><b>[Times: user=0.00 sys=0.00, real=0.00 secs]</b>: a &quot;Times&quot; rész konkurens fázisok esetén nem túl sokat mond, mert a konkurens reset kezdetétől indul ugyan, de nem
      a fázis során ténylegesen elvégzett munka idejét mutatja.</li>
  </ol>
  <h3>G1</h3>
  <p class="bekezd">
    Utolsóként vizsgáljuk meg a G1-gyel futtatott program logját: <span class="programkod">-XX:+UseG1GC</span>
  </p>
  <p class="bekezd">
    <b>Evacuation pause: fully young</b>
  </p>
  <p class="bekezd">Az alkalmazás életciklusának kezdetén a G1-nek még nincs információja a korábban végrehajtott futásokból, ekkor még ún. fully young módban fut. Amikor a young
    generáció betelik, jön egy teljes megállási esemény és az éden régiókban lévő még élő objektumok átkerülnek (az esetlegesen újonnan létrehozott) survivor régiókba. Az evakuációs
    megállások teljes logja elég nagy, úgyhogy kihagytam néhány apróbb, itt most még lényegtelen részt belőle. Később majd ezekre részletesen rátérek.</p>
  <pre class="programkod">2017-09-08T20:22:52.022+0200: 0.343: [GC pause (G1 Evacuation Pause) (young), 0.0048649 secs]<sup>1</sup>
   [Parallel Time: 3.9 ms, GC Workers: 4]<sup>2</sup>
[...]
   [Code Root Fixup: 0.0 ms]<sup>3</sup>
   [Code Root Purge: 0.0 ms]<sup>4</sup>
   [Clear CT: 0.2 ms]
   [Other: 0.7 ms]<sup>5</sup>
[...]
   [Eden: 14.0M(14.0M)->0.0B(8192.0K)<sup>6</sup> Survivors: 0.0B->2048.0K<sup>7</sup> Heap: 14.0M(128.0M)->13.5M(128.0M)<sup>8</sup>]
 [Times: user=0.00 sys=0.05, real=0.02 secs]<sup>9</sup>
  </pre>
  <ol>
    <li><b>0.343: [GC pause (G1 Evacuation Pause) (young), 0.0048649 secs]</b>: a G1 itt csak a young régiókat pucolja ki. A megállás 343 ezredmásodperccel a JVM indulása után indult
      és 0,0048 másodpercig tartott.</li>
    <li><b>[Parallel Time: 3.9 ms, GC Workers: 4]</b>: 3,9 ezredmásodpercig többek között a következő tevékenységeket csinálta meg 4 párhuzamos szál:
      <ul>
        <li><b>[Code Root Fixup: 0.0 ms]</b>: a párhuzamos feladatokhoz használt adatstruktúrák felszabadítása (szekvenciális folyamat). Ennek mindig 0 közeli értéknek kell lennie.</li>
        <li><b>[Code Root Purge: 0.0 ms]</b>: még több adatszerkezet kipucolása (szintén szekvenciális), ennek is nagyon gyorsnak kell lennie, de nem szükségszerűen 0.</li>
        <li><b>[Other: 0.7 ms]</b>: különféle egyéb tevékenység, sok ezek közül párhuzamosítva van.</li>
      </ul></li>
    <li><b>Eden: 14.0M(14.0M)->0.0B(8192.0K)</b>: az éden terület kihasználtsága és kapacitása a megállás előtt és után</li>
    <li><b>Survivors: 0.0B->2048.0K</b>: survivor régiók által használt terület a megállás előtt és után</li>
    <li><b>Heap: 14.0M(128.0M)->13.5M(128.0M)</b>: a teljes heap kihasználtság és kapacitás a megállás előtt és után</li>
    <li><b>[Times: user=0.00 sys=0.05, real=0.02 secs]</b>: a szemétgyűjtés időtartama különböző kategóriák szerint:
      <ul>
        <li>user: a GC szálak által használt teljes CPU idő</li>
        <li>sys: operációs rendszerhívásokra való várakozás ideje</li>
        <li>real: az alkalmazás leállításának időtartama. Párhuzamos GC esetén ez nagyjából a (user+sys)/GC által használt szálak száma. Ez esetben 1 szál volt használatban.</li>
      </ul></li>
  </ol>
  <p class="bekezd">A munka nehezét több dedikált GC munkavégző szál (GC worker) végzi el. Ezek tevékenységére az alábbi logbejegyzésekben derül fény (a fenti logban az első kipontozott
    rész):</p>
  <pre class="programkod">[Parallel Time: 3.9 ms, GC Workers: 4]<sup>1</sup>
      [GC Worker Start (ms)<sup>2</sup>: Min: 343.0, Avg: 343.1, Max: 343.1, Diff: 0.0]
      [Ext Root Scanning (ms)<sup>3</sup>: Min: 0.2, Avg: 0.3, Max: 0.3, Diff: 0.1, Sum: 1.0]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms)<sup>4</sup>: Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms)<sup>5</sup>: Min: 3.1, Avg: 3.2, Max: 3.3, Diff: 0.2, Sum: 12.9]
      [Termination (ms)<sup>6</sup>: Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.9]
         [Termination Attempts<sup>7</sup>: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms)<sup>8</sup>: Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms)<sup>9</sup>: Min: 3.6, Avg: 3.8, Max: 3.8, Diff: 0.2, Sum: 15.0]
      [GC Worker End (ms)<sup>10</sup>: Min: 346.7, Avg: 346.8, Max: 346.9, Diff: 0.2]</pre>
  <ol>
    <li><b>[Parallel Time: 3.9 ms, GC Workers: 4]</b>: azt mutatja meg az úri közönségnek, hogy az alábbi feladatok 3,9 ezredmásodpercig (valós idő) tartottak 4 párhuzamos szál
      használatával</li>
    <li><b>GC Worker Start (ms)</b>: a pillanat, ahol a munkafolyamatok elkezdék a saját tevékenységüket a megállás kezdetének időpontjához illesztve. Ha a min és a max nagyon eltér,
      akkor az jelentheti azt, hogy túl sok a használt szál vagy más folyamatok ellopják a CPU időt a JVM-ben lévő szemétgyűjtőtől.</li>
    <li><b>Ext Root Scanning (ms)</b>: ennyi ideig tartott átvizsgálni a külső (nem heap-en lévő) GC root-okat (mint például az osztálybetöltők, JNI referenciák, JVM system root-ok,
      stb.)</li>
    <li><b>Code Root Scanning (ms)</b>: azon szemétgyűjtő gyökerek átvizsgálása, amelyek már a tulajdonképpeni kódból jönnek: lokális változók, stb.</li>
    <li><b>Object Copy (ms)</b>: az élő objektumok átmásolása a szemétgyűjtőzött régiókból</li>
    <li><b>Termination (ms)</b>: ennyi ideig tartott a munkavégző szálaknak, hogy megbizonyosodjanak róla, biztonságosan meg tudnak állni és nem kell további munkát végezniük</li>
    <li><b>Termination Attempts</b>: hányszor próbáltak megállni a munkavégző szálak. Egy próbálkozás sikertelen, ha a munkát végző szál felfedezi, hogy még több munkát meg kell
      csinálni és még túl korai megállni</li>
    <li><b>GC Worker Other (ms)</b>: egyéb kiegészítő kisebb tevékenységek, amiket a log többi részéhez nem lehetett hozzácsapni</li>
    <li><b>GC Worker Total (ms)</b>: a munkavégző szálak teljes egészében mennyi időt dolgoztak</li>
    <li><b>GC Worker End (ms)</b>: az időpont, amikor a munkavégző szálak befejezték a dolgukat. Általában ezeknek az értékeknek nagyjából azonosnak kell lenniük, ha mégsem, akkor az
      annak jele, hogy túl sok szál van felfüggesztve vagy nagyon elfoglalt a CPU.</li>
  </ol>
  <p class="bekezd">Ezeken kívül van még némi egyéb munka is az evakuációs megállás során. Itt ennek csak egy részét nézzük át, a többiről később lesz szó. (A fenti logban a második
    kipontozott rész.)</p>
  <pre class="programkod">   [Other: 0.7 ms]<sup>1</sup>
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.5 ms]<sup>2</sup>
      [Ref Enq: 0.0 ms]<sup>3</sup>
      [Redirty Cards: 0.2 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]<sup>4</sup>
  </pre>
  <ol>
    <li><b>[Other: 0.7 ms]</b>: néhány egyéb művelet, melyek közül sok szintén párhuzamosítható</li>
    <li><b>[Ref Proc: 0.5 ms]</b>: a nem erős referenciák feldolgozására szánt idő: meghatározza, hogy ki kell-e őket pucolni és ha igen, megteszi</li>
    <li><b>[Ref Enq: 0.0 ms]</b>: a fennmaradó nem erős referenciák megfelelő ReferenceQueue-ba való sorbaállításának ideje</li>
    <li><b>[Free CSet: 0.0 ms]</b>: ennyi ideig tartott visszaadni a CSet-ekben lévő felszabadított régiókat, tehát ezután már elérhetőek lesznek az új foglalások számára</li>
  </ol>
  <p class="bekezd">
    <b>Concurrent marking</b>
  </p>
  <p class="bekezd">
    <i style="text-decoration: underline;">1. fázis: initial mark</i>: megjelöli az összes, GC root-okból közvetlenü elérhető objektumot. Ezt a megállást a GC logban az <span
      class="programkod">(initial-mark)</span> szöveg jelzi egy evakuációs megállás első sorában (figyeljük meg, hogy az előző példánál ez még nem szerepelt ott, az tisztán minor
    szemétgyűjtés volt, itt viszont a minor GC már kiegészül az initial mark fázissal):
  </p>
  <pre class="programkod">2017-09-08T20:22:52.335+0200: 0.643: [GC pause (G1 Evacuation Pause) (young)
    (initial-mark), 0.0189492 secs]</pre>
  <p class="bekezd">
    <i style="text-decoration: underline;">2. fázis: root region scan</i>: root régiók átvizsgálása old generációkba mutató referenciák után (azokat meg is jelöli).
  </p>
  <pre class="programkod">2017-09-08T20:22:52.351+0200: 0.663: [GC concurrent-root-region-scan-start]
2017-09-08T20:22:52.351+0200: 0.665: [GC concurrent-root-region-scan-end, 0.0023534 secs]</pre>
  <p class="bekezd">
    <i style="text-decoration: underline;">3. fázis: concurrent mark</i>
  </p>
  <pre class="programkod">2017-09-08T20:22:52.351+0200: 0.665: [GC concurrent-mark-start]
2017-09-08T20:22:52.351+0200: 0.669: [GC concurrent-mark-end, 0.0039860 secs]</pre>
  <p class="bekezd">
    <i style="text-decoration: underline;">4. fázis: remark</i>: teljes megállási esemény (stop the world pause)
  </p>
  <pre class="programkod">2017-09-08T20:22:52.351+0200: 0.670: [GC remark 2017-09-08T20:22:52.351+0200: 0.670:
    [Finalize Marking, 0.0002606 secs] 2017-09-08T20:22:52.351+0200: 0.670: 
    [GC ref-proc, 0.0001265 secs] 2017-09-08T20:22:52.351+0200: 0.670: 
    [Unloading, 0.0014274 secs], 0.0028999 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs]</pre>
  <p class="bekezd">
    <i style="text-decoration: underline;">5. fázis: cleanup</i>: megágyaz a következő evakuációs fázisnak. Beállítja az élő objektumokat nem tartalmazó régiókat. A fázis néhány része
    konkurens, mint például az üres régiók kezelése és az élőségi számítások nagy része, de a véglegesítéshez ennek is teljes megállási esemény kell.
  </p>
  <pre class="programkod">2017-09-08T20:22:52.351+0200: 0.673: [GC cleanup 105M->105M(205M), 0.0004022 secs]
 [Times: user=0.02 sys=0.00, real=0.01 secs]</pre>
  <p class="bekezd">Amikor olyan régiókat talál, amik csak szemetet tartalmaznak, a bejegyzés kicsit különbözik:</p>
  <pre class="programkod">2017-09-08T20:22:52.351+0200: 0.673: [GC cleanup 105M->105M(205M), 0.0004022 secs]
 [Times: user=0.02 sys=0.00, real=0.01 secs]
2017-09-08T20:22:52.444+0200: 0.766: [GC concurrent-cleanup-start]
2017-09-08T20:22:52.446+0200: 0.768: [GC concurrent-cleanup-end, 0.0014846 secs]</pre>
  <p class="bekezd">
    <b>Evacuation pause: mixed</b>
  </p>
  <p class="bekezd">Az a legjobb, amikor a concurrent cleanup az old generációban teljes régiókat tud felszabadítani, de nem mindig ez a helyzet. Miután a concurrent marking sikeresen
    végetért, egy evakuációs megállás következik. Ennek egyik változata a mixed collection, ami nem csak a szemetet pucolja ki a young generációból, hanem egy csomó old régiót is betesz a
    collection set-ekbe. (A tisztán young generációs evakuációs megállást fentebb már láttuk.) Egy mixed evacuation pause nem mindig pontosan követi a concurrent marking fázist.</p>
  <p class="bekezd">A logja néhány új dolgot is tartalmaz (a kipontozott részeket korábban már láttuk):</p>
  <pre class="programkod">2017-09-08T20:22:52.976+0200: 1.284: [GC pause (G1 Evacuation Pause) (mixed), 0.0093513 secs]
   [Parallel Time: 7.4 ms, GC Workers: 4]
      [...]
      [Update RS (ms)<sup>1</sup>: Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.2]
         [Processed Buffers<sup>2</sup>: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 5]
      [Scan RS (ms)<sup>3</sup>: Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [...]
   [Clear CT: 0.3 ms]<sup>4</sup>
   [Other: 1.7 ms]
      [...]
      [Redirty Cards: 0.4 ms]<sup>5</sup>
      [...]</pre>
  <ol>
    <li><b>Update RS (ms)</b>: mivel a remembered set-ek feldolgozása konkurensen történik, biztosítani kell, hogy a pufferelve lévő kártyák mind feldolgozódjanak, mielőtt a
      tulajdonképpeni szemétgyűjtés elkezdődik. Ha ez a szám magas, akkor a konkurens GC szálak képtelenek kezelni a terhelést. Ez lehet például azért, mert túl sok bejövő mező módosítás
      történik vagy nincs elég CPU erőforrás.</li>
    <li><b>Processed Buffers</b>: mennyi local puffert tud az egy-egy feldolgozó szál kezelni</li>
    <li><b>Scan RS (ms)</b>: mennyi ideig tartott átvizsgálni a remembered set-ekből jövő referenciákat</li>
    <li><b>Clear CT</b>: ennyi ideig tartott kitörölni a kártyákat a card table-ből. Ez egyszerűen csak kikapcsolja a piszkos státuszt.</li>
    <li><b>Redirty Cards</b>: ennyi ideig tart piszkosnak jelölni a megfelelő helyeket a card table-ben.</li>
  </ol>
  <h2>A finalize() piszkos kis trükkjei</h2>
  <p class="bekezd">Immáron végeztünk a szemétgyűjtés technológiáinak áttekintésével, a GC-tuningolás mesterségéről ebben a cikkben már nem fogok mesélni. Viszont van még egy téma, ami
    ide kívánkozik. Mivel is lehetne befejezni egy GC-ről szóló cikket, ha nem a finalize()-zal?</p>
  <p class="bekezd">
    A <span class="programkod">java.lang.Object</span> osztály <span class="programkod">finalize()</span> metódusáról a Java 7 API dokumentációja a következőt írja: &quot;Called by the
    garbage collector on an object when garbage collection determines that there are no more references to the object.&quot;
  </p>
  <p class="bekezd">
    A helyzet pofonegyszerűnek tűnik. A <span class="programkod">finalize()</span>-t a GC meghívja, amikor eljött az ideje, vagyis úgy érzékeli, hogy az objektumra nem hivatkozik több
    (erős) referencia. Persze ha a helyzet tényleg iylen egyszerű lenne, akkor ez a fejezet sem született volna meg. Nézzünk először egy egyszerű példát:
  </p>
  <pre class="programkod">
<span class="java_keyword">public</span> <span class="java_keyword">class</span> FinalizeTest1 {

    <span class="java_keyword">static</span> <span class="java_keyword">long</span> NumberOfCreatedInstances = 0;
    <span class="java_keyword">static</span> <span class="java_keyword">long</span> NumberOfDeletedInstances = 0;

    <span class="java_keyword">public</span> FinalizeTest1() {
        NumberOfCreatedInstances++;
    }

    <span class="java_keyword">static</span> <span class="java_keyword">public</span> <span class="java_keyword">void</span> main(String args[]) {
        <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0;; i++) {
            FinalizeTest1 obj = <span class="java_keyword">new</span> FinalizeTest1();
            obj = <span class="java_keyword">null</span>;
            <span class="java_keyword">if</span> (i % 10000000 == 0) {
                System.out.println(NumberOfCreatedInstances - NumberOfDeletedInstances);
            }
        }

    }
}</pre>
  <p class="bekezd">
    Ez a kis osztály nem implementál saját <span class="programkod">finalize()</span> metódust, hogy később lássuk a különbséget. Ez egy egyszerű kis ciklus, ami csak példányosít és
    megszüntet hivatkozásokat a <span class="programkod">FinalizeTest1</span>-re. Úgy fut, ahogy elvárható: csinál egy csomó szemetet, amit a szemétgyűjtő rendszeresen kipucol. Most
    módosítsuk egy kicsit az osztályt, hogy legyen <span class="programkod">finalize()</span> implementációja!
  </p>
  <pre class="programkod">
<span class="java_keyword">public</span> <span class="java_keyword">class</span> FinalizeTest2 {

    <span class="java_keyword">static</span> <span class="java_keyword">long</span> NumberOfCreatedInstances = 0;
    <span class="java_keyword">static</span> <span class="java_keyword">long</span> NumberOfDeletedInstances = 0;

    <span class="java_keyword">public</span> FinalizeTest2() {
        NumberOfCreatedInstances++;
    }

    <span class="java_keyword">protected</span> <span class="java_keyword">void</span> finalize() {
        NumberOfDeletedInstances++;
    }

    <span class="java_keyword">static</span> <span class="java_keyword">public</span> <span class="java_keyword">void</span> main(String args[]) {
        <span class="java_keyword">for</span> (<span class="java_keyword">int</span> i = 0;; i++) {
            FinalizeTest2 obj = <span class="java_keyword">new</span> FinalizeTest2();
            obj = <span class="java_keyword">null</span>;
            <span class="java_keyword">if</span> (i % 10000000 == 0) {
                System.out.println(NumberOfCreatedInstances - NumberOfDeletedInstances);
            }
        }
    }
}</pre>
  <p class="bekezd">
    Látszólag nincs sok különbség a két tesztosztály között, de ha lefuttatjuk GC logolást is bekapcsolva, akkor hatalmas különbséget látunk a két futás között. A FinalizeTest1 szépen
    nyugisan futogat; folyamatosan létrehozza az objektumokat, amit néha nagyon gyors young generációs GC megszakít, pont ahogy a kód alapján elvárnánk. A FinalizeTest2 viszont kegyetlenül
    lelassul (nem árt <span class="programkod">-Xmx64m</span> vagy még kisebb heap mérettel indítani). 1.6 előtti JVM-nél pedig akár még OutOfMemory kivételt is dobhat (bár eltart egy
    darabig). Hacsak nincs tapasztalatunk korábbról a <span class="programkod">finalize()</span> metódussal, akkor ez az eredmény meglepő lehet. Egy pár soros kis egyszerű kód teljesen
    megfektetni a JVM-et. De nézzük, mi is történik a háttérben!
  </p>
  <p class="bekezd">
    Nézzük a FinalizeTest1-et. Egyszerű osztály egyszerű életciklussal. A <span class="programkod">main()</span> metódusban a <span class="programkod">new FinalizeTest1()</span> sor
    létrehozza az éden területen a FinalizeTest1 objektumot. A következő sorban jön a referencia törlése (<span class="programkod">obj=null;</span>); ezzel az objektumhoz minden referencia
    törlődik. Egyébként ha az a sor nem lenne, a referencia törlése akkor is megtörténne a következő iterációban, amikor az obj a következő FinalizeTest1 példányra mutat majd, de jobb, ha
    ez a példa most ilyen egyértelmű.
  </p>
  <p class="bekezd">Egyszercsak már elég FinalizeTest1 példányt hoztunk létre az éden területen, ami tele lesz. Ez kivált egy minor GC-t. Mivel semmi nem mutat az éden területen lévő
    objektumokra (vagy esetleg még egy, attól függően, hogy mikor indul el a GC), az édent nagyon hatékonyan üresre lehet állítani. Ha egy objektumra még van hivatkozás, az átmásolódik a
    survivor területre. Tehát nagyon gyorsan visszakerülünk egy üres édenhez és a fő ciklus folytatódhat további objektum létrehozással.</p>
  <p class="bekezd">
    A FinalizeTest2 viszont máshogy néz ki. Először is amikor egy FinalizeTest2 példány létrejön, akkor a JVM látja, hogy van felüldefiniált <span class="programkod">finalize()</span>
    metódusa, ami nem egyezik meg az Object-ével. Nemtriviális <span class="programkod">finalize()</span> metódus létrehozása (vagy akár öröklése) egy osztályban már önmagában elegendő
    hozzá, hogy megváltoztassa az objektumok létrehozását. A JVM figyelmen kívül hagyja a triviális <span class="programkod">finalize()</span> metódust (ami visszatér anélkül, hogy bármit
    csinálna, pont mint az <span class="programkod">Object</span>-féle). Viszont ha egy példánynak nemtriviális <span class="programkod">finalize()</span> metódusa van vagy öröklött egyet,
    akkor a JVM a következőt csinálja:
  </p>
  <ul>
    <li>létrehozza a példányt</li>
    <li>létrehoz egy <span class="programkod">java.lang.ref.Finalizer</span> példányt, ami az épp létrehozott objektumpéldányra mutat (és egy sorra, amibe majd beteszi a GC)
    </li>
    <li>a <span class="programkod">java.lang.ref.Finalizer</span> osztály hivatkozik az épp létrehozott Finalizer példányra, amit most hozott létre (így tartja életben, hiszen
      egyébként a következő GC-nél az is ki lenne dobva).
    </li>
  </ul>
  <p class="bekezd">
    Tehát minden létrehozott <span class="programkod">FinalizeTest2</span> példány esetén kapunk egy különálló <span class="programkod">java.lang.ref.Finalizer</span> példányt is, ami a <span
      class="programkod">FinalizeTest2</span> példányra mutat. Ez ennyire durva lenne? Hát nem igazán. Jó persze egy objektum helyett kettőt hozunk létre, de hát láttuk, hogy a modern
    JVM-ek csudamód hatékonyak az objektumok létrehozásában, tehát ez nem nagy ügy.
  </p>
  <p class="bekezd">
    <b>Az első GC</b>
  </p>
  <p class="bekezd">
    Mi következik ezután? Pont mint korábban, a <span class="programkod">FinalizeTest2</span> példányokra hivatkozó referencia megszűnik, tehát ki lehet őket pucolni. Ez az éden betelésekor
    lesz amikor jön egy minor GC. Ez esetben viszont a GC-nek van extra tennivalója. Először is egy csomó nem hivatkozott objektum helyett immár egy csomó, a <span class="programkod">Finalizer</span>
    objektumok által hivatkozott objektumunk van. Arra meg a <span class="programkod">Finalizer</span> osztály hivatkozik. Vagyis minden életben maradt! A GC átmásol mindent a survivor
    területre. De ha az nem elég nagy, hogy az összes objektumot befogadja, akkor néhány átkerül az old generációba (aminek költségesebb a GC ciklusa is). Egyből sokkal több munkánk van és
    ráadásul még egy csomó dolog lesz ezután is. Itt ugyanis még nincs vége! Mivel a GC felismeri, hogy semmi más nem mutat a <span class="programkod">FinalizeTest2</span> objektumokra a <span
      class="programkod">Finalizer</span> példányokon kívül, azt mondja magában, hogy aha, bármely <span class="programkod">FinalizeTest2</span> példányra mutató <span class="programkod">Finalizer</span>-t
    fel tudom dolgozni! Így aztán mindegyik <span class="programkod">Finalizer</span> objektumot hozzáadja a <span class="programkod">java.lang.ref.Finalizer.ReferenceQueue</span> sorhoz.
    Most már végzett és láthatóan több munkát csinált, mint amíg nem voltak <span class="programkod">Finalizer</span>-jeink. És nézzünk rá a heap-ünkre: <span class="programkod">FinalizeTest2</span>
    példányok lézengenek benne: kitöltik a survivor területet és még az old generációban is vannak. Emellett <span class="programkod">Finalizer</span> példányok is lófrálnak, amiket még
    mindig használ a Finalizer osztály. A GC végetért és semmi se lett kitakarítva!
  </p>
  <p class="bekezd">
    <b>A Finalizer szál</b>
  </p>
  <p class="bekezd">
    Most, hogy a minor GC végetért, a teljes megállási esemény befejeződik és az alkalmazás szála folytatódik. A <span class="programkod">java.lang.ref.Finalizer</span> osztályban van egy
    belső osztály, amit <span class="programkod">FinalizerThread</span>-nek hívnak és ez indítja el a <span class="programkod">Finalizer</span> démon szálat, amikor a JVM betölti a <span
      class="programkod">java.lang.ref.Finalizer</span> osztályt. Ezt a démon szálat egy monitorozó programmal láthatjuk is a JVM-ben. Ez a Finalizer démon szál egyébként meglehetősen
    egyszerű. Egy ciklusban dolgozik, ami arra vár, hogy valamit találjon a <span class="programkod">java.lang.ref.Finalizer.ReferenceQueue</span> sorban. Az alábbi kódrészlethez hasonlóan
    (a valódi kód ennél picit bonyolultabb, mert néhány hibakezelési és elérési problémával is foglalkozik):
  </p>
  <pre class="programkod">for(;;)
{
    Finalizer f = java.lang.ref.Finalizer.ReferenceQueue.remove();
    f.get().finalize();
}</pre>
  <p class="bekezd">
    A ciklus most sokkal több <span class="programkod">FinalizeTest2</span> példányt csinál a hozzájuk csatolt <span class="programkod">Finalizer</span> objektumokkal, mialatt a Finalizer
    démon szál átfut a Finalizer objektumokon, amiket a korábbi GC rakott a referenciasorba. Ezzel még nem lehetne probléma, hiszen a mi <span class="programkod">FinalizeTest2.finalize()</span>
    metódusunknak gyorsabbnak kellene lenni, mint egy új FinalizeTest2 példány létrehozásának, tehát azt várjuk, hogy képes lesz gyorsan lefutni. Van azonban egy kis probléma ezzel az
    optimista elvárással. A Finalizer démon szálat az alapértelmezettnél alacsonyabb prioritással futtatja a JVM. Ez pedig azt jelenti, hogy a main szál sokkal több processzoridőt kap, mint
    a Finalizer démon szál, tehát a FinalizeTest2 esetén az sosem fogja utolérni a main-t.
  </p>
  <p class="bekezd">
    Normál alkalmazásoknál ez a kiegyensúlyozatlanság persze általában nem számít. A nemtriviális <span class="programkod">finalize()</span>-zal rendelkező objektumokat nem hozzák létre
    ilyen nagy számban vagy ha mégis, akkor is csomagokban. Az átlagos alkalmazásnak tehát lesz processzorideje, hogy a Finalizer démon szál utolérje magát. A mi alattomos kis
    tesztprogramunk viszont túl gyorsan hozza létre a Finalizer démon szálnak feldolgozandó objektumokat és így a Finalizer referenciasor folyamatosan növekvő marad.
  </p>
  <p class="bekezd">
    <b>A második GC</b>
  </p>
  <p class="bekezd">
    Néhány Finalizer objektum azért persze kikerül a sorból és meghívódik az általuk hivatkozott FinalizeTest2 példányok <span class="programkod">finalize()</span> metódusa. Ezen a ponton a
    Finalizer démon szál még egy kis plusz munkát is csinál: eltávolítja a feldolgozott Finalizer példányra mutató referenciát a Finalizer sorból. Emlékezzünk rá, hogy ez tartotta életben a
    Finalizer példányt! Most már semmi sem mutat arra a Finalizer példányra és a következő GC ki tudja dobni - ahogy immár a FinalizeTest2 példányt is. Egy újabb szemétgyűjtéskor a
    feldolgozott Finalizer objektumok ki lesznek pucolva. A többi, ami még mindig a Finalizer sorban van, ide-oda másolódik a survivor területeken, egy részük közben megint feldolgozódik és
    kipucolódik, de a legtöbb (a mi FinalizeTest2 alkalmazásunkban) végül az old generációban köt ki. Végül az old generáció is tele lesz és elindul egy major GC. Bár az képes kicsit többet
    kipucolni, már nem sokat segít - most már látjuk, hogyan érjük el az OutOfMemoryError kivételt. Az a sor folyamatosan csak nő és végül nem lesz elég hely, hogy új objektumokat
    létrehozzunk. (Elképzelhető olyan JVM és konfiguráció (a 7-es a jelek szerint ilyen), ahol a szemétgyűjtő elég okos, hogy képes legyen felismerni a helyzetet és egy kicsit tovább hagyja
    futni a Finalizer szálat, így a végtelenségig tudunk futni majdnem teljes heap-pel.)
  </p>
  <p class="bekezd">
    <b>Végszó</b>
  </p>
  <p class="bekezd">
    Most már láttuk, hogyan dolgozódnak fel a <span class="programkod">finalize()</span>-t használó objektumok. A példa persze szándékosan ilyen volt, de legalább jól demonstrálta, hogy mi
    történik drámai esetben. Való életben azért azért legtöbbször nem ilyen rossz a helyzet. Megoldásként persze lehetne emelni a Finalizer démon szál prioritását, de erre nincs API, tehát
    kézzel kell megtenni.
  </p>
  <p class="bekezd">
    A problémára egyébként a fantom referenciák is megoldást kínálnak. A <span class="programkod">finalize()</span> kezelését ezzel teljesen átveheti az alkalmazásunk és mivel a
    PhantomReference nem használható egy objektum újrafelhasználására, az objektumot azonnal ki lehet pucolni már az első szemétgyűjtési ciklusban, amikor az már csak fantom referenciaként
    érhető el. Ezután pedig saját hatáskörben felszabadíthatunk olyan erőforrást, amit csak akarunk. A <span class="programkod">finalize()</span> metódust igazából nem is nagyon kellene
    használni. A <span class="programkod">PhantomReference</span> tisztább, szárazabb, biztonságosabb érzést ad. (Egyébként talán egyetlen értelmes érv van a <span class="programkod">finalize()</span>
    használatára: amikor valamilyen erőforrást expliciten le kell, hogy zárjon a program, akkor az erőforrás <span class="programkod">finalize()</span> metódusában meg tudjuk vizsgálni,
    hogy ezt megtette-e és ha nem, akkor tudunk hibalogot dobni a hiányról, vagyis hogy a program elfelejtette meghívni a <span class="programkod">close()</span>-t.)
  </p>
  <p class="bekezd">
    És ha már itt tartunk, nézzük, mi szól a <span class="programkod">finalize()</span> használata ellen:
  </p>
  <ul>
    <li>ha véletlenül sikerül egy új erős referenciát létrehozni erre az objektumra, akkor nem tudja kipucolni a GC, de ettől még nem kerül vissza élő állapotba sem: zombi lesz. Ha
      újra elfogy minden erős referencia, akkor a GC megtalálja és ledarálja, de már nem fogja újra meghívni a <span class="programkod">finalize()</span>-t.
    </li>
    <li>a JVM sokszor egy szálon, egymás után futtatja a finalizer metódusokat, de még ha van is több szál, az akkor is fogja, de legalábbis befolyásolja a GC-t.</li>
    <li>amikor a finalizer fut, akkor tipikusan kevés a memória, hiszen elindult a GC, és mi másért indult volna el, mint azért, mert kevés a memória.</li>
    <li>és végül: a <span class="programkod">finalize()</span> egyáltalán nem biztos, hogy valaha is meghívódik, hiszen nincs garancia arra, hogy az adott objektumot a GC fel fogja
      szabadítani az alkalmazás vagy a JVM életciklusa alatt
    </li>
  </ul>
  <p class="bekezd">Referencia sorral azonban a fenti problémák egy része kiküszöbölhető.</p>
</body>
</html>