[java] خلق تسرب الذاكرة مع جافا



14 Answers

مرجع كائن احتجاز ثابت في حقل [esp field field]

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();
}

المناطق التي يتعذر الوصول إليها من جامع البيانات المهملة في JVM ، مثل الذاكرة المخصصة من خلال الأساليب الأصلية

في تطبيقات الويب ، يتم تخزين بعض الكائنات في نطاق التطبيق حتى يتم إيقاف التطبيق أو إزالته بشكل صريح.

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

خيارات JVM غير صحيحة أو غير مناسبة ، مثل خيار noclassgc على IBM JDK الذي يمنع مجموعة البيانات غير المستخدمة noclassgc

انظر إعدادات IBM jdk .

Question

لقد أجريت للتو مقابلة ، وطُلب مني إنشاء تسرب للذاكرة باستخدام Java. وغني عن القول أنني شعرت بالغباء وعدم وجود أي فكرة حول كيفية البدء في إنشاء واحدة.

ماذا سيكون المثال؟




Here's a simple/sinister one via 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);
    }
}

Because the substring refers to the internal representation of the original, much longer string, the original stays in memory. Thus, as long as you have a StringLeaker in play, you have the whole original string in memory, too, even though you might think you're just holding on to a single-character string.

The way to avoid storing an unwanted reference to the original string is to do something like this:

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

For added badness, you might also .intern() the substring:

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

Doing so will keep both the original long string and the derived substring in memory even after the StringLeaker instance has been discarded.




I don't think anyone has said this yet: you can resurrect an object by overriding the finalize() method such that finalize() stores a reference of this somewhere. The garbage collector will only be called once on the object so after that the object will never destroyed.




I thought it was interesting that no one used the internal class examples. If you have an internal class; it inherently maintains a reference to the containing class. Of course it is not technically a memory leak because Java WILL eventually clean it up; but this can cause classes to hang around longer than anticipated.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Now if you call Example1 and get an Example2 discarding Example1, you will inherently still have a link to an Example1 object.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

I've also heard a rumor that if you have a variable that exists for longer than a specific amount of time; Java assumes that it will always exist and will actually never try to clean it up if cannot be reached in code anymore. But that is completely unverified.




I have had a nice "memory leak" in relation to PermGen and XML parsing once. The XML parser we used (I can't remember which one it was) did a String.intern() on tag names, to make comparison faster. One of our customers had the great idea to store data values not in XML attributes or text, but as tagnames, so we had a document like:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

In fact, they did not use numbers but longer textual IDs (around 20 characters), which were unique and came in at a rate of 10-15 million a day. That makes 200 MB of rubbish a day, which is never needed again, and never GCed (since it is in PermGen). We had permgen set to 512 MB, so it took around two days for the out-of-memory exception (OOME) to arrive...




ربما أحد أبسط الأمثلة على تسرب للذاكرة المحتملة ، وكيفية تجنبها ، هو تنفيذ 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 في مسح عنصر الصفيف الذي لم يعد مستخدمًا ( elementData[--size] = null )؟ قد تحافظ هذه الإشارة على كائن ضخم على قيد الحياة ...




As a lot of people have suggested, Resource Leaks are fairly easy to cause - like the JDBC examples. Actual Memory leaks are a bit harder - especially if you aren't relying on broken bits of the JVM to do it for you...

The ideas of creating objects that have a very large footprint and then not being able to access them aren't real memory leaks either. If nothing can access it then it will be garbage collected, and if something can access it then it's not a leak...

One way that used to work though - and I don't know if it still does - is to have a three-deep circular chain. As in Object A has a reference to Object B, Object B has a reference to Object C and Object C has a reference to Object A. The GC was clever enough to know that a two deep chain - as in A <--> B - can safely be collected if A and B aren't accessible by anything else, but couldn't handle the three-way chain...




The interviewer was probably looking for a circular reference like the code below (which incidentally only leak memory in very old JVMs that used reference counting, which isn't the case any more). But it's a pretty vague question, so it's a prime opportunity to show off your understanding of JVM memory management.

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 */
}

Then you can explain that with reference counting, the above code would leak memory. But most modern JVMs don't use reference counting any longer, most use a sweep garbage collector, which will in fact collect this memory.

Next you might explain creating an Object that has an underlying native resource, like this:

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. */
    }
}

Then you can explain this is technically a memory leak, but really the leak is caused by native code in the JVM allocating underlying native resources, which weren't freed by your Java code.

At the end of the day, with a modern JVM, you need to write some Java code that allocates a native resource outside the normal scope of the JVM's awareness.




تعتمد الإجابة كليًا على ما اعتقده القائم بالمقابلة أنهم يسألون.

هل من الممكن في الواقع لجعل جافا التسرب؟ بالطبع ، وهناك الكثير من الأمثلة في الإجابات الأخرى.

