12 Temmuz 2012 Perşembe

JAXB Marshaller/Unmarshaller Oluşturma Yavaşlığı


Merhaba,

Performans testleri esnasında server-side uygulamamızın beklenilenden çok daha düşük kapasite ile çalıştığının görülmesi üzerine duruma bir el attım. İlginç bir darboğaz yakaladım. Concurrent çalışan bir noktada bulunan metotlar içerisinde önce JAXBContext instance'ları oluşturulup ardından marshaller ve unmarshaller objeleri oluşturuluyordu. Yük altında bu JAXBContext objelerinin oluşturulması gerçekten yavaş olabiliyormuş. Statik tek bir JAXBContext objesi oluşturup marshaller/unmarshaller objelerini bunun üzerinden oluşturmak gerekiyor. JAXBContext thread-safe bir sınıf. Gönül rahatlığıyla tek instance üzerinden marshaller/unmarshaller newInstance çağırılabilir.

Öncelikle kısaca JAXB'den bahsedeyim. Açılımı Java Architecture for Xml Binding olan bu kütüphane java nesneleri ile xml'leri birbirine adreslememizi sağlar. İki yönlüde çalışabilmektedir. Java objelerinden xml oluşturulabilir (marshal). Yada tam tersi şekilde XML'den Java objeleri oluşturulabilir (unmarshal).

Aşağıda marshaller/unmarshaller objelerinin nasıl oluşturulduğu ve kullanıldığı görülebilir:
...
 JAXBContext jc = JAXBContext.newInstance(MyClass1.class);
 Marshaller m = jc.createMarshaller();
...
JAXBContext jc = JAXBContext.newInstance(MyClass2.class);
Unmarshaller u = jc.createUnmarshaller();
...




Küçük bir load test yazalım. Aşağıdaki sınıf içerisinde 150 thread ile 100 bin defa önce JAXBContext objeleri oluşturup ardından unmarsaller objeleri oluşturuyoruz.


public class TestJaxbUtil1 {
    private static AtomicCounter ctr = new AtomicCounter();
    public static void main(String[] args) throws JAXBException, InterruptedException {
        ExecutorService tPool = Executors.newFixedThreadPool(150);        for (int i = 0; i < 100000; i++) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    try {
                        long t1 = System.currentTimeMillis();
                        JAXBContext jc = JAXBContext.newInstance(MyClass.class);
                        jc.createUnmarshaller();
                        long t2 = System.currentTimeMillis();
                        ctr.increment();
                        System.out.println("Test# : " + ctr.value() + " : " + (t2 - t1) + " ms");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            tPool.execute(r);
        }
    }
}

Bu test esnasında lokalimde 9-10 saniyeleri gördüğüm oldu. Yapılan iyileştirme JAXBContext objesinin statik yapılıp marshaller/unmarshaller'ların bu tek instance üzerinden oluşturulmasından ibaret. Kod aşağıda:


public class TestJaxbUtil2 {
    private static AtomicCounter ctr = new AtomicCounter();
    private static JAXBContext   jc;
    public static void main(String[] args) throws JAXBException, InterruptedException {
        jc = JAXBContext.newInstance(MyClass.class);
        ExecutorService tPool = Executors.newFixedThreadPool(150);
        for (int i = 0; i < 100000; i++) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    try {
                        long t1 = System.currentTimeMillis();
                        jc.createUnmarshaller();
                        long t2 = System.currentTimeMillis();
                        ctr.increment();
                        System.out.println("Test# : " + ctr.value() + " : " + (t2 - t1) + " ms");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            tPool.execute(r);
        }
    }
}


Bu test sonuçlarına göre unmarshaller oluşturulması en fazla 2 ms sürmüş. Çoğu durumda makul bir süre sayılır :)



java.lang.NumberFormatException: For input string: "�"


Merhaba,


Bugün ilginç bir problem yaşayıp uzun aramalar sonucu çözebildik. Logta aralıklarla akan java.lang.NumberFormatException: For input string: "�"  hatası görüyorduk. 


Stack trace şöyleydi:
java.lang.NumberFormatException: For input string: "�"at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1280)at java.lang.Double.valueOf(Double.java:486)at java.lang.Double.<init>(Double.java:578)...


Öncelikle problemi çok başka yerlerde aradık.  "�" karakterleri UTF8 header'larına ait karakterler. Bir şekilde konfigürasyon dosyalarımızın encoding'i ile ilgili problem olabileceğini düşündük. Bu dosyalarla epey boğuştuktan sonra debug session'larımızdan birinde hatayı yakaladık. Hata demek haksızlık olur, hatalar zinciri diyelim :)


Öncelikle hatalı class içinde 2  double değişkeni birbirine bölüp yeni bir double elde ediyoruz:
double total = x / y;


Bölenimiz(y) bazen 0 gelebiliyormuş. Bu yüzden bir exception atılmıyor, bölüm değeri (total) NaN (Not A Number) olarak oluşuyor.


Sonrasında bu total değeri DecimalFormat ile formatlanıyor:
DecimalFormat df = new DecimalFormat("#.##");df.format(total);


Tataaam, alın size "�" :)  Çözüm çok basit, bölme işleminde bölen için 0 olup olmama kontrolü.