java जावा के साथ मेमोरी रिसाव बनाना




memory memory-leaks (24)

यदि आप JDBC नहीं समझते हैं, तो निम्नलिखित एक सुंदर व्यर्थ उदाहरण है। या कम से कम जेडीबीसी उम्मीद करता है कि एक डेवलपर को finalize के कार्यान्वयन पर निर्भर रहने के बजाय, उन्हें छोड़ने या संदर्भों को खोने से पहले Connection , Statement और परिणाम सेट सेट बंद करने की अपेक्षा करें।

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

उपरोक्त के साथ समस्या यह है कि Connection ऑब्जेक्ट बंद नहीं है, और इसलिए भौतिक कनेक्शन तब तक खुला रहेगा जब तक कचरा कलेक्टर न हो और यह देख सके कि यह पहुंच योग्य नहीं है। जीसी finalize विधि का आह्वान करेगा, लेकिन ऐसे जेडीबीसी ड्राइवर हैं जो finalize लागू नहीं करते हैं, कम से कम नहीं, जिस तरह से Connection.close लागू किया गया है। परिणामस्वरूप व्यवहार यह है कि जब पहुंचने योग्य पहुंच योग्य वस्तुओं के कारण मेमोरी पुनः प्राप्त की जाएगी, तो Connection ऑब्जेक्ट से जुड़े संसाधन (स्मृति सहित) को पुनः दावा नहीं किया जा सकता है।

ऐसी घटना में जहां Connection की finalize विधि सबकुछ साफ नहीं करती है, तो वास्तव में यह पता चल सकता है कि डेटाबेस सर्वर से भौतिक कनेक्शन कई कचरा संग्रहण चक्रों तक चलेगा, जब तक कि डेटाबेस सर्वर अंततः यह नहीं बताता कि कनेक्शन जीवित नहीं है ( अगर यह करता है), और बंद होना चाहिए।

यहां तक ​​कि यदि जेडीबीसी चालक को finalize लिए लागू किया finalize , तो अंतिम रूप के दौरान अपवादों को फेंकना संभव है। परिणामी व्यवहार यह है कि अब "निष्क्रिय" ऑब्जेक्ट से जुड़ी कोई भी स्मृति पुनः दावा नहीं की जाएगी, क्योंकि finalize रूप finalize की गारंटी केवल एक बार लागू की जाती है।

ऑब्जेक्ट फाइनलाइजेशन के दौरान अपवादों का सामना करने के उपरोक्त परिदृश्य किसी अन्य परिदृश्य से संबंधित है जो संभावित रूप से स्मृति रिसाव - वस्तु पुनरुत्थान का कारण बन सकता है। किसी अन्य वस्तु से ऑब्जेक्ट को अंतिम रूप देने से मजबूत संदर्भ बनाकर ऑब्जेक्ट पुनरुत्थान अक्सर जानबूझकर किया जाता है। जब वस्तु पुनरुत्थान का दुरुपयोग किया जाता है तो यह मेमोरी लीक के अन्य स्रोतों के साथ संयोजन में स्मृति रिसाव का कारण बनता है।

ऐसे कई उदाहरण हैं जिन्हें आप अपनाना चाहते हैं

  • एक List उदाहरण प्रबंधित करना जहां आप केवल सूची में जोड़ रहे हैं और इससे हट नहीं रहे हैं (हालांकि आपको उन तत्वों से छुटकारा पाना चाहिए जिनकी आपको अब आवश्यकता नहीं है), या
  • Socket एस या File एस खोलना, लेकिन जब उन्हें अब आवश्यकता नहीं है तो उन्हें बंद नहीं करना ( Connection वर्ग से जुड़े उपर्युक्त उदाहरण के समान)।
  • जावा ईई एप्लिकेशन को कम करते समय सिंगलेट्स को अनलोड नहीं करना। जाहिर है, क्लासलोडर जो सिंगलटन क्लास को लोड करता है, कक्षा के संदर्भ को बनाए रखेगा, और इसलिए सिंगलटन इंस्टेंस कभी एकत्र नहीं किया जाएगा। जब एप्लिकेशन का एक नया उदाहरण तैनात किया जाता है, तो एक नया वर्ग लोडर आमतौर पर बनाया जाता है, और सिंगल क्लास लोडर सिंगलटन के कारण मौजूद रहेगा।

मेरे पास अभी एक साक्षात्कार था, और मुझे जावा के साथ मेमोरी रिसाव बनाने के लिए कहा गया था। कहने की जरूरत नहीं है कि मुझे बहुत मूर्ख महसूस हुआ है कि कोई भी कैसे बनाना शुरू करना है इस पर कोई सुराग नहीं है।

एक उदाहरण क्या होगा?


स्टेटिक फ़ील्ड होल्डिंग ऑब्जेक्ट रेफरेंस [एएसपी फाइनल फील्ड]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

