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





15 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 .

java memory memory-leaks

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

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




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

  • 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 . أفضل جزء من الأخيرين: لا يمكنك العثور عليها عبر أدوات التنميط العادية المتاحة.

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

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




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

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

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

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

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

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




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




يمكنك إجراء تسرب للذاكرة مع 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();
        }
    }

}



إليك رسالة بسيطة / شريرة عبر 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);
    }
}

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

طريقة تجنب تخزين مرجع غير مرغوب فيه للسلسلة الأصلية هي القيام بشيء كالتالي:

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

لمزيد من السوء ، يمكنك أيضًا أن .intern()تكون سلسلة فرعية:

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

سيؤدي القيام بذلك إلى الاحتفاظ بالسلسلة الطويلة الأصلية والسلسلة الفرعية المشتقة في الذاكرة حتى بعد التخلص من مثيل StringLeaker.




خذ أي تطبيق ويب يعمل في أي حاوية servlet (Tomcat ، Jetty ، Glassfish ، أيا كان ...). أعد توزيع التطبيق 10 أو 20 مرة على التوالي (قد يكفي أن تلمس WAR ببساطة في دليل autodeploy للخادم.

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

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

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

المواضيع التي بدأها التطبيق الخاص بك ، المتغيرات ThreadLocal ، مناضلون تسجيل بعض المشتبه بهم المعتادة للتسبب في تسرب classloader.




لقد حصلت على "تسرب للذاكرة" لطيف بالنسبة إلى تحليل PermGen و XML مرة واحدة. محلل XML الذي استخدمناه (لم أكن أتذكر أي واحد كان) قام به String.intern () على أسماء العلامات ، لإجراء المقارنة بشكل أسرع. كان لدى أحد عملائنا فكرة رائعة لتخزين قيم البيانات لا في سمات XML أو نصها ، ولكن كأسماء علامات ، لذلك كان لدينا وثيقة مثل:

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

في الواقع ، لم يستخدموا أرقامًا ، بل أرقامًا تعريفية أطول (حوالي 20 حرفًا) ، والتي كانت فريدة من نوعها ، وجاءت بمعدل 10-15 مليونًا في اليوم. وهذا يجعل 200 ميغابايت من القمامة في اليوم ، والتي لا حاجة إليها مرة أخرى ، ولم يسبق لـ GCed (حيث توجد في PermGen). كان لدينا permgen لتعيين 512 ميغابايت ، لذلك استغرق الأمر حوالي يومين للاستثناء خارج الذاكرة (OOME) للوصول ...




اعتقدت أنه من المثير للاهتمام أن لا أحد استخدم أمثلة الطبقة الداخلية. إذا كان لديك صف داخلي يحتفظ بطبيعته بمرجع إلى الطبقة المحتوية. بالطبع ليس تقنيا ذاكرة من أجل جافا في نهاية المطاف تنظيفه ؛ ولكن هذا يمكن أن يتسبب في تعليق الصفوف لفترة أطول من المتوقع.

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

الآن في حالة استدعاء Example1 والحصول على Example2 تجاهل Example1 ، ستظل تحتوي على ارتباط إلى كائن Example1.

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

لقد سمعت أيضًا إشاعة مفادها أنه إذا كان لديك متغير موجود لفترة أطول من الوقت المحدد ؛ تفترض Java أنها ستظل موجودة دائمًا ولن تحاول أبدًا تنظيفها إذا لم يمكن الوصول إليها بعد الآن. لكن هذا لم يتم التحقق منه تمامًا.




إنشاء خريطة ثابتة ومواصلة إضافة مراجع صلبة إليها. هذه لن تكون أبدا 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); }
}



يمكنك إنشاء تسرب للذاكرة المتحركة عن طريق إنشاء مثيل جديد لفئة في طريقة finalize للفئة. نقاط المكافأة إذا قام صانع الصيغة بإنشاء نسخ متعددة. إليك برنامج بسيط يسرب كومة الذاكرة المؤقتة بالكامل في وقت ما بين بضع ثوانٍ وبضع دقائق حسب حجم كومة الذاكرة المؤقتة:

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



