2012-03-27 7 views
12

मैं जावा एप्लिकेशन बनाने की प्रक्रिया में हूं जो लंबे समय तक चल रहा है जिसके लिए बिना बंद किए अद्यतन की कार्यक्षमता की आवश्यकता होती है। मैंने इस अद्यतन कार्यक्षमता को .java फ़ाइलों के रूप में लोड करके (डेटाबेस से बाइट सरणी के रूप में खींचा) प्रदान करने का निर्णय लिया है जो स्मृति और त्वरित में संकलित हैं। यदि आपके पास बेहतर तरीका है तो मैं सभी कान हूं।जावा लोडिंग और अनलोडिंग .java फ़ाइलें गतिशील रूप से, कचरा संग्रह?

जिस समस्या में मैंने भाग लिया है वह यह है कि जब मैं कृत्रिम वातावरण में कुछ परीक्षण करता हूं तो इन "स्क्रिप्ट" को लोड करने के प्रत्येक चक्र के साथ मेमोरी पदचिह्न थोड़ा बढ़ता है।

नोट: यह वास्तव में मेरा पहला समय जावा या बिल्कुल कुछ ऐसा करने जैसा है। मैंने लोडिंग और अनलोडिंग .cs फ़ाइलों के साथ सी # में पहले ऐसा कुछ हासिल किया था और मेमोरी पदचिह्न के मुद्दों को भी हल किया था ... हल करने के लिए कि मैंने उन्हें एक अलग ऐपडोमेन में लोड किया और जब मैंने फ़ाइलों को दोबारा बनाया, तो मैंने उस ऐपडोमेन को उतार दिया और बनाया नया।

प्रवेश बिंदु


इस प्रविष्टि विधि है कि मैं का उपयोग करें (कई recompile चक्र) की लंबी अवधि के बाद स्मृति पदचिह्न अनुकरण करने के लिए उपयोग कर रहा हूँ है। मैं इसे थोड़े समय के लिए चलाता हूं और यह जल्दी से 500 एमबी + खाता है।

यह अस्थायी निर्देशिका में केवल दो डमी स्क्रिप्ट के साथ है।

public static void main(String[ ] args) throws Exception { 
    for (int i = 0; i < 1000; i++) { 
     Container[ ] containers = getScriptContainers(); 
     Script[ ] scripts = compileScripts(containers); 

     for (Script s : scripts) s.Begin(); 
     Thread.sleep(1000); 
    } 
} 

लिपियों (अस्थायी) की एक सूची एकत्रित


यह अस्थायी विधि मैं स्क्रिप्ट फ़ाइलों की एक सूची इकट्ठा करने के लिए उपयोग कर रहा हूँ है। उत्पादन के दौरान इन्हें वास्तव में बाइट एरे के रूप में लोड किया जाएगा, जिसमें कुछ अन्य जानकारी जैसे डाटाबेस से कक्षा का नाम होता है।

@Deprecated 
private static Container[ ] getScriptContainers() throws IOException { 
    File root = new File("C:\\Scripts\\"); 
    File[ ] files = root.listFiles(); 

    List<Container> containers = new ArrayList<>(); 
    for (File f : files) { 
     String[ ] tokens = f.getName().split("\\.(?=[^\\.]+$)"); 
     if (f.isFile() && tokens[ 1 ].equals("java")) { 
      byte[ ] fileBytes = Files.readAllBytes(Paths.get(f.getAbsolutePath())); 
      containers.add(new Container(tokens[ 0 ], fileBytes)); 
     } 
    } 

    return containers.toArray(new Container[ 0 ]); 
} 

कंटेनर वर्ग


यह सरल कंटेनर वर्ग है।

public class Container { 
    private String className; 
    private byte[ ] classFile; 

    public Container(String name, byte[ ] file) { 
     className = name; 
     classFile = file; 
    } 

    public String getClassName() { 
     return className; 
    } 

    public byte[ ] getClassFile() { 
     return classFile; 
    } 
} 

लिपियों


यह वास्तविक विधि है कि जावा फ़ाइलों संकलित करता है तथा स्क्रिप्ट वस्तुओं में को दर्शाता है संकलन।

private static Script[ ] compileScripts(Container[ ] containers) throws InstantiationException, IllegalAccessException, ClassNotFoundException { 
    List<ClassFile> sourceScripts = new ArrayList<>(); 
    for (Container c : containers) 
     sourceScripts.add(new ClassFile(c.getClassName(), c.getClassFile())); 

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
    JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null)); 

    compiler.getTask(null, manager, null, null, null, sourceScripts).call(); 

    List<Script> compiledScripts = new ArrayList<>(); 
    for (Container c : containers) 
     compiledScripts.add((Script)manager.getClassLoader(null).loadClass(c.getClassName()).newInstance()); 

    return (Script[ ])compiledScripts.toArray(new Script[ 0 ]); 
} 

MemoryFileManager वर्ग


यह कस्टम JavaFileManager कार्यान्वयन है कि मैं संकलक के लिए बनाए गए ताकि मैं स्मृति में के बजाय शारीरिक .class फ़ाइलों में उत्पादन स्टोर कर सकते हैं है।

public class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> { 
    private HashMap< String, ClassFile > classes = new HashMap<>(); 

    public MemoryFileManager(StandardJavaFileManager standardManager) { 
     super(standardManager); 
    } 