लंबी स्ट्रिंग पर String.intern() को कॉल करना

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(खुलासा) खुली धाराएं (फ़ाइल, नेटवर्क आदि ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

अनलॉक कनेक्शन

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

वे क्षेत्र जो जेवीएम के कचरा कलेक्टर से पहुंच योग्य नहीं हैं , जैसे मूल तरीकों के माध्यम से आवंटित स्मृति

वेब अनुप्रयोगों में, कुछ ऑब्जेक्ट्स एप्लिकेशन स्कोप में तब तक संग्रहीत किए जाते हैं जब तक एप्लिकेशन स्पष्ट रूप से रोका या हटा दिया जाता है।

getServletContext().setAttribute("SOME_MAP", map);

गलत या अनुचित JVM विकल्प , जैसे कि आईबीएम noclassgc पर noclassgc विकल्प जो अप्रयुक्त वर्ग कचरा संग्रह को रोकता है

आईबीएम जेडीके सेटिंग्स देखें।


चूंकि बहुत से लोगों ने सुझाव दिया है, संसाधन लीक काफी कारण हैं - जेडीबीसी उदाहरणों की तरह। वास्तविक मेमोरी लीक थोड़ा कठिन हैं - खासकर यदि आप JVM के टूटी बिट्स पर निर्भर नहीं हैं तो यह आपके लिए ...

ऑब्जेक्ट्स बनाने के विचार जिनके पास बहुत बड़ा पदचिह्न है और फिर उन्हें एक्सेस करने में सक्षम नहीं हैं, वे वास्तविक स्मृति लीक नहीं हैं। यदि कुछ भी इसका उपयोग नहीं कर सकता है तो यह कचरा इकट्ठा किया जाएगा, और यदि कुछ इसका उपयोग कर सकता है तो यह एक रिसाव नहीं है ...

एक तरीका जो काम करने के लिए प्रयोग किया जाता था - और मुझे नहीं पता कि यह अभी भी करता है - एक तीन-गहरी परिपत्र श्रृंखला है। ऑब्जेक्ट ए में ऑब्जेक्ट बी का संदर्भ है, ऑब्जेक्ट बी में ऑब्जेक्ट सी का संदर्भ है और ऑब्जेक्ट सी के पास ऑब्जेक्ट ए का संदर्भ है। जीसी यह समझने के लिए पर्याप्त चालाक था कि एक दो गहरी श्रृंखला - जैसे कि <-> B - सुरक्षित रूप से एकत्र किया जा सकता है यदि ए और बी किसी और चीज से सुलभ नहीं हैं, लेकिन तीन-तरफा श्रृंखला को संभाल नहीं सकते ...


साक्षात्कारकर्ता शायद नीचे दिए गए कोड की तरह एक परिपत्र संदर्भ की तलाश कर रहा था (जो संयोग से केवल पुराने पुराने जेवीएम में स्मृति को रिसाव करता है जो संदर्भ गणना का उपयोग करता है, जो अब और मामला नहीं है)। लेकिन यह एक बहुत अस्पष्ट सवाल है, इसलिए यह जेवीएम मेमोरी प्रबंधन की समझ को दिखाने का एक प्रमुख अवसर है।

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

फिर आप समझा सकते हैं कि संदर्भ गिनती के साथ, उपर्युक्त कोड स्मृति को रिसाव करेगा। लेकिन अधिकांश आधुनिक जेवीएम अब संदर्भ गणना का उपयोग नहीं करते हैं, अधिकांश स्वीप कचरा कलेक्टर का उपयोग करते हैं, जो वास्तव में इस स्मृति को एकत्रित करेगा।

इसके बाद आप एक ऑब्जेक्ट बनाने की व्याख्या कर सकते हैं जिसमें अंतर्निहित देशी संसाधन है, जैसे:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

फिर आप यह समझा सकते हैं कि यह तकनीकी रूप से एक स्मृति रिसाव है, लेकिन वास्तव में रिसाव मूलभूत संसाधनों को आवंटित करने वाले जेवीएम में देशी कोड के कारण होता है, जो आपके जावा कोड से मुक्त नहीं होते थे।

दिन के अंत में, एक आधुनिक जेवीएम के साथ, आपको कुछ जावा कोड लिखने की आवश्यकता है जो JVM की जागरूकता के सामान्य दायरे से बाहर एक मूल संसाधन आवंटित करता है।


कई अलग-अलग स्थितियां स्मृति रिसाव होगी। एक का सामना करना पड़ा, जो एक नक्शा का पर्दाफाश करता है जिसे प्रकट नहीं किया जाना चाहिए और दूसरी जगह में इस्तेमाल नहीं किया जाना चाहिए।

public class ServiceFactory {

private Map<String, Service> services;

private static ServiceFactory singleton;

private ServiceFactory() {
    services = new HashMap<String, Service>();
}

public static synchronized ServiceFactory getDefault() {

    if (singleton == null) {
        singleton = new ServiceFactory();
    }
    return singleton;
}

public void addService(String name, Service serv) {
    services.put(name, serv);
}

public void removeService(String name) {
    services.remove(name);
}

public Service getService(String name, Service serv) {
    return services.get(name);
}

// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose 
public Map<String, Service> getAllServices() {
    return services;
}

}

// resource class is a heavy class
class Service {

}

मैं यहां से अपना उत्तर कॉपी कर सकता हूं: जावा में मेमोरी लीक का कारण बनने का सबसे आसान तरीका?

"कंप्यूटर विज्ञान (या रिसाव, इस संदर्भ में) में एक स्मृति रिसाव तब होता है जब एक कंप्यूटर प्रोग्राम स्मृति का उपभोग करता है लेकिन इसे ऑपरेटिंग सिस्टम पर वापस छोड़ने में असमर्थ है।" (विकिपीडिया)

आसान जवाब है: आप नहीं कर सकते। जावा स्वचालित मेमोरी प्रबंधन करता है और उन संसाधनों को मुक्त करेगा जो आपके लिए आवश्यक नहीं हैं। आप इसे होने से नहीं रोक सकते हैं। यह हमेशा संसाधनों को जारी करने में सक्षम होगा। मैन्युअल मेमोरी प्रबंधन के साथ कार्यक्रमों में, यह अलग है। आप malloc () का उपयोग कर सी में कुछ स्मृति प्राप्त नहीं कर सकते हैं। स्मृति को मुक्त करने के लिए, आपको उस पॉइंटर की आवश्यकता होती है जो मॉलोक लौटा और उस पर मुफ्त () कॉल करें। लेकिन अगर आपके पास अब पॉइंटर नहीं है (अधिलेखित, या आजीवन पार हो गया है), तो आप दुर्भाग्य से इस स्मृति को मुक्त करने में असमर्थ हैं और इस प्रकार आपके पास स्मृति रिसाव है।

अब तक के सभी अन्य उत्तर मेरी परिभाषा में वास्तव में स्मृति रिसाव नहीं हैं। वे सभी लक्ष्यहीन वस्तुओं के साथ स्मृति को भरने का लक्ष्य रखते हैं। लेकिन किसी भी समय आप अभी भी बनाई गई वस्तुओं को कम कर सकते हैं और इस प्रकार स्मृति को मुक्त कर सकते हैं -> कोई लीक नहीं। acconrad का जवाब बहुत करीब आता है, हालांकि मुझे स्वीकार करना है क्योंकि उसका समाधान कचरा कलेक्टर को अंतहीन लूप में मजबूर कर "क्रैश" करने के लिए प्रभावी रूप से प्रभावी है।

लंबा जवाब यह है: आप जेएनआई का उपयोग कर जावा के लिए लाइब्रेरी लिखकर मेमोरी रिसाव प्राप्त कर सकते हैं, जिसमें मैन्युअल मेमोरी प्रबंधन हो सकता है और इस प्रकार मेमोरी लीक हो सकती है। यदि आप इस पुस्तकालय को कॉल करते हैं, तो आपकी जावा प्रक्रिया स्मृति को रिसाव कर देगी। या, आप JVM में बग्स प्राप्त कर सकते हैं, ताकि JVM स्मृति को खो देता है। शायद JVM में कीड़े हैं, कुछ ज्ञात भी हो सकते हैं क्योंकि कचरा संग्रह छोटा नहीं है, लेकिन फिर भी यह एक बग है। डिजाइन द्वारा यह संभव नहीं है। आप ऐसे जावा कोड के लिए पूछ रहे हैं जो इस तरह की एक बग से प्रभावित होता है। क्षमा करें, मुझे एक नहीं पता है और यह अगले जावा संस्करण में अब भी एक बग नहीं हो सकता है।


आप उस कक्षा की अंतिम विधि में कक्षा का एक नया उदाहरण बनाकर एक चलती स्मृति रिसाव बना सकते हैं। बोनस पॉइंट्स अगर फ़ाइनलज़र कई उदाहरण बनाता है। यहां एक साधारण कार्यक्रम है जो आपके ढेर के आकार के आधार पर कुछ सेकंड और कुछ मिनटों के बीच पूरे ढेर को लीक करता है:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}

