Monday, September 22, 2014

İşletim Sistemleri

"İşletim Sistemleri" başlıklı kitabımın kalan bölümlerini yakın zamanda tamamlamayı planlıyorum. Yayınlanan bölümlerin içerisinde eksiklikler bulunabilir. İlerleyen iterasyonlarda hem eksik bölümleri hem de yayınlanan bölümlerdeki eksiklikleri gidermeye çalışacağım. İçerikte hem teorik konulara hem de C/C++11 ve Java 8'de ve 9'da yazılmış örnek uygulamalara yer verdim. Başta yazım hataları olmak üzere önerilerinizi ve geri bildirimlerinizi bu e-posta adresi üzerinden yapabilirsiniz.

İçindekiler
  1. İşletim Sistemi
  2. Kabuk
  3. Prosesler
  4. İplikler
  5. İplikler Arası Eş Zamanlama
  6. Prosesler Arası İletişim
  7. İş Sıralama
  8. Prosesler Arası Eş zamanlama
  9. Bellek Yönetimi
10. Dosya Sistemi
11. Soket Haberleşme

Tuesday, September 16, 2014

Java 8'deki MapReduce Çatısının Başarımının C++11 ile Karşılaştırılması

Java SE 8 ile gelen yenilikler ile birlikte artık C++11 ile Java 8'in karşılaştırılabilir konuma geldiğini söyleyebiliriz. C++11 ve Java 8 artık her ikisi de birer fonksiyonel programlama yapabildiğimiz dillere dönüştüler. C++11 ile nihayet Thread tabanlı programlama dilin parçası haline geldi. Java'nın en güçlü yönü Java programlama dili değil, Java platformudur. Yazılımın başarımını etkileyen bir çok farklı etmen bulunur: yazılımın mimarisi, donanım, işletim sistemi. Java'nın öğündüğü "bir kere yaz her yerde çalışsın" söylemi Hotspot sayesinde gerçekleşir. Her yerde çalışır ama aynı başarımda değil. Örneğin Hotspot'un Linux platformlarda Windows işletim sistemine göre daha yüksek başarım sergilediğini biliyoruz. Hotspot'un en güçlü yönü, çalışma zamanında, Java derleyicisinin bytecode'a dönüştürdüğü ikili kodu üzerinde çalıştığı işlemcinin doğrudan çalıştırabileceği doğal makina kodlarına dönüştürebilmesidir. Bunu yaparken çeşitli iyileştirmeler de yapar. Bu iyileştirmelerin bir listesi için bu adrese göz atabilirsiniz. Hotspot'un içinde basit bir profiler yer alır ve çalışan uygulamanın şeklini almasını bekler ve aldığı şekle uygun en iyilemeler yapar. Hatta bu en iyilemeleri bazen şartlar artık geçerli olmadığı için geriye aldığı da olur. Buna karşılık C++'da derleyicinin yapabildiği iyileştirmeler derleme aşamasında gerçekleşir ve bu nedenle kısıtlıdır. Eğer GNU derleyici kullanıyorsanız iyileştirmeleri en üst düzeyde açmak için -O3 seçeneğini vermeniz gerekir.
Bu yazıda C++11'in dil özellikleri ile Java 8'in dil özellikleri karşılaştırılmayacaktır. Bunun yerine, büyükçe bir dizinin elemanlarının toplamını hesaplamak gibi basit problemi her iki dilde de paralel olarak çözmeye çalışarak başarımı karşılaştırılacaktır. Her iki dilde de karşılaştırma yapabilecek yetkinliğe sahip olduğum söylenebilir.
Önce C++11 ile başlayalım:
#include <iostream>
#include <thread>
#include <future>
#include <algorithm>

using namespace std;

const int SIZE=80000000;
int numbers[SIZE];

template <typename iter>
void init_problem(iter beg,iter end){
    int i=1;
 for (iter p=beg;p!=end;p++,++i)
     *p= i;
}

template <typename iter>
int parallel_sum(iter begin, iter end){
    long len= distance(begin,end);
    if (len <= 10000000){
    return accumulate(begin,end,int()); 
 }  
    iter mid= begin + len /2;
    auto handle_left= 
    async(launch::async,parallel_sum<iter>,begin,mid);   
    auto handle_right= 
    async(launch::async,parallel_sum<iter>,mid,end);   
    return handle_left.get()+
        handle_right.get();
}