    @Override 
    public ClassLoader getClassLoader(Location location) { 
     return new SecureClassLoader() { 
      @Override 
      protected Class<?> findClass(String className) throws ClassNotFoundException { 
       if (classes.containsKey(className)) { 
        byte[ ] classFile = classes.get(className).getClassBytes(); 
        return super.defineClass(className, classFile, 0, classFile.length); 
       } else throw new ClassNotFoundException(); 
      } 
     }; 
    } 

    @Override 
    public ClassFile getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) { 
     if (classes.containsKey(className)) return classes.get(className); 
     else { 
      ClassFile classObject = new ClassFile(className, kind); 
      classes.put(className, classObject); 
      return classObject; 
     } 
    } 
} 

classfile वर्ग


यह मेरा बहुउद्देश्यीय SimpleJavaFileObject कार्यान्वयन है कि मैं स्मृति में स्रोत जावा फ़ाइलें और .class संकलित फ़ाइलों को स्टोर करने के लिए उपयोग है।

public class ClassFile extends SimpleJavaFileObject { 
    private byte[ ] source; 
    protected final ByteArrayOutputStream compiled = new ByteArrayOutputStream(); 

    public ClassFile(String className, byte[ ] contentBytes) { 
     super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 
     source = contentBytes; 
    } 

    public ClassFile(String className, CharSequence contentCharSequence) throws UnsupportedEncodingException { 
     super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 
     source = ((String)contentCharSequence).getBytes("UTF-8"); 
    } 

    public ClassFile(String className, Kind kind) { 
     super(URI.create("string:///" + className.replace('.', '/') + kind.extension), kind); 
    } 

    public byte[ ] getClassBytes() { 
     return compiled.toByteArray(); 
    } 

    public byte[ ] getSourceBytes() { 
     return source; 
    } 

    @Override 
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws UnsupportedEncodingException { 
     return new String(source, "UTF-8"); 
    } 

    @Override 
    public OutputStream openOutputStream() { 
     return compiled; 
    } 
} 

स्क्रिप्ट इंटरफ़ेस


और अंत में सरल स्क्रिप्ट इंटरफ़ेस।

public interface Script { 
    public void Begin() throws Exception; 
} 

मैं अभी भी नई तरह की है जब यह प्रोग्रामिंग करने के लिए आता है और मैं छोटे समस्याओं मैं सामना करना पड़ा करने के लिए कुछ समाधान खोजने के लिए थोड़ी देर के लिए ढेर का इस्तेमाल किया है कर रहा हूँ, यह मेरी पहली बार एक सवाल इसलिए पूछ रहा माफी माँगता है अगर मैंने बहुत अधिक जानकारी शामिल की है या यदि यह बहुत लंबा है; मैं बस यह सुनिश्चित करना चाहता था कि मैं पूरी तरह से था।

+0

आप स्मृति पदचिह्न को कैसे मापते हैं? जावा में, प्रोग्राम वास्तव में * कितनी मेमोरी * का उपयोग करता है, और यह कितना * उपयोग के लिए आरक्षित * के बीच एक गहरा अंतर है। एक अच्छी तरह से रखे प्रश्न के लिए –

+0

+1, मुझे इसका जवाब देखने में दिलचस्पी है। क्या आपने मौका से जावा प्रतिबिंब देखा है? – FloppyDisk

+0

मैं अपने टास्क मैनेजर में विशिष्ट javaw.exe प्रक्रिया के लिए बढ़ी हुई स्मृति उपयोग को ध्यान में रखते हुए एक्लिप्स मेमोरी विश्लेषक का उपयोग कर रहा था। ऐसा लगता है कि कचरा कलेक्टर अप्रयुक्त अवशेषों को इकट्ठा करने के लिए कुछ भी नहीं कर रहा है ... इसके अलावा यह भी तथ्य है कि अगर मैं नींद को हटा देता हूं और इसे (सत्य) पर सेट करता हूं तो यह स्मृति से बाहर दुर्घटनाग्रस्त हो जाता है। – Jordan

उत्तर

5

आप संकलित कक्षाओं को लोड करने के लिए एप्लिकेशन के डिफ़ॉल्ट क्लासलोडर का उपयोग कर रहे हैं - जिससे वर्गों को कचरा इकट्ठा करना असंभव हो जाता है।

तो आप अपने हाल में संकलित कक्षाओं के लिए create a separate classloader किया है। इस प्रकार ऐप सर्वर इसे करते हैं।

हालांकि, अगर आप अपने संकलित कक्षाओं के लिए एक अलग क्लासलोडर का उपयोग करते हैं, तो भी उन कक्षाओं को कचरा संग्रह द्वारा उठाया जा सकता है, क्योंकि क्लासलोडर और लोड की गई सभी कक्षाएं कचरा collcetion के लिए योग्य नहीं हैं जब तक कि उन वर्गों में से किसी एक का एक उदाहरण किसी और को संदर्भित किया जाता है (यानी आपका शेष आवेदन)।

इसे classloader leak और ऐप्स सर्वर के साथ एक आम समस्या के रूप में जाना जाता है, जिससे पुन: नियोजन और अधिक स्मृति का उपयोग करने और अंततः विफल हो जाता है। एक क्लासलोडर रिसाव का निदान और फिक्सिंग बहुत मुश्किल हो सकता है; लेख में सभी विवरण हैं।

+0

सूचनात्मक लिंक के लिए धन्यवाद, विशेष रूप से क्लासलोडर लीक पर एक। – Jordan

संबंधित मुद्दे