किसी भी सर्वलेट कंटेनर (टोमकैट, जेट्टी, ग्लासफ़िश, जो भी ...) में चल रहे किसी भी वेब एप्लिकेशन को ले जाएं। पंक्ति में ऐप को 10 या 20 बार पुन: नियोजित करें (यह सर्वर की ऑटोोडोल्ड निर्देशिका में WAR को स्पर्श करने के लिए पर्याप्त हो सकता है।

जब तक किसी ने वास्तव में इसका परीक्षण नहीं किया है, तो संभावना है कि आपको कुछ पुनर्निर्माण के बाद आउटऑफमेमरी एरर मिलेगा, क्योंकि एप्लिकेशन ने खुद को साफ करने की परवाह नहीं की थी। आप इस परीक्षण के साथ अपने सर्वर में एक बग भी पा सकते हैं।

समस्या यह है कि कंटेनर का जीवनकाल आपके आवेदन के जीवनकाल से अधिक लंबा है। आपको यह सुनिश्चित करना होगा कि कंटेनर को आपके संदर्भ के ऑब्जेक्ट्स या कक्षाओं के सभी संदर्भों को कचरा इकट्ठा किया जा सकता है।

यदि आपके वेब ऐप की बेरोजगारी से बचने वाला एक संदर्भ है, तो संबंधित क्लासलोडर और इसके परिणामस्वरूप आपके वेब ऐप के सभी वर्ग कचरा एकत्र नहीं किए जा सकते हैं।

आपके आवेदन द्वारा शुरू किए गए थ्रेड, थ्रेडलोकल वैरिएबल, लॉगिंग एपेंडर कुछ सामान्य संदिग्ध हैं जो क्लासलोडर लीक का कारण बनते हैं।


आप sun.misc.Unsafe क्लास के साथ मेमोरी लीक बनाने में सक्षम हैं। वास्तव में इस सेवा वर्ग का प्रयोग विभिन्न मानक वर्गों में किया जाता है (उदाहरण के लिए java.nio कक्षाओं में)। आप सीधे इस वर्ग का उदाहरण नहीं बना सकते हैं , लेकिन आप ऐसा करने के लिए प्रतिबिंब का उपयोग कर सकते हैं

कोड ग्रहण आईडीई में संकलित नहीं है - इसे कमांड javac का उपयोग करके संकलित करें (संकलन के दौरान आपको चेतावनियां मिलेंगी)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}