ولكن هناك العديد من الأسئلة الفوقية التي ربما تم طرحها؟

  • هل تطبيق جافا "مثالي" نظريا عرضة للتسرب؟
  • هل يفهم المرشح الفرق بين النظرية والواقع؟
  • هل يفهم المرشح كيفية عمل جمع القمامة؟
  • أو كيف من المفترض أن تعمل مجموعة القمامة في حالة مثالية؟
  • هل يعلمون أنه يمكنهم الاتصال بلغات أخرى من خلال واجهات محلية؟
  • هل يعرفون تسرب الذاكرة في تلك اللغات الأخرى؟
  • هل يعرف المرشح ما هي إدارة الذاكرة وما الذي يجري وراء المشهد في جافا؟

إنني أقرأ السؤال الفريد الخاص بك على أنه "ما هي إجابة كان من الممكن أن استخدمها في هذا الموقف من المقابلة". وبالتالي ، سأركز على مهارات المقابلة بدلاً من جافا. أعتقد أن من المرجح أن تكرر موقف عدم معرفة الإجابة عن سؤال في مقابلة مما أنت عليه في مكان تحتاج فيه إلى معرفة كيفية جعل تسريب جافا. لذا ، نأمل أن يساعد ذلك.

واحدة من أهم المهارات التي يمكنك تطويرها لإجراء المقابلات هي تعلم الاستماع الفعال إلى الأسئلة والعمل مع الشخص الذي يجري المقابلة لاستخراج نواياهم. لا يتيح لك هذا فقط الإجابة عن سؤالهم بالطريقة التي يريدونها ، بل يوضح أيضًا أن لديك بعض مهارات الاتصال الحيوية. وعندما يتعلق الأمر باختيار بين العديد من المطورين الموهوبين بنفس القدر ، سوف أقوم بتوظيف الشخص الذي يستمع ويفكر ويفهم قبل أن يستجيب في كل مرة.




Create a static Map and keep adding hard references to it. Those will never be GC'd.

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); }
}



A common example of this in GUI code is when creating a widget/component and adding a listener to some static/application scoped object and then not removing the listener when the widget is destroyed. Not only do you get a memory leak, but also a performance hit as when whatever you are listening to fires events, all your old listeners are called too.




يمكنك إجراء تسرب للذاكرة مع sun.misc.Unsafe class. في الواقع ، يتم استخدام فئة الخدمة هذه في فئات قياسية مختلفة (على سبيل المثال في فئات java.nio ). لا يمكنك إنشاء مثيل لهذا الفصل مباشرةً ، لكن يمكنك استخدام التفكير للقيام بذلك .

لا يتم ترجمة التعليمات البرمجية في Eclipse IDE - قم بتجميعها باستخدام javac command (أثناء التحويل البرمجي ستحصل على تحذيرات)

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();
        }
    }

}



The interviewer might have be looking for a circular reference solution:

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

This is a classic problem with reference counting garbage collectors. You would then politely explain that JVMs use a much more sophisticated algorithm that doesn't have this limitation.

-Wes Tarle




Threads are not collected until they terminate. They serve as roots of garbage collection. They are one of the few objects that won't be reclaimed simply by forgetting about them or clearing references to them.

Consider: the basic pattern to terminate a worker thread is to set some condition variable seen by the thread. The thread can check the variable periodically and use that as a signal to terminate. If the variable is not declared volatile , then the change to the variable might not be seen by the thread, so it won't know to terminate. Or imagine if some threads want to update a shared object, but deadlock while trying to lock on it.

If you only have a handful of threads these bugs will probably be obvious because your program will stop working properly. If you have a thread pool that creates more threads as needed, then the obsolete/stuck threads might not be noticed, and will accumulate indefinitely, causing a memory leak. Threads are likely to use other data in your application, so will also prevent anything they directly reference from ever being collected.

As a toy example:

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

Call System.gc() all you like, but the object passed to leakMe will never die.

(*edited*)




أدناه سيكون هناك حالة غير واضحة حيث تسرب جافا ، إلى جانب الحالة القياسية للمستمعين المنسيين ، ومراجع ثابتة ، ومفاتيح زائفة / قابلة للتعديل في هاشمابس ، أو مجرد خيوط عالقة دون أي فرصة لإنهاء دورة حياتها.

  • File.deleteOnExit() - تسرّب دائمًا السلسلة ، إذا كانت السلسلة عبارة عن سلسلة فرعية ، فإن التسرب أسوأ من ذلك (يتم أيضًا تسريب char الأساسي []) - في السلسلة 7 جافا أيضا نسخ char[] ، لذلك لا ينطبق في وقت لاحق ؛ @ دانيال ، لا يحتاج إلى أصوات ، على الرغم من.