int main(){
 init_problem(numbers,numbers+SIZE);
 int sum= parallel_sum(numbers,numbers+SIZE);
 cout << "Sum (parallel): " << sum << endl ; 
}
Kodda karşılaştığınız auto, async, future C++11 ile gelen yenilikler. Kodda üretken programlamanın (=Generic Programming) gücü hissediliyor. Aynı testi std::vector ile kodda neredeyse hiçbir değişiklik yapmadan gerçekleştirebiliriz. Ama iş paralel programlamaya gelince hala alt düzey yapıları kullanmamız gerekiyor. Bu problemde gerekmedi ama barrier, semaphore gibi yapılar hazır gelmiyor. Bizim mutex yada atomic değişkenler kullanarak bu yapıları gerçekleştirmemiz bekleniyor.
Yukarıdaki kodu derlemek için GNU C++ derleyicisi kullanıldı:

g++ -lpthread -O3 -std=c++11 -o parsum parsum.cpp

Şimdi aynı problemi Java 8'de MapReduce çatısını kullanarak çözelim:

public class ParallelSum {

    private static final int SIZE = 80_000_000;
    private static int numbers[] = new int[SIZE];

    private static void init_problem(int[] array) {
        for (int i = 0, j = 1; i < array.length; ++i, ++j) {
            array[i] = j;
        }
    }

    public static void solve() {
        int sum= Arrays.stream(numbers).parallel().sum();
        System.err.println("Sum (parallel): " + sum );        
    }

    public static void main(String[] args) {
        init_problem(numbers);
        for (int i=0;i<100;++i)
            solve();
    }
}
Java 8'de çözüm aslında tek satırdan oluşuyor: int sum= Arrays.stream(numbers).parallel().sum();
Şimdi başarımları karşılaştırabiliriz. Çalışma sürelerine bakalım. Her iki uygulama da 10 defa çalıştırıldı ve süreler milisaniye hassasiyetinde ölçüldü. 4 çekirdekli 8 sanal işlemcili makinada başarım aşağıdaki şekilde gerçekleşti:
C++11 Java 8
0.018948 0.155995
0.01986 0.01995
0.022167 0.019785
0.020191 0.018711
0.020895 0.019071
0.020394 0.018406
0.021172 0.019326
0.019818 0.019334
0.020595 0.019033
0.021555 0.019154

Karşılaştırmayı daha kolay yapabilmek için aşağıdaki grafikten  yararlanabilirsiniz:


Burada JIT derleyicinin iyi iş çıkardığını görüyoruz: çalışma süresini 0.15 saniyeden 0.019 saniyeye düşürüyor. Yinelemelerde işlem sürelerinin başa baş gittiğini görüyoruz. Kodları karşılaştırdığımızda, bu durumun, Java 8 ve Hotspot için bir zafer olduğunu söyleyebiliriz. 
View Binnur Kurt's profile on LinkedIn

Java 8'in Çok Çekirdekli Programlama Başarımı