मैं हाल ही में एक और सूक्ष्म संसाधन संसाधन रिसाव में आया था। हम क्लास लोडर के getResourceAsStream के माध्यम से संसाधन खोलते हैं और ऐसा हुआ कि इनपुट स्ट्रीम हैंडल बंद नहीं थे।

उहम, आप कह सकते हैं, क्या मूर्ख है।

खैर, यह दिलचस्प क्या बनाता है: इस तरह, आप JVM के ढेर के बजाय अंतर्निहित प्रक्रिया की ढेर स्मृति को रिसाव कर सकते हैं।

आपको बस एक फ़ाइल के साथ एक जार फ़ाइल चाहिए जिसमें जावा कोड से संदर्भित किया जाएगा। जार फ़ाइल जितनी बड़ी होगी, तेज स्मृति आवंटित हो जाएगी।

आप निम्न वर्ग के साथ आसानी से ऐसे जार बना सकते हैं:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

बस BigJarCreator.java नाम की एक फ़ाइल में पेस्ट करें, संकलित करें और इसे कमांड लाइन से चलाएं:

javac BigJarCreator.java
java -cp . BigJarCreator

और voilà: आप अपनी वर्तमान कामकाजी निर्देशिका में अंदर दो फाइलों के साथ एक जार संग्रह मिलता है।

आइए दूसरी कक्षा बनाएं:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

यह वर्ग मूल रूप से कुछ भी नहीं करता है, लेकिन बिना संदर्भित इनपुटस्ट्रीम ऑब्जेक्ट्स बनाते हैं। उन वस्तुओं को कचरा तुरंत इकट्ठा किया जाएगा और इस प्रकार, ढेर आकार में योगदान नहीं है। हमारे उदाहरण के लिए एक जार फ़ाइल से मौजूदा संसाधन लोड करना महत्वपूर्ण है, और आकार यहां मायने रखता है!

यदि आप संदिग्ध हैं, तो उपरोक्त वर्ग को संकलित करने और शुरू करने का प्रयास करें, लेकिन एक सभ्य ढेर आकार (2 एमबी) चुनना सुनिश्चित करें:

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

आपको ओओएम त्रुटि का सामना नहीं करना पड़ेगा, क्योंकि कोई संदर्भ नहीं रखा गया है, आवेदन जारी रहेगा इससे कोई फर्क नहीं पड़ता कि आपने उपर्युक्त उदाहरण में इवेंटेशन कितना बड़ा चुना है। आपकी प्रक्रिया की स्मृति खपत (शीर्ष (आरईएस / आरएसएस) या प्रक्रिया एक्सप्लोरर में दिखाई देने वाली) बढ़ती है जब तक कि एप्लिकेशन प्रतीक्षा आदेश तक नहीं पहुंच जाता। उपरोक्त सेटअप में, यह स्मृति में लगभग 150 एमबी आवंटित करेगा।

यदि आप एप्लिकेशन को सुरक्षित खेलना चाहते हैं, तो इनपुट स्ट्रीम को जहां इसे बनाया गया है, बंद करें:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

और पुनरावृत्ति गणना से स्वतंत्र, आपकी प्रक्रिया 35 एमबी से अधिक नहीं होगी।

काफी सरल और आश्चर्यजनक।


जवाब पूरी तरह से इस बात पर निर्भर करता है कि साक्षात्कारकर्ता ने सोचा कि वे क्या पूछ रहे थे।

जावा रिसाव बनाने के लिए अभ्यास में यह संभव है? बेशक यह है, और अन्य उत्तरों में कई उदाहरण हैं।

लेकिन कई मेटा-प्रश्न हैं जिन्हें पूछा जा सकता है?

  • एक सैद्धांतिक रूप से "सही" जावा कार्यान्वयन लीक के लिए कमजोर है?
  • क्या उम्मीदवार सिद्धांत और वास्तविकता के बीच अंतर को समझता है?
  • क्या उम्मीदवार समझता है कि कचरा संग्रह कैसे काम करता है?
  • या एक आदर्श मामले में कचरा संग्रह कैसे काम करना चाहिए?
  • क्या वे जानते हैं कि वे देशी इंटरफेस के माध्यम से अन्य भाषाओं को कॉल कर सकते हैं?
  • क्या वे उन अन्य भाषाओं में स्मृति को रिसाव करने के बारे में जानते हैं?
  • क्या उम्मीदवार यह भी जानता है कि मेमोरी प्रबंधन क्या है, और जावा में दृश्य के पीछे क्या चल रहा है?

मैं आपके मेटा-प्रश्न को पढ़ रहा हूं "इस साक्षात्कार की स्थिति में मैं क्या जवाब दे सकता था"। और इसलिए, मैं जावा के बजाय साक्षात्कार कौशल पर ध्यान केंद्रित करने जा रहा हूं। मेरा मानना ​​है कि जावा रिसाव को बनाने के तरीके के बारे में जानने की आवश्यकता के मुकाबले एक साक्षात्कार में किसी प्रश्न के उत्तर को नहीं जानने की स्थिति को दोहराने की संभावना अधिक है। तो, उम्मीद है कि यह मदद करेगा।