سأركز على الخيوط لإظهار خطر الخيوط غير المدارة في الغالب ، لا ترغب في حتى اللمس التأرجح.

  • Runtime.addShutdownHook وإزالة ... ثم حتى مع removeShutdownHook بسبب وجود خطأ في فئة ThreadGroup فيما يتعلق بموضوعات غير مبتدئة قد لا يتم تجميعها ، تسرب ThreadGroup بشكل فعال. JGroup لديه تسرب في GossipRouter.

  • إنشاء ، ولكن لا تبدأ ، يدخل Thread في نفس الفئة على النحو الوارد أعلاه.

  • إنشاء مؤشر ترابط ThreadGroup ContextClassLoader و AccessControlContext ، بالإضافة إلى ThreadGroup و أي InheritedThreadLocal ، كل هذه المراجع هي التسريبات المحتملة ، جنبا إلى جنب مع فئات كاملة تحميلها من كلدروفيرر وجميع المراجع الثابتة ، و ja-ja. يكون التأثير مرئيًا بشكل خاص من خلال إطار عمل jucExecutor بأكمله الذي يتميز بواجهة ThreadFactory بسيطة ThreadFactory ، ومع ذلك فإن معظم المطورين ليس لديهم أدنى فكرة عن الخطر ThreadFactory . أيضا الكثير من المكتبات تبدأ مؤشرات الترابط عند الطلب (طريقة الكثير من المكتبات الشعبية في الصناعة).

  • مخابئ ThreadLocal ؛ تلك شر في كثير من الحالات. أنا متأكد من أن الجميع قد رأوا قدرا كبيرا من المخابئ البسيطة القائمة على ThreadLocal ، وأيضا الأخبار السيئة: إذا استمر الخيط في الذهاب أكثر من المتوقع للحياة سياق ClassLoader ، فإنه تسرب بسيط جميل نقي. لا تستخدم ذاكرة التخزين المؤقت ThreadLocal ما لم تكن هناك حاجة حقا.

  • استدعاء ThreadGroup.destroy() عندما لا يحتوي ThreadGroup على مؤشرات الترابط نفسها ، ولكنه لا يزال يحتفظ ThreadGroups التابع. تسرب غير صالح يمنع ThreadGroup لإزالة من الأصل الخاص به ولكن كافة الأطفال تصبح un-enumerateable.

  • باستخدام WeakHashMap والقيمة (في) مراجع مباشرة المفتاح. هذا هو أحد الصعب العثور على دون تفريغ كومة الذاكرة المؤقتة. وينطبق ذلك على جميع Weak/SoftReference التي قد تحافظ على الرجوع الثابت إلى الكائن الذي Weak/SoftReference للحراسة.

  • باستخدام java.net.URL مع بروتوكول HTTP (S) وتحميل المورد من (!). هذا واحد هو خاص ، و KeepAliveCache إنشاء مؤشر ترابط جديد في ThreadGroup النظام الذي يتسبب في تدفق مصنف سلسلة المحادثات الحالية. يتم إنشاء مؤشر الترابط عند الطلب الأول عندما لا يوجد مؤشر ترابط قيد الحياة ، لذلك إما قد تحصل على الحظ أو تسرب فقط. التسرب تم إصلاحه بالفعل في Java 7 والرمز الذي يقوم بإنشاء مؤشر الترابط بشكل صحيح يزيل classloader السياق. هناك عدد قليل من الحالات ( مثل ImageFetcher ، ثابتة أيضا ) من خلق المواضيع المماثلة.

  • باستخدام InflaterInputStream تمرير new java.util.zip.Inflater() في المنشئ ( PNGImageDecoder على سبيل المثال) وعدم استدعاء end() من inflater. حسنا ، إذا كنت تمر في منشئ مع new فقط ، أي فرصة ... ونعم ، لا close() على تيار لا تغلق inflater إذا تم تمريره يدويا كمعلمة منشئ. هذا ليس تسربًا حقيقيًا لأنه سيتم تحريره من قبل finalizer ... عندما يراه ضروريًا. وحتى ذلك الحين ، فإنه يأكل الذاكرة المحلية بشكل سيء للغاية ويمكن أن يسبب لينكس oom_killer لقتل العملية مع الإفلات من العقاب. وتتمثل المشكلة الرئيسية في أن وضع اللمسات الأخيرة في جافا أمر لا يمكن الاعتماد عليه ، وجعل G1 أسوأ حتى 7.0.2. أخلاق القصة: إطلاق الموارد المحلية في أقرب وقت ممكن ؛ المتأهل هو فقير جدا.

  • نفس الحالة مع java.util.zip.Deflater . هذا هو أسوأ بكثير منذ Deflater هو الذاكرة الجياع في جاوة ، أي يستخدم دائما 15 بت (ماكس) و 8 مستويات الذاكرة (9 هو الحد الأقصى) تخصيص عدة مئات من KB من الذاكرة الأصلية. لحسن الحظ ، لا يستخدم Deflater على نطاق واسع وعلى حد علمي لا يحتوي JDK على أي سوء استخدام. دائما استدعاء end() إذا كنت يدويا Deflater أو Inflater . أفضل جزء من الأخيرين: لا يمكنك العثور عليها عبر أدوات التنميط العادية المتاحة.

(يمكنني إضافة المزيد من الوقت للوقت للراحة التي واجهتها عند الطلب.)

حظا موفقا ابقى في امان؛ التسريبات هي الشر!




Related