2016-04-04 23 views
4

GroovyShell/Groovy komut dosyalarından kaynaklanan bir bellek sızıntımız var (sondaki GroovyEvaluator koduna bakın). "< sistem sınıfı yükleyici >" tarafından yüklenirJava8'deki GroovyShell: bellek sızıntısı/yinelenen sınıflar [src code + load test sağlandı]

sınıfı "java.beans.ThreadGroupContext" 807.406.960 (33.38%) bayt işgal: Ana problemler (MAT analiz kopya macunu) vardır.

ve: "org.codehaus.groovy.reflection.ClassInfo $ ClassInfoSet $ ​​Segment" nin

16 örneklerini, "0x7004e9c80 sun.misc.Launcher $ AppClassLoader @" tarafından yüklenen işgal Biz Groovy 2.3.11 ve Java8 (1.8.0_25 tam olarak) kullandığınız

1.510.256.544 (62,44%) bayt.
Yükseltme Groovy 2.4.6 sorunu çözmez. Sadece bellek kullanımı birbirazbit, esp geliştirir. olmayan yığın. Kullandığımız
Java args: XX: + CMSClassUnloadingEnabled XX: + UseConcMarkSweepGC

BTW, https://dzone.com/articles/groovyshell-and-memory-leaks okudum. Artık gerekmediğinde GroovyShell kabuğunu sıfırlamak için ayarladık. GroovyShell(). Parse()'u kullanmak muhtemelen yardımcı olacaktır, ancak bu bizim için gerçekten bir seçenek değildir - her biri 20-100 komuttan oluşan> 10 kümeye sahibiz ve herhangi bir zamanda değiştirilebilir (çalışma zamanında) .

Ayar MaxMetaspaceSize da yardımcı olmalıdır, ancak kök sorunu gerçekten gidermez, kök nedenini kaldırmaz. Yani hala onu ezmeye çalışıyorum.


Sorunu (sonunda kod bakın) yeniden yaratmak için yükleme testi yarattı. Onu çalıştırdığınızda:

  • yığın boyutu, metaspace büyüklüğü ve sınıf sayısı ilk 3 dakika boyunca