साक्षात्कार के लिए आप विकसित कर सकते हैं सबसे महत्वपूर्ण कौशल में से एक है सक्रिय रूप से प्रश्नों को सुनना और साक्षात्कारकर्ता के साथ अपने इरादे को निकालने के लिए काम करना सीखना। न केवल यह आपको उनके प्रश्न का उत्तर देने के तरीके का उत्तर देता है, बल्कि यह भी दिखाता है कि आपके पास कुछ महत्वपूर्ण संचार कौशल हैं। और जब यह कई समान प्रतिभाशाली डेवलपर्स के बीच एक विकल्प के लिए आता है, तो मैं उस व्यक्ति को किराए पर लेता हूं जो हर बार जवाब देने से पहले सुनता है, सोचता है और समझता है।


यहां सबसे अधिक उदाहरण "बहुत जटिल" हैं। वे किनारे के मामले हैं। इन उदाहरणों के साथ, प्रोग्रामर ने गलती की (जैसे बराबर / हैशकोड को फिर से परिभाषित नहीं करना), या JVM / JAVA (स्थिर के साथ कक्षा का भार ...) के कोने मामले से काटा गया है। मुझे लगता है कि उदाहरण के प्रकार का साक्षात्कारकर्ता नहीं चाहता है या यहां तक ​​कि सबसे आम मामला भी है।

लेकिन मेमोरी लीक के लिए वास्तव में सरल मामले हैं। कचरा कलेक्टर केवल तभी मुक्त होता है जिसे अब संदर्भित नहीं किया जाता है। हम जावा डेवलपर्स के रूप में स्मृति की परवाह नहीं करते हैं। आवश्यकता होने पर हम इसे आवंटित करते हैं और इसे स्वचालित रूप से मुक्त कर देते हैं। ठीक।

लेकिन किसी भी लंबे समय तक रहने वाले आवेदन में राज्य साझा किया जाता है। यह कुछ भी हो सकता है, statics, singletons ... अक्सर गैर-तुच्छ अनुप्रयोग जटिल वस्तुओं ग्राफ बनाने के लिए होते हैं। सिर्फ एक ऑब्जेक्ट को निकालने के लिए भूलना भूल जाते हैं या एक संग्रह से एक वस्तु को हटाने के लिए भूल जाते हैं, स्मृति मेमोरी बनाने के लिए पर्याप्त है।

निस्संदेह सभी तरह के श्रोताओं (जैसे यूआई श्रोताओं), कैश, या किसी भी लंबे समय तक साझा राज्य स्थिति मेमोरी रिसाव का उत्पादन करती है अगर सही तरीके से संभाला नहीं जाता है। क्या समझा जाएगा कि यह जावा कोने का मामला नहीं है, या कचरा कलेक्टर के साथ एक समस्या है। यह एक डिजाइन समस्या है। हम डिज़ाइन करते हैं कि हम एक लंबे समय तक ऑब्जेक्ट में श्रोता जोड़ते हैं, लेकिन जब हम अब आवश्यकता नहीं रखते हैं तो हम श्रोता को नहीं हटाते हैं। हम वस्तुओं को कैश करते हैं, लेकिन हमारे पास कैश से उन्हें हटाने की कोई रणनीति नहीं है।

हमारे पास शायद एक जटिल ग्राफ है जो गणना के द्वारा आवश्यक पिछले राज्य को संग्रहीत करता है। लेकिन पिछला राज्य पहले से ही राज्य से जुड़ा हुआ है।

जैसे हमें SQL कनेक्शन या फ़ाइलों को बंद करना है। हमें शून्य से उचित संदर्भ सेट करने और संग्रह से तत्वों को हटाने की आवश्यकता है। हमारे पास उचित कैशिंग रणनीतियों (अधिकतम मेमोरी आकार, तत्वों की संख्या, या टाइमर) होंगे। सभी ऑब्जेक्ट्स जो श्रोता को अधिसूचित करने की अनुमति देते हैं उन्हें एक ऐडलिस्टर और निकालने के लिए विधि प्रदान करना चाहिए। और जब इन नोटिफायरों का अब उपयोग नहीं किया जाता है, तो उन्हें अपनी श्रोता सूची को साफ़ करना होगा।

एक स्मृति रिसाव वास्तव में वास्तव में संभव है और पूरी तरह से अनुमान लगाया जा सकता है। विशेष भाषा सुविधाओं या कोने के मामलों की कोई ज़रूरत नहीं है। मेमोरी लीक या तो एक संकेतक हैं कि कुछ शायद गायब हो या डिजाइन समस्याओं की भी हो।


जीयूआई कोड में इसका एक आम उदाहरण है जब विजेट / घटक बनाते हैं और कुछ स्थैतिक / एप्लिकेशन स्कोप्ड ऑब्जेक्ट में श्रोता जोड़ते हैं और फिर विजेट नष्ट होने पर श्रोता को हटा नहीं देते हैं। न केवल आपको स्मृति रिसाव मिलती है, बल्कि जब भी आप घटनाओं को आग लगाना सुनते हैं तो प्रदर्शन भी प्रभावित होता है, आपके सभी पुराने श्रोताओं को भी बुलाया जाता है।


मुझे हाल ही में log4j द्वारा एक तरह से एक स्मृति रिसाव स्थिति का सामना करना पड़ा।