جئت عبر نوع من تسرب أكثر دقة من الموارد في الآونة الأخيرة. نحن نفتح الموارد عبر getResourceAsStream الخاص بصنف الطبقة ، وقد حدث أن مقابض تدفق المدخلات لم تكن مغلقة.

أوم ، قد تقول ، ما أحمق.

حسنا ، ما يجعل هذا مثير للاهتمام هو: بهذه الطريقة ، يمكنك تسريب ذاكرة الكومة العملية الأساسية ، بدلا من كومة JVM.

كل ما تحتاجه هو ملف jar مع ملف بداخل والذي سيتم الإشارة إليه من Java البرمجية. كلما كبر ملف الجرة ، يتم تخصيص الذاكرة الأسرع.

يمكنك إنشاء هذه الجرة بسهولة باستخدام الصف التالي:

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

Et voilà: تجد أرشيف jar في دليل عملك الحالي مع ملفين بالداخل.

دعونا نخلق فئة ثانية:

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

}

لا يقوم هذا الفصل بأي شيء بشكل أساسي ، ولكنه ينشئ كائنات InputStream غير المتداولة. هذه الأشياء سيتم جمع القمامة على الفور ، وبالتالي ، لا تسهم في حجم الكومة. من المهم أن يقوم مثالنا بتحميل مورد موجود من ملف jar ، ولا يهم الحجم هنا!

إذا كنت مشكوكًا في ذلك ، فحاول تجميع الصفوف وبدءها أعلاه ، ولكن تأكد من اختيار حجم مناسب للكرة (2 ميغابايت):

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

لن تواجه خطأ في OOM هنا ، حيث لا يتم الاحتفاظ بالمراجع ، سيظل التطبيق قيد التشغيل بغض النظر عن حجمه الذي اخترته ITERATIONS في المثال أعلاه. ينمو استهلاك الذاكرة من العملية الخاصة بك (مرئية في الأعلى (RES / RSS) أو مستكشف العمليات) ما لم يحصل التطبيق على أمر الانتظار. في الإعداد أعلاه ، سيخصص حوالي 150 ميغابايت في الذاكرة.

إذا كنت تريد أن يكون التطبيق آمنًا ، فأغلق تدفق الإدخال مباشرةً حيث تم إنشاؤه:

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

ولن تتجاوز العملية 35 ميغابايت ، بغض النظر عن عدد التكرارات.

بسيط جدا ومدهش.




كما اقترح الكثير من الناس ، من السهل جدًا تسريب الموارد إلى الموارد - مثل أمثلة JDBC. التسريبات الفعلية للذاكرة تكون أصعب قليلاً - خاصة إذا كنت لا تعتمد على أجزاء متقطعة من JVM للقيام بذلك نيابة عنك ...

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

إحدى الطرق التي استخدمت للعمل - ولا أعرف ما إذا كان لا يزال - تعمل على إنشاء سلسلة دائرية ثلاثية. كما في Object A يحتوي على مرجع إلى كائن B ، يحتوي الكائن B على مرجع إلى الكائن C و C الكائن يحتوي على مرجع إلى كائن A. كان GC ذكيًا بما يكفي لمعرفة سلسلتين عميقتين - كما في A <-> B - يمكن جمعها بأمان إذا كان A و B غير قابل للوصول من قبل أي شيء آخر ، ولكن لا يمكن التعامل مع السلسلة الثلاثية ...




لا يتم جمع المواضيع حتى يتم إنهاؤها. هم بمثابة roots جمع القمامة. فهي واحدة من الأشياء القليلة التي لن يتم استعادتها ببساطة عن طريق نسيانها أو مسح الإشارات إليها.

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

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

كمثال لعبة:

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 static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

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

في تارلي




Related