4GB daha büyük performans çizelgeleri birkaç dakika sonra çekilen

  • Yığın Dökümü edilir artan tutun: enter image description here

    Daha önce de belirttiğim gibi yığın yığınlarını analiz etmek için MAT kullanıyorum. Öyleyse Dominator ağacı raporunu kontrol edelim:

    enter image description here

    HashMap yığın>% 30 alır. Yani daha fazla analiz edelim. İçinde ne olduğunu görelim.en karma girdileri kontrol edelim:

    enter image description here

    O 38 830 entiries bildirir. ". sınıf Senaryo" tuşları eşleşen 38 780 girdileri dahil

    Başka bir şey, "yinelenen sınıfları" raporu: yük testleri 400 G.scripts tanımlar çünkü

    enter image description here

    Biz (400 girdilerine sahip), tüm "ScriptN" sınıfları için. Hepsi $

    innerloader buldum benzer hata groovyclassloader başvurular tutan bildirdi: https://issues.apache.org/jira/browse/GROOVY-7498 (sonunda yorum ve ekteki ekran bakınız) - sorunlarının 1.8u51 için Java yükseltme ile çözümlendi. Yine de bizim için bir şey yapmadı.

    Bizim kodu:

    public class GroovyEvaluator 
    { 
        private GroovyShell shell; 
    
        public GroovyEvaluator() 
        { 
         this(Collections.<String, Object>emptyMap()); 
        } 
    
        public GroovyEvaluator(final Map<String, Object> contextVariables) 
        { 
         shell = new GroovyShell(); 
         for (Map.Entry<String, Object> contextVariable : contextVariables.entrySet()) 
         { 
          shell.setVariable(contextVariable.getKey(), contextVariable.getValue()); 
         } 
        } 
    
        public void setVariables(final Map<String, Object> answers) 
        { 
         for (Map.Entry<String, Object> questionAndAnswer : answers.entrySet()) 
         { 
          String questionId = questionAndAnswer.getKey(); 
          Object answer = questionAndAnswer.getValue(); 
          shell.setVariable(questionId, answer); 
         } 
        } 
    
        public Object evaluateExpression(String expression) 
        { 
         return shell.evaluate(expression); 
        } 
    
        public void setVariable(final String name, final Object value) 
        { 
         shell.setVariable(name, value); 
        } 
    
        public void close() 
        { 
         shell = null; 
        } 
    } 
    

    Yük testi:

    /** Run using -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC */ 
    public class GroovyEvaluatorLoadTest 
    { 
        private static int NUMBER_OF_QUESTIONS = 400; 
        private final Map<String, Object> contextVariables = Collections.emptyMap(); 
        private List<Fact> factMappings = new ArrayList<>(); 
    
        public GroovyEvaluatorLoadTest() 
        { 
         for (int i=0; i<NUMBER_OF_QUESTIONS; i++) 
         { 
          factMappings.add(new Fact("fact" + i, "question" + i)); 
         } 
        } 
    
        private void callEvaluateExpression(int iter) 
        { 
         GroovyEvaluator groovyEvaluator = new GroovyEvaluator(contextVariables); 
    
         Map<String, Object> factValues = new HashMap<>(); 
         Map<String, Object> answers = new HashMap<>(); 
         for (int i=0; i<NUMBER_OF_QUESTIONS; i++) 
         { 
          factValues.put("fact" + i, iter + "-fact-value-" + i); 
          answers.put("question" + i, iter + "-answer-" + i); 
         } 
    
         groovyEvaluator.setVariables(answers); 
         groovyEvaluator.setVariable("answers", answers); 
         groovyEvaluator.setVariable("facts", factValues); 
    
         for (Fact fact : factMappings) 
         { 
          groovyEvaluator.evaluateExpression(fact.mapping); 
         } 
         groovyEvaluator.close(); 
        } 
    
        public static void main(String [] args) 
        { 
         GroovyEvaluatorLoadTest test = new GroovyEvaluatorLoadTest(); 
    
         for (int i=0; i<995000; i++) 
         { 
          test.callEvaluateExpression(i); 
         } 
         test.callEvaluateExpression(0); 
        } 
    } 
    
    public class Fact 
    { 
        public final String factId; 
    
        public final String mapping; 
    
        public Fact(final String factId, final String mapping) 
        { 
         this.factId = factId; 
         this.mapping = mapping; 
        } 
    } 
    

    herhangi bir düşünce?

    public class GroovyEvaluator 
    { 
        private static GroovyScriptCachingBuilder groovyScriptCachingBuilder = new GroovyScriptCachingBuilder(); 
        private Map<String, Object> variables = new HashMap<>(); 
    
        public GroovyEvaluator() 
        { 
         this(Collections.<String, Object>emptyMap()); 
        } 
    
        public GroovyEvaluator(final Map<String, Object> contextVariables) 
        { 
         variables.putAll(contextVariables); 
        } 
    
        public void setVariables(final Map<String, Object> answers) 
        { 
         variables.putAll(answers); 
        } 
    
        public void setVariable(final String name, final Object value) 
        { 
         variables.put(name, value); 
        } 
    
        public Object evaluateExpression(String expression) 
        { 
         final Binding binding = new Binding(); 
         for (Map.Entry<String, Object> varEntry : variables.entrySet()) 
         { 
          binding.setProperty(varEntry.getKey(), varEntry.getValue()); 
         } 
         Script script = groovyScriptCachingBuilder.getScript(expression); 
         synchronized (script) 
         { 
          script.setBinding(binding); 
          return script.run(); 
         } 
        } 
    
    } 
    
    public class GroovyScriptCachingBuilder 
    { 
        private GroovyShell shell = new GroovyShell(); 
        private Map<String, Script> scripts = new HashMap<>(); 
    
        public Script getScript(final String expression) 
        { 
         Script script; 
         if (scripts.containsKey(expression)) 
         { 
          script = scripts.get(expression); 
         } 
         else 
         { 
          script = shell.parse(expression); 
          scripts.put(expression, script); 
         } 
         return script; 
        } 
    } 
    

    Yeni çözüm sabit seviyede de yüklü sınıfları ve Meta veri boyutunun sayısını tutar: peşin

  • cevap

    4

    Tamam içinde Thx, bu benim çözümdür. Öbeksiz ayrılmış bellek kullanımı = ~ 70 MB.

    Ayrıca: UseConcMarkSweepGC'yi artık kullanmaya gerek yok. İstediğiniz herhangi bir GC'yi seçebilir veya varsayılan bir değerle kullanabilirsiniz :)

    Komut dosyalarına erişimi eşitleme en iyi seçenek olmayabilir, ancak Metaspace boyutunu makul düzeyde tutan tek bulduğum. Ve daha da iyisi - sabit tutar. Hala. Herkes için en iyi çözüm olmayabilir ama bizim için harika çalışıyor. Bu çözümün (hemen hemen) ölçeklenebilir olduğu anlamına gelen küçük komut dizileri var. shell.evaluate (ifade) ile

    • eski yaklaşım:
     
    0 iterations took 5.03 s 
    100 iterations took 285.185 s 
    200 iterations took 821.307 s 
    
    • script.setBinding (bağlanma):

      en kullanarak GroovyEvaluator ile GroovyEvaluatorLoadTest için bazı İSTATİSTİKLERİ görelim

     
    0 iterations took 4.524 s 
    100 iterations took 19.291 s 
    200 iterations took 33.44 s 
    300 iterations took 47.791 s 
    400 iterations took 62.086 s 
    500 iterations took 77.329 s 
    

    Bunun için ek avantajı şudur: Bir önceki, sızıntı çözümüyle karşılaştırıldığında yıldırım hızında;)

    +0

    Bunun için bir demet teşekkürler. Benzer bir senaryoyu ele alıyorum: (büyük bir olasılıkla) her biri sık sık yapılan küçük betikler: https://github.com/delving/sip-creator/issues/485 –

    +0

    Esasen bellek sızıntısı devam ediyor. Yüklendikten sonra bir betik hala hiçbir zaman boşaltılamaz. Onları önbelleğe alarak, metaspace'in hızlıca doldurulmadığından emin olun, bu da güzel.Benim amaçlarım için, infazın tüm izlerini ortadan kaldıran bir çözüm bulmayı tercih ediyorum ve bunun için bir performanstan fedakarlık etmekten mutluluk duyuyorum. Görev devam ediyor ... :) –

    +0

    Evet, bu bizim KULLANIM ŞEKLİMİZ İÇİN bulmayı başardığım en iyi çözümdür :) Yazdığım gibi: her biri 20-100 betikten oluşan> 10 setimiz var. Yani ihtiyaçlarımız için tam olarak ihtiyacımız olanı yapar. İhtiyaçlarınız nedir bilmiyorum ama belki de tek yapmanız gereken GroovyScriptCachingBuilder'i ayarlamak mı? Pls, bir çözüm bulursanız çözümünüzü gönderin, diğerleri yararlı bulabilir :) –