Log4j में इस तंत्र को नेस्ड डायग्नोस्टिक कंटेक्स्ट (एनडीसी) कहा जाता है जो विभिन्न स्रोतों से अंतःस्थापित लॉग आउटपुट को अलग करने के लिए एक उपकरण है। ग्रैन्युलरिटी जिस पर एनडीसी काम करता है वह थ्रेड होता है, इसलिए यह अलग-अलग धागे से अलग-अलग लॉग आउटपुट को अलग करता है।

थ्रेड विशिष्ट टैग को स्टोर करने के लिए, log4j की एनडीसी कक्षा एक हैशटेबल का उपयोग करती है जिसे थ्रेड ऑब्जेक्ट द्वारा ही किया जाता है (जैसा कि थ्रेड आईडी कहने के विपरीत है), और इस प्रकार जब तक एनडीसी टैग स्मृति में रहता है तो थ्रेड से लटकने वाली सभी ऑब्जेक्ट्स वस्तु स्मृति में भी रहती है। हमारे वेब एप्लिकेशन में हम एक अनुरोध आईडी के साथ लॉगआउटपुट को टैग करने के लिए एनडीसी का उपयोग अलग-अलग अनुरोध से लॉग को अलग करने के लिए करते हैं। कंटेनर जो एनडीसी टैग को धागे से जोड़ता है, अनुरोध से प्रतिक्रिया वापस करते समय इसे हटा देता है। समस्या तब हुई जब एक अनुरोध संसाधित करने के दौरान, एक बच्चा धागा पैदा हुआ, कुछ कोड की तरह कुछ:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

तो एक एनडीसी संदर्भ इनलाइन थ्रेड से जुड़ा हुआ था जो उत्पन्न हुआ था। थ्रेड ऑब्जेक्ट जो इस एनडीसी संदर्भ के लिए महत्वपूर्ण था, इनलाइन थ्रेड है जिसमें विशाल सूची वस्तुएं लटकती हैं। इसलिए थ्रेड के बाद भी यह कर रहा था कि वह क्या कर रहा था, विशाल सूची का संदर्भ एनडीसी संदर्भ हेस्टेबल द्वारा जिंदा रखा गया था, इस प्रकार मेमोरी रिसाव हो रहा था।


साक्षात्कारकर्ता एक परिपत्र संदर्भ समाधान की तलाश में हो सकता है:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

संदर्भ कचरा कलेक्टरों के संदर्भ में यह एक क्लासिक समस्या है। फिर आप विनम्रता से समझाएंगे कि जेवीएम एक अधिक परिष्कृत एल्गोरिदम का उपयोग करते हैं जिसमें यह सीमा नहीं है।

-वेस तारले


नीचे एक गैर-स्पष्ट मामला होगा जहां जावा लीक, भुलाए गए श्रोताओं के मानक मामले, स्थिर संदर्भ, हॉगहैप्स में बोगस / संशोधित कुंजी के अलावा, या केवल उनके जीवन चक्र को समाप्त करने के किसी भी मौके के बिना फंसे धागे।

  • File.deleteOnExit() - हमेशा स्ट्रिंग को लीक करता है, यदि स्ट्रिंग एक सबस्ट्रिंग है, तो रिसाव भी बदतर है (अंतर्निहित char [] भी लीक किया जाता है) - जावा 7 में सबस्ट्रिंग भी char[] प्रतिलिपि बनाता है, इसलिए बाद में लागू नहीं होता है ; @ डैनियल, वोटों की कोई ज़रूरत नहीं है, हालांकि।

मैं ज्यादातर अप्रबंधित धागे के खतरे को दिखाने के लिए धागे पर ध्यान केंद्रित करूंगा, स्विंग को छूना भी नहीं चाहता हूं।

  • Runtime.addShutdownHook और हटाएं ... और उसके बाद भी हटाए गए ShutdownHook के साथ थ्रेड ग्रुप क्लास में अनस्टार्ट किए गए थ्रेड के बारे में एक बग के कारण यह एकत्र नहीं हो सकता है, प्रभावी रूप से थ्रेड ग्रुप को रिसाव कर सकता है। जी ग्रुप गॉसिप राउटर में रिसाव है।

  • बनाना, लेकिन शुरू नहीं करना, एक Thread उपरोक्त के समान श्रेणी में जाता है।

  • थ्रेड बनाना ContextClassLoader और AccessControlContext , प्लस ThreadGroup और किसी भी InheritedThreadLocal ThreadGroup को ThreadGroup , ये सभी संदर्भ ThreadGroup द्वारा लोड किए गए पूरे वर्गों और सभी स्थिर संदर्भों और जावा-जे के साथ संभावित लीक होते हैं। प्रभाव विशेष रूप से पूरे jucExecutor ढांचे के साथ दिखाई देता है जिसमें एक सुपर सरल ThreadFactory इंटरफ़ेस है, फिर भी अधिकांश डेवलपर्स के पास खतरनाक खतरे का कोई संकेत नहीं है। इसके अलावा कई पुस्तकालय अनुरोध पर थ्रेड शुरू करते हैं (जिस तरह से कई उद्योग लोकप्रिय पुस्तकालय)।

  • ThreadLocal कैश; वे कई मामलों में बुराई हैं। मुझे यकीन है कि थ्रेडलोकल के आधार पर हर किसी ने सरल कैशों को देखा है, अच्छी तरह से बुरी खबर: अगर धागा संदर्भ क्लासलोडर के जीवन की अपेक्षा से अधिक जा रहा है, तो यह एक शुद्ध अच्छी छोटी रिसाव है। वास्तव में जरूरत होने तक थ्रेडलोकल कैश का उपयोग न करें।

  • ThreadGroup.destroy() कॉल ThreadGroup.destroy() जब थ्रेड ग्रुप में कोई थ्रेड नहीं होता है, लेकिन यह अभी भी बच्चे थ्रेड ग्रुप को रखता है। एक खराब रिसाव जो थ्रेड ग्रुप को अपने माता-पिता से हटाने के लिए रोक देगा, लेकिन सभी बच्चे अनगिनत हो जाते हैं।

  • WeakHashMap का उपयोग करना और मान (इन) सीधे कुंजी का संदर्भ देता है। एक ढेर डंप के बिना यह मुश्किल है। यह सभी विस्तारित Weak/SoftReference पर लागू होता है जो संरक्षित ऑब्जेक्ट पर एक कठिन संदर्भ रख सकता है।

  • HTTP (एस) प्रोटोकॉल के साथ java.net.URL का उपयोग करना और संसाधन को (!) से लोड करना। यह एक विशेष है, KeepAliveCache सिस्टम थ्रेड ग्रुप में एक नया धागा बनाता है जो वर्तमान थ्रेड के संदर्भ क्लासलोडर को रिसाव करता है। धागा पहले अनुरोध पर बनाया गया है जब कोई जीवित धागा मौजूद नहीं है, तो या तो आप भाग्यशाली हो सकते हैं या बस रिसाव कर सकते हैं। रिसाव जावा 7 में पहले से तय है और थ्रेड बनाता है जो कोड सही ढंग से संदर्भ क्लासलोडर को हटा देता है। कुछ और मामले हैं ( ImageFetcher की तरह , भी तय ) समान धागे बनाने के।

  • InflaterInputStream का उपयोग InflaterInputStream new java.util.zip.Inflater() के लिए (उदाहरण के लिए PNGImageDecoder ) और inflater के end() को कॉल नहीं करना। खैर, अगर आप केवल new साथ कन्स्ट्रक्टर में पास करते हैं, तो कोई मौका नहीं है ... और हाँ, स्ट्रीम पर close() को कॉल करना अगर inflator पैरामीटर के रूप में मैन्युअल रूप से पास हो जाता है तो inflater को बंद नहीं करता है। यह एक वास्तविक रिसाव नहीं है क्योंकि इसे अंतिमकर्ता द्वारा जारी किया जाएगा ... जब इसे आवश्यक समझा जाता है। उस क्षण तक यह मूल स्मृति को इतनी बुरी तरह खाता है कि यह लिनक्स oom_killer को दंड के साथ प्रक्रिया को मारने का कारण बन सकता है। मुख्य मुद्दा यह है कि जावा में अंतिम रूप बहुत अविश्वसनीय है और जी 1 ने इसे 7.0.2 तक खराब कर दिया है। कहानी का नैतिक: जैसे ही आप कर सकते हैं मूल संसाधनों को छोड़ दें; फाइनलाइज़र बहुत खराब है।

  • java.util.zip.Deflater साथ एक ही मामला। यह एक बहुत खराब है क्योंकि Deflater जावा में भूख लगी है, यानी हमेशा 15 बिट्स (अधिकतम) और 8 मेमोरी लेवल का उपयोग करता है (9 अधिकतम है) कई सैकड़ों देशी मूल स्मृति आवंटित करता है। सौभाग्य से, Deflater व्यापक रूप से उपयोग नहीं किया जाता है और मेरे ज्ञान के लिए जेडीके में कोई दुरुपयोग नहीं है। यदि आप मैन्युअल रूप से Inflater या Inflater बनाते हैं तो हमेशा end() कॉल करें। पिछले दो का सबसे अच्छा हिस्सा: आप उन्हें उपलब्ध सामान्य प्रोफाइलिंग टूल के माध्यम से नहीं ढूंढ सकते हैं।

(मैं अनुरोध पर सामना करने के कुछ और समय बर्बाद कर सकते हैं।)

खुशकिस्मत रहें और सुरक्षित रहें; लीक बुराई हैं!


http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 माध्यम से यहां एक सरल / भयावह है ।

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

चूंकि सबस्ट्रिंग मूल, आंतरिक स्ट्रिंग के आंतरिक प्रतिनिधित्व को संदर्भित करता है, मूल स्मृति में रहता है। इस प्रकार, जब तक आपके पास स्ट्रिंग लीकर खेलना है, तब तक आपके पास स्मृति में पूरी मूल स्ट्रिंग भी है, भले ही आपको लगता है कि आप केवल एक-वर्ण स्ट्रिंग पर हैं।

मूल स्ट्रिंग के अवांछित संदर्भ को संग्रहीत करने से बचने का तरीका ऐसा कुछ करना है:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

अतिरिक्त खराबता के लिए, आप .intern()सबस्ट्रिंग भी कर सकते हैं :

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

ऐसा करने से स्ट्रिंगलीकर उदाहरण को छोड़ दिए जाने के बाद भी मूल लम्बी स्ट्रिंग और स्मृति में व्युत्पन्न सबस्ट्रिंग दोनों को बनाए रखा जाएगा।


शायद संभावित स्मृति रिसाव के सबसे सरल उदाहरणों में से एक, और इससे कैसे बचें, ArrayList.remove (int) का कार्यान्वयन है:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

यदि आप इसे स्वयं लागू कर रहे थे, तो क्या आपने उस सरणी तत्व को साफ़ करने के लिए सोचा होगा जो अब उपयोग नहीं किया गया है ( elementData[--size] = null )? उस संदर्भ में एक विशाल वस्तु जीवित रह सकती है ...


मेमोरी रिसाव क्या है:

  • यह एक बग या खराब डिजाइन के कारण होता है
  • यह स्मृति की बर्बादी है।
  • यह समय के साथ बदतर हो जाता है।
  • कचरा कलेक्टर इसे साफ नहीं कर सकता है।

विशिष्ट उदाहरण:

वस्तुओं का एक कैश गड़बड़ चीजों के लिए एक अच्छा प्रारंभिक बिंदु है।

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

आपका कैश बढ़ता है और बढ़ता है। और बहुत जल्द पूरे डेटाबेस को स्मृति में चूसा जाता है। एक बेहतर डिजाइन एक LRUMap का उपयोग करता है (केवल कैश में हाल ही में प्रयुक्त वस्तुओं को रखता है)।

निश्चित रूप से, आप चीजों को और अधिक जटिल बना सकते हैं:

  • थ्रेडलोकल निर्माण का उपयोग कर ।
  • अधिक जटिल संदर्भ पेड़ जोड़ना ।
  • या तृतीय पक्ष पुस्तकालयों के कारण लीक ।

अक्सर क्या होता है:

यदि इस जानकारी ऑब्जेक्ट में अन्य ऑब्जेक्ट्स के संदर्भ हैं, जो कि फिर से अन्य ऑब्जेक्ट्स के संदर्भ हैं। एक तरह से आप इसे किसी प्रकार की मेमोरी लीक मान सकते हैं, (खराब डिजाइन के कारण)।


शायद जेएनआई के माध्यम से बाहरी देशी कोड का उपयोग कर?

शुद्ध जावा के साथ, यह लगभग असंभव है।

लेकिन यह एक "मानक" प्रकार की स्मृति रिसाव के बारे में है, जब आप अब स्मृति तक नहीं पहुंच सकते हैं, लेकिन यह अभी भी एप्लिकेशन के स्वामित्व में है। इसके बजाय आप अप्रयुक्त वस्तुओं के संदर्भ, या बाद में बंद किए बिना खुली धाराओं को संदर्भित कर सकते हैं।


हर कोई हमेशा देशी कोड मार्ग भूल जाता है। रिसाव के लिए यहां एक सरल सूत्र है:

  1. देशी विधि घोषित करें।
  2. मूल विधि में, कॉल करें malloc। फोन न करें free
  3. मूल विधि को बुलाओ।

याद रखें, देशी कोड में स्मृति आवंटन JVM ढेर से आते हैं।


जब तक वे समाप्त नहीं होते हैं तब तक थ्रेड एकत्र नहीं किए जाते हैं। वे कचरा संग्रह की roots के रूप में काम करते हैं । वे उन कुछ वस्तुओं में से एक हैं जिन्हें उनके बारे में भूलकर या संदर्भों को समाशोधन करके पुनः प्राप्त नहीं किया जाएगा।

विचार करें: एक कार्यकर्ता थ्रेड को समाप्त करने के लिए मूल पैटर्न थ्रेड द्वारा देखी गई कुछ स्थिति चर सेट करना है। धागा आवधिक रूप से चर की जांच कर सकता है और इसे समाप्त करने के संकेत के रूप में उपयोग कर सकता है। यदि चर घोषित नहीं किया गया है volatile, तो चर में परिवर्तन थ्रेड द्वारा नहीं देखा जा सकता है, इसलिए इसे समाप्त नहीं किया जाएगा। या कल्पना करें कि कुछ धागे किसी साझा ऑब्जेक्ट को अपडेट करना चाहते हैं, लेकिन इसे लॉक करने का प्रयास करते समय डेडलॉक।

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

एक खिलौना उदाहरण के रूप में:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

System.gc()आप जो भी पसंद करते हैं उसे कॉल करें , लेकिन पारित वस्तु leakMeकभी मर जाएगी नहीं।

(* संपादित *)


एक स्थिर मानचित्र बनाएं और इसमें कठिन संदर्भ जोड़ना जारी रखें। वे कभी भी जीसीएड नहीं होंगे।

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}

मुझे लगता है कि एक वैध उदाहरण एक ऐसे वातावरण में थ्रेडलोकल चर का उपयोग कर सकता है जहां धागे को पूल किया जाता है।

उदाहरण के लिए, Servlets में ThreadLocal चर का उपयोग अन्य वेब घटकों के साथ संवाद करने के लिए, कंटेनर द्वारा बनाए गए धागे और पूल में निष्क्रिय लोगों को बनाए रखने के साथ। थ्रेडलोकल वैरिएबल, अगर सही तरीके से साफ नहीं किया जाता है, तब तक वहां रहेंगे, संभवतः, वही वेब घटक उनके मूल्यों को ओवरराइट करता है।

बेशक, एक बार पहचान की, समस्या आसानी से हल किया जा सकता है।





memory-leaks