Java 8'in, Mart 2014'de duyurulmasının ardından 6 ay geçti. Şu an JDK'nın en güncel sürümü 8u20. Artık platform olgunlaşmaya başladı. Başarımı test etmenin tam zamanı. Java 8 ile pek çok yenilik ile tanıştık. Detaylar için bu makaleye göz atmanızda fayda var. Java 8'deki yeniliklerin ortak hedefi, çok çekirdekli platformlarda yüksek başarım elde etmek. Uygulama başarımı tek boyutlu bir büyüklük olarak ölçülemez. Birim zamanda çıkarılan iş, yanıt süresi, bellek ayak izi gibi birden fazla ölçüte bakmak gerekebilir. Üstelik bu ölçütler arasında bir ödünleşim (=trade-off) de vardır. Hepsini birden iyileştirmek mümkün olmayabilir.  Bu yazıda, Java 8 platformu üzerinde veri paralelliği farklı yöntemlerle gerçeklenecek ve karşılaştırması yapılacaktır. Temel olarak üç farklı yöntem izlenecektir:
I. Callable İplikler (=Threads), Future ve Executor Kullanımı
Runnable ipliklerin en kötü yönü run() metodunun herhangi bir parametre alamaması ve ipliğin bir değer döndürememesidir. Bu yüzden görevi yaratan iplik ile sonucu üretecek iplik arasında mutlaka bir senkronizasyon kurmak gerekir. Java SE 5 ile gelen Callable arayüzü ile artık sonucu call() metodundan dönmek ve Future arayüzünün get() çağrısı ile bu sonuca asenkron bir şekilde ulaşmak mümkündür. Java SE 5 ile gelen yeniliklerden biri de İplik havuzudur (=Thread Pool). Farklı havuz türleri ihtiyaca göre seçilebilir: Her zaman  sabit sayıda ipliğin yer aldığı Fixed Thread Pool, Parlamalı çalışma modu için Cached Thread Pool, Yığın işler için Single Thread Pool, Periyodik işler için Scheduled Thread Pool. Ancak buradaki tüm yapılar alt düzeydedir. Bu yüzden bu yapı üzerinde uygulama geliştirmek vakit alır, test etmesi zordur, yarış durumları (=race conditions) ve ölümcül kilitlenme (=deadlock) gibi durumların iyi düşünülmüş olması gerekir.
II. Fork/Join Çatısısnın (=Framework) Kullanımı
Java SE 7 ile gelen Fork/Join çatısı temelde İş Çalma (=Work Stealing) algoritmasına dayanır. Java 7'deki Fork/Join çatısı kullanımına ilişkin detayları bu yazıdan okuyabilirsiniz. Fork/Join çatısı I'e göre kodlaması daha kolay olsa da hala alt düzey bir API sunmaktadır. Üstelik problemi iki alt parçaya ayırmak ile seri olarak çözümü arasında genellikle veri boyutu üzerinden verilmesi gereken bir karar vardır.
III. Dönüştür-İndirge (=Map-Reduce) Çatısının Kullanımı
Dönüştür-İndirge çatısı Java SE 8 ile gelmiştir ve alt tarafta Fork/Join çatısını kullanır. Collection API ile hazır olarak gelen paralel kaplar ve Dönüştür-İndirge çerçevesi kullanılarak, çekirdek sayısına göre ölçeklenebilir çözümlere hızlıca ulaşabilir.

Örnek Problemin Tanımı

Karşılaştırmada seçilen problem uzayını tanıyalım:
public class Programmer implements Serializable {

    private int id;
    private String name;
    private String surname;
    private int age;
    private ProgrammingLanguage programmingLanguage;
    . . .
}

public class ProgrammingLanguage implements Serializable {

    private String name;
    private int level;
    . . .
}

Önce 60 milyon Programmer sınıfından rastgele nesne yaratıp bir ArrayList'in içine atıyoruz. Amacımız bu listenin içinden 40 yaşın üstünde "en bilgili" Java programcısını bulmak. Bunun için yukarıda tanımladığımız üç yaklaşımı da kullanacağız ve süreleri karşılaştıracağız.

Seri Çözüm

Çözümün doğruluğunu sınamak ve hızlanmayı karşılaştırabilmek için problemi seri olarak çözmek uygun olur:

private static long serialSolve(List<Programmer> list) {
        long start = System.nanoTime();
        Programmer oldest = null;
        for (Programmer programmer : list) {
            if (programmer.getAge() > 40 && programmer.getProgrammingLanguage().getName().equalsIgnoreCase("java")) {
                if (oldest == null) {
                    oldest = programmer;
                } else if (programmer.getProgrammingLanguage().getLevel() > oldest.getProgrammingLanguage().getLevel()) {
                    oldest = programmer;
                }
            }
        }
        System.err.println("Oldest [Serial]: " + oldest);
        long stop = System.nanoTime();
        return (stop - start);
    }

Callable İplik ve Future Kullanımı

private static long parallelSolveCallableThread(List<Programmer> list) throws InterruptedException {
        long start = System.nanoTime();
        Programmer oldest = null;
        int numberOfSegments = DATA_SIZE / SERIAL_THRESHOLD;
        ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        List<Callable<Programmer>> callables = new ArrayList<>(numberOfSegments);
        for (int i = 0, j = 0; i < numberOfSegments; ++i, j += SERIAL_THRESHOLD) {
            callables.add(new ProcessingThread(j, SERIAL_THRESHOLD, list));
        }
        List<Future<Programmer>> partialSolutions = es.invokeAll(callables);

        try {
            oldest = partialSolutions.get(0).get();
            for (int i = 1; i < partialSolutions.size(); ++i) {
                Programmer programmer = partialSolutions.get(i).get();
                if (programmer.getProgrammingLanguage().getLevel() > oldest.getProgrammingLanguage().getLevel()) {
                    oldest = programmer;
                }
            }
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(PerformanceTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.err.println("Oldest [Callable Thread]: " + oldest);
        long stop = System.nanoTime();
        es.shutdown();
        return (stop - start);
    }

public class ProcessingThread implements Callable<Programmer> {

    private final int start;
    private final int length;
    private final List<Programmer> list;

    public ProcessingThread(int start, int length, List<Programmer> list) {
        this.start = start;
        this.length = length;
        this.list = list;
    }

    @Override
    public Programmer call() throws Exception {
        Programmer oldest = list.get(start);
        Programmer programmer;
        for (int i = start + 1, j = 1; j < length; ++j, ++i) {
            programmer = list.get(i);
            if (programmer.getAge() > 40 && programmer.getProgrammingLanguage().getName().equalsIgnoreCase("java")) {
                if (programmer.getProgrammingLanguage().getLevel() > oldest.getProgrammingLanguage().getLevel()) {
                    oldest = programmer;
                }
            }
        }
        return oldest;
    }

}

Fork/Join Çatısının Kullanımı

private static long parallelSolveForkJoin(List<Programmer> list) {
        long start = System.nanoTime();
        ForkJoinPool pool = new ForkJoinPool();
        ForkListProcessing flp = new ForkListProcessing(0, list.size(), list);
        Programmer oldest = pool.invoke(flp);
        System.err.println("Oldest [FJ]: " + oldest);
        long stop = System.nanoTime();
        return (stop - start);
    }

public class ForkListProcessing extends RecursiveTask<Programmer> {

    private final int start;
    private final int length;
    private final List<Programmer> list;
    private static final int SERIAL_THRESHOLD = 5_000_000;

    public ForkListProcessing(int start, int length, List<Programmer> list) {
        this.start = start;
        this.length = length;
        this.list = list;
    }

    @Override
    public Programmer compute() {
        if (length <= SERIAL_THRESHOLD) {
            return serialSolver();
        } else {
            int startLeft = start;
            int lengthLeft = length / 2;
            int startRight = start + lengthLeft;
            int lengthRight = length - lengthLeft;
            RecursiveTask<Programmer> left = new ForkListProcessing(startLeft, lengthLeft, list);
            RecursiveTask<Programmer> right = new ForkListProcessing(startRight, lengthRight, list);
            left.fork();
            right.fork();
            Programmer leftSolution = left.join();
            Programmer rightSolution = right.join();
            if (leftSolution.getProgrammingLanguage().getLevel() >= rightSolution.getProgrammingLanguage().getLevel()) {
                return leftSolution;
            } else {
                return rightSolution;
            }
        }
    }

    private Programmer serialSolver() {
        Programmer oldest = list.get(start);
        Programmer programmer;
        for (int i = start + 1, j = 1; j < length; ++j, ++i) {
            programmer = list.get(i);
            if (programmer.getAge() > 40 && programmer.getProgrammingLanguage().getName().equalsIgnoreCase("java")) {
                if (programmer.getProgrammingLanguage().getLevel() > oldest.getProgrammingLanguage().getLevel()) {
                    oldest = programmer;
                }
            }
        }
        return oldest;
    }

}


Dönüştür-İndirge Çatısının Kullanımı

private static long parallelSolveMapReduce(List<Programmer> list) {
        long start = System.nanoTime();
        Programmer oldest = list.parallelStream()
                .filter((Programmer prg) -> prg.getAge() > 40 && prg.getProgrammingLanguage().getName().equalsIgnoreCase("java"))
                .reduce((left, right) -> left.getProgrammingLanguage().getLevel() >= right.getProgrammingLanguage().getLevel() ? left : right).get();

        System.err.println("Oldest [MapReduce]: " + oldest);
        long stop = System.nanoTime();
        return (stop - start);
    }

Deneysel Sonuçlar

Örnek uygulama 4 fiziksel çekirdeğin ve 8 sanal işlemcinin olduğu i7 işlemcili bir dizüstü bilgisayarda (Asus N56VZ-S4255H), Hotspot Java Sanal Makinası üzerinde koşturulmuştur. Kullanılan Hotspot parametreleri aşağıdaki gibidir: -Xms5096m -Xmx6096m -XX:+UseG1GC -Xcomp.
Sürelerin yer aldığı tabloyu aşağıda izleyebilirsiniz:
Serial Fork/Join Map-Reduce Callable Thread
2668505068 287335266 2944504598 371450954
1520665434 256402330 220530644 297969676
1466104840 248664714 216625912 344167024
1464082558 253262636 218372802 298162542
1516772250 255020642 239981168 302813920
1524937500 248614678 214424022 298976328
1466208756 248644184 213896322 371450954
1465564738 250441532 213332274 299036198
1478366830 384069164 211912956 299329982
1459489336 248061746 211580254 295550550
Tablodaki değerler yöntemin aldığı süreyi nano saniye biriminde vermektedir. Her bir satır yinelemeyi göstermektedir. Her bir yöntem 10 defa yinelenerek tekrar çalıştırılmıştır. Karşılaştırmayı daha kolay yapabilmek için aşağıdaki grafikten  yararlanabilirsiniz:
Aynı uygulamanın i7 işlemcili 2 fiziksel çekirdeği 4 sanal işlemcisi bulunan Samsung ultrabook (NP-900X4C)'daki başarımı aşağıdaki gibi gerçekleşmiştir:
Serial Fork/Join Map-Reduce Callable Thread
19741743117 580408617 4701616707 626018473
1806017710 448612143 496646177 424259928
1848274359 417033783 502213371 484369695
1597769920 462913357 499692308 408970979
1610160940 416298935 486345985 423605953
1606404182 435899678 534527811 426688621
1625213014 408824009 486342701 626018473
1632420684 422062362 576433047 435159082
1680902197 592661288 489187262 724467204
1653464440 430231904 493916565 406734823
Yukarıdaki tabloyu daha kolay yorumlayabilmek için aşağıdaki grafikten yararlanabilirsiniz:

En iyi sonucu 8 sanal işlemcili makinada Map-Reduce ile 4 sanal işlemcili makinada Callable Thread kullanarak elde ediyoruz. Genel olarak sonuçların biri birine çok yakın olduğunu söyleyebiliriz. Map-Reduce'un farkını daha yüksek çekirdek sayılarında görebiliriz. Map-Reduce ile elde edilen en önemli kazanç geliştirme süresinin kısa olması, testinin ve bakımının kolay olmasıdır. Çözüm yaklaşık 10 satırlık bir kod parçasından oluşmaktadır. Burada Callable Thread ve Fork/Join çözümleri özellikle en iyilenmemiştir, ortalama başarımları karşılaştırılmaya çalışılmıştır. Kodun tamamına NetBeans projesi olarak bu adresten ulaşabilirsiniz.
View Binnur Kurt's profile on LinkedIn

Monday, September 15, 2014

Java 8'de String Deduplication


Oracle'ın Java 8'i Mart 2014'de duyurmasından tam 6 ay geçti. Şu an JDK'nın en güncel sürümü 8u20. Son sürümü bu bağlantıdan indirip deneyebilirsiniz. Artık platform olgunlaşmaya başladı. JDK 8u20 ile gelen birkaç ilginç yenilik de bulunuyor. Bu özelliklerden biriyle G1 çöp toplayıcısının kullanılması durumunda karşılaşıyoruz. Garbage First (G1), çöp toplama zamanı elvermesi halinde, karakter katarlarını sabit havuzuna alıyor (String Deduplication). Sabit havuzu eskiden PermGen olarak adlandırılan alanda yer alıyordu. Artık Java 8'de PermGen yok, yerine MetaSpace olarak adlandırılan bellek bölgesi geldi. MetaSpace alanının eski PermGen'e göre en önemli kazanımı, dinamik olarak genişleyebiliyor olmasıdır. Sabit havuzunu derleme aşamasında değeri belirli String, bir sekizliğe sığan ([-128..127])Integer gibi tamsayı sınıfları kullanır:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestConstantPool {
    public static void main(String[] args) {
        String s1= "Hello"; // CP
        String s2= "Hello"; // CP
        // String is immutable
        String s3= new String("Hello"); // Heap (Eden)
        String s4= new String("Hello"); // Heap (Eden)
        if (s1==s2){
            System.out.println("s1 is equal to s2");
        }else{
            System.out.println("s1 is NOT equal to s2");
        }
        if (s4==s3){
            System.out.println("s1 is equal to s3");
        }else{
            System.out.println("s1 is NOT equal to s3");
        }
        s3= s3.intern();
        if (s1==s3){
            System.out.println("s1 is equal to s3");
        }else{
            System.out.println("s1 is NOT equal to s3");
        }       
    }
}
Burada s1 ve s2 sabit havuzunda yaratılan aynı karakter katarını ("Hello") gösterirken s3 ve s4 Heap'de, içeriği aynı olsa da farklı bellek adreslerinde yaratılan nesneleri gösterir. Bu yüzden 8. satırdaki karşılaştırma doğru, 13. satırdaki karşılaştırma yanlıştır. String sınıfında intern() isimli bir çağrı yer alır. Bu çağrıyı kullanarak bir String nesnesini sabit havuzuna almak mümkündür. 18. satırdaki s3= s3.intern() çağrısı ile artık s3 sabit havuzundaki "Hello" katarını göstermektedir. Böylelikle s1, s2 ve s3 artık aynı nesneyi gösterir. Bu yüzden 19. satırdaki s1==s3 karşılaştırması doğrudur.
Aşağıdaki kodda ise 108 bir sekizliğe sığdığı için a ve b sabit havuzunda, 549 ise sekizliğe sığmadığı için m ve n ise Heap'deki nesneleri gösterir:
public class TestInteger {
    public static void main(String[] args) {
        Integer a= 108; // CP
        Integer b= 108; // CP
        Integer m= 549; // Heap (Eden)
        Integer n= 549; // Heap (Eden)
        if (a==b){
            System.out.println("a is equal to b");
        } else {
            System.out.println("a is NoT equal to b");
        }
        if (m==n)){
            System.out.println("m is equal to n");
        } else {
            System.out.println("m is NoT equal to n");
        }
    }
}
Bu yüzden a==b karşılaştırması doğru iken m==n karşılaştırması yanlıştır. 
"String deduplication" özelliği normalde kapalı geliyor. Bu özelliği açmak için Hotspot'u -XX:+UseG1GC -XX:+UseStringDeduplication seçenekleri ile başlatmalısınız. Aradaki farkı izlemek için aşağıdaki program parçasını kullanalım. 
public class LotsOfStrings {
 
  private static final LinkedList<String> LOTS_OF_STRINGS = new LinkedList<>();
 
  public static void main(String[] args) throws Exception {
    int iteration = 0;
    while (true) {
      for (int i = 0; i < 100; i++) {
        for (int j = 0; j < 1000; j++) {
          LOTS_OF_STRINGS.add(new String("String " + j));
        }
      }
      iteration++;
      System.out.println("Survived Iteration: " + iteration);
      Thread.sleep(100);
    }
  }
}
Kodu dikkatlice incelediğimizde sonsuz döngü içinde sürekli String nesnesi yaratıldığını görüyoruz. Kodu çalıştırdığımızda kaçınılmaz olarak Yetersiz Bellek Hatası (java.lang.OutOfMemoryError) verecektir. 128MB kapasiteli bir heap için bakalım kaç döngü dayanabilecek: -Xmx128m -Xms128m
Survived Iteration: 1
Survived Iteration: 2
Survived Iteration: 3
Survived Iteration: 4
Survived Iteration: 5
Survived Iteration: 6
Survived Iteration: 7
Survived Iteration: 8
Survived Iteration: 9
Survived Iteration: 10
Survived Iteration: 11
Survived Iteration: 12
Survived Iteration: 13
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
 at com.example.console.LotsOfStrings.main(LotsOfStrings.java:18)
Şimdi tekrar bu özelliği açarak yeniden çalıştıralım ve aradaki farkı görmeye çalışalım: 
-Xms128m -Xmx128m -XX:+UseG1GC -XX:+UseStringDeduplication
Survived Iteration: 1
Survived Iteration: 2
Survived Iteration: 3
Survived Iteration: 4
Survived Iteration: 5
Survived Iteration: 6
Survived Iteration: 7
Survived Iteration: 8
Survived Iteration: 9
Survived Iteration: 10
Survived Iteration: 11
Survived Iteration: 12
Survived Iteration: 13
Survived Iteration: 14
Survived Iteration: 15
Survived Iteration: 16
Survived Iteration: 17
Survived Iteration: 18
Survived Iteration: 19
Survived Iteration: 20
Survived Iteration: 21
Survived Iteration: 22
Survived Iteration: 23
Survived Iteration: 24
Survived Iteration: 25

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Java Result: 1
Şimdi döngü 25 defa dönebildi ve ardından Yetersiz Bellek Hatası (java.lang.OutOfMemoryError) aldık. Peki bu özelliği açmak bize ne kazandıracak? Bu soruyu cevaplamak için aşağıdaki istatistiğe bakmak yeterli olur:
  • Heap'deki canlı nesnelerin ortalama dörtte biri String nesnesidir.
  • Heap'de yinelenen String oranı ortalama %13.5'dir.
  • Ortalama String uzunluğu 45'dir.
Heap üzerinde ortalama olarak %5-10'luk bir rahatlama getirmesi beklenmelidir. Buna karşılık olarak yinelenen String nesnelerin sabit havuzuna atmak için G1 çöp toplayıcısının işlemci zamanımızdan çaldığını ve bunun bir maliyeti olduğunu unutmayalım. Son bir nokta, G1 çöp toplayıcı eğer vakit kalırsa bu işlemi yaptığını ve kodda çöp toplayıcıya vakit tanımak için uygulamanın uykuya daldığını (Thread.sleep(100)) hatırlatırım. Thread.sleep(100) satırını koddan çıkarıp, tekrar çalıştırırsak uygulamanın tepkisi aşağıdaki gibi olmaktadır:
Survived Iteration: 1
Survived Iteration: 2
Survived Iteration: 3
Survived Iteration: 4
Survived Iteration: 5
Survived Iteration: 6
Survived Iteration: 7
Survived Iteration: 8
Survived Iteration: 9
Survived Iteration: 10
Survived Iteration: 11
Survived Iteration: 12
Survived Iteration: 13
Survived Iteration: 14
Survived Iteration: 15
Survived Iteration: 16
Survived Iteration: 17
Survived Iteration: 18
Survived Iteration: 19

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Java Result: 1 
Deduplication yükünü kontrol etmek için kullanabileceğimiz bir parametremiz var: -XX:StringDeduplicationAgeThreshold. Değerini artırarak bu en iyilemenin genç String nesneleri yerine, yaşı geçkince olan String’lere uygulanması sağlanabilir. Varsayılan değeri 3’dür.
Bu özelliği açmanın gerçekten işe yarayıp yaramadığına karar vermek için yapılan işlemin başarımını kayıtlardan izlemek gerekir. Bunun için -XX:+PrintStringDeduplicationStatistics seçeneğini vermek yeterli olur:
Survived Iteration: 1
[GC concurrent-string-deduplication, 2131.9K->39.3K(2092.6K), avg 98.2%, 0.0121525 secs]
   [Last Exec: 0.0121525 secs, Idle: 0.0000008 secs, Blocked: 0/0.0000000 secs]
      [Inspected:           54680]
         [Skipped:              0(  0.0%)]
         [Hashed:           54680(100.0%)]
         [Known:                0(  0.0%)]
         [New:              54680(100.0%)   2131.9K]
      [Deduplicated:        53678( 98.2%)   2092.6K( 98.2%)]
         [Young:                0(  0.0%)      0.0B(  0.0%)]
         [Old:              53678(100.0%)   2092.6K(100.0%)]
   [Total Exec: 1/0.0121525 secs, Idle: 1/0.0000008 secs, Blocked: 0/0.0000000 secs]
      [Inspected:           54680]
         [Skipped:              0(  0.0%)]
         [Hashed:           54680(100.0%)]
         [Known:                0(  0.0%)]
         [New:              54680(100.0%)   2131.9K]
      [Deduplicated:        53678( 98.2%)   2092.6K( 98.2%)]
         [Young:                0(  0.0%)      0.0B(  0.0%)]
         [Old:              53678(100.0%)   2092.6K(100.0%)]
   [Table]
      [Memory Usage: 49.5K]
      [Size: 1024, Min: 1024, Max: 16777216]
      [Entries: 1770, Load: 172.9%, Cached: 0, Added: 1770, Removed: 0]
      [Resize Count: 0, Shrink Threshold: 682(66.7%), Grow Threshold: 2048(200.0%)]
      [Rehash Count: 0, Rehash Threshold: 120, Hash Seed: 0x0]
      [Age Threshold: 3]
   [Queue]
      [Dropped: 0]
Burada ilk bakılması gereken satır özetçenin yer aldığı ilk satırdır:
[GC concurrent-string-deduplication, 2131.9K->39.3K(2092.6K), avg 98.2%, 0.0121525 secs]
Bu satırdan, String deduplication için ne kadar süre harcandığı ve ortalama ne kadar yineleme yakalandığını okumak mümkündür. Karar sizin.
View Binnur Kurt's profile on LinkedIn