2017-01-10 11 views
7

मैं एक स्मृति रिसाव शिकार कर रहा हूँ, और हीप डंप मुझे लैम्ब्डा उदाहरणों में से एक नंबर से पता चलता हमलावर वस्तुओं पकड़े हुए हैं ढूँढना। लैम्ब्डा का नाम आसपास के वर्ग का नाम $$lambda$107 अंत में है। मैं भी देख सकते हैं यह एक एकल क्षेत्र है (यह उसके लिए सही नाम है), arg$1 कहा जाता है जो वस्तुओं ढेर भरने संदर्भ देता है। दुर्भाग्यवश, मेरे पास इस कक्षा में काफी कुछ भेड़-बकरियां हैं, और मुझे आश्चर्य है कि मैं इसे कम करने के लिए क्या कर सकता हूं।एक ढेर में अपनी घायल नाम से एक जावा लैम्ब्डा डंप

मुझे लगता है कि arg$1 एक निहित तर्क है - लैम्ब्डा अभिव्यक्ति में एक मुक्त चर जो लैम्ब्डा बंद होने पर कब्जा कर लिया जाता है। क्या वो सही है?

मैं भी 107 अलगाव में कोई वास्तविक मदद वहाँ कुछ झंडे अनुमान लगा रहा हूँ है, लेकिन कर रहे हैं मैं जो लैम्ब्डा अभिव्यक्ति क्या नंबर प्राप्त करता है लॉग ऑन करने के लिए सेट कर सकते हैं?

किसी भी अन्य उपयोगी सुझाव?

उत्तर

5

ओपी का अनुमान है कि arg$1 एक पर कब्जा कर लिया मूल्य युक्त एक लैम्ब्डा वस्तु का एक क्षेत्र है सही है। lukeg का उत्तर लैम्ब्डा मेटाफैक्टरी को अपने प्रॉक्सी वर्गों को डंप करने के लिए सही रास्ते पर है। (+1)

यहाँ एक दृष्टिकोण संदर्भ स्रोत कोड के लिए रोके हुए उदाहरण ट्रैक करने के लिए javap उपकरण का उपयोग करता है है। असल में आपको सही प्रॉक्सी कक्षा मिलती है; यह पता लगाने के लिए कि यह कौन सी सिंथेटिक लैम्ब्डा विधि कॉल करता है उसे अलग करें; फिर उस सिंथेटिक लैम्ब्डा विधि को स्रोत कोड में एक विशेष लैम्ब्डा अभिव्यक्ति के साथ संबद्ध करें।

(अधिकांश, यदि यह जानकारी नहीं है, तो ओरेकल जेडीके और ओपनजेडीके पर लागू होती है। यह विभिन्न जेडीके कार्यान्वयन के लिए काम नहीं कर सकती है। इसके अलावा, यह भविष्य में बदलाव के अधीन है। इसे किसी भी हालिया ओरेकल जेडीके 8 के साथ काम करना चाहिए या ओपनजेडीके 8 हालांकि, यह शायद जेडीके 9 में काम करना जारी रखेगा।)

सबसे पहले, पृष्ठभूमि का एक बिट।जब लैम्बडास युक्त एक स्रोत फ़ाइल संकलित की जाती है, javac लैम्ब्डा निकायों को सिंथेटिक विधियों में संकलित करेगा जो कि कक्षा में रहते हैं। ये विधियां निजी और स्थैतिक हैं, और उनके नाम lambda$<method>$<count> जैसे विधि लैम्बडा युक्त विधि का नाम है, और गिनती एक अनुक्रमिक काउंटर है जो स्रोत फ़ाइल की शुरुआत से संख्या विधियां शुरू कर रहा है शून्य से)।

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

अपनी प्रॉक्सी वर्गों डंप करने के लिए लैम्ब्डा metafactory हो रही करके, आप javap उपयोग कर सकते हैं bytecodes डिसअसेंबल करने और एक प्रॉक्सी उदाहरण वापस लैम्ब्डा अभिव्यक्ति है जिसके लिए वह जेनरेट हुआ था करने के लिए पता लगाने के लिए। यह शायद एक उदाहरण से सबसे अच्छा सचित्र है। निम्नलिखित कोड पर विचार करें:

public class CaptureTest { 
    static List<IntSupplier> list; 

    static IntSupplier foo(boolean b, Object o) { 
     if (b) { 
      return() -> 0;      // line 20 
     } else { 
      int h = o.hashCode(); 
      return() -> h;      // line 23 
     } 
    } 

    static IntSupplier bar(boolean b, Object o) { 
     if (b) { 
      return() -> o.hashCode();   // line 29 
     } else { 
      int len = o.toString().length(); 
      return() -> len;     // line 32 
     } 
    } 

    static void run() { 
     Object big = new byte[10_000_000]; 

     list = Arrays.asList(
      bar(false, big), 
      bar(true, big), 
      foo(false, big), 
      foo(true, big)); 

     System.out.println("Done."); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     run(); 
     Thread.sleep(Long.MAX_VALUE); // stay alive so a heap dump can be taken 
    } 
} 

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

ऐसा करने के लिए पहली बात यह है कि इस कक्षा को संकलित करें और javap -v -p CaptureTest चलाएं। -v विकल्प विघटित बाइटकोड और अन्य जानकारी जैसे लाइन नंबर टेबल दिखाता है। निजी तरीकों को अलग करने के लिए javap प्राप्त करने के लिए -p विकल्प प्रदान किया जाना चाहिए। इस के उत्पादन में सामान का एक बहुत शामिल है, लेकिन महत्वपूर्ण भागों सिंथेटिक लैम्ब्डा तरीके हैं:

private static int lambda$bar$3(int); 
    descriptor: (I)I 
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
    Code: 
    stack=1, locals=1, args_size=1 
     0: iload_0 
     1: ireturn 
    LineNumberTable: 
     line 32: 0 
    LocalVariableTable: 
     Start Length Slot Name Signature 
      0  2  0 len I 

private static int lambda$bar$2(java.lang.Object); 
    descriptor: (Ljava/lang/Object;)I 
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
    Code: 
    stack=1, locals=1, args_size=1 
     0: aload_0 
     1: invokevirtual #3     // Method java/lang/Object.hashCode:()I 
     4: ireturn 
    LineNumberTable: 
     line 29: 0 
    LocalVariableTable: 
     Start Length Slot Name Signature 
      0  5  0  o Ljava/lang/Object; 

private static int lambda$foo$1(int); 
    descriptor: (I)I 
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
    Code: 
    stack=1, locals=1, args_size=1 
     0: iload_0 
     1: ireturn 
    LineNumberTable: 
     line 23: 0 
    LocalVariableTable: 
     Start Length Slot Name Signature 
      0  2  0  h I 

private static int lambda$foo$0(); 
    descriptor:()I 
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 
    Code: 
    stack=1, locals=0, args_size=0 
     0: iconst_0 
     1: ireturn 
    LineNumberTable: 
     line 20: 0 

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

फिर, मेमोरी प्रोफाइलर के तहत प्रोग्राम चलाएं, कमांड लाइन तर्क -Djdk.internal.lambda.dumpProxyClasses=<outputdir>java कमांड पर आपूर्ति करें। यह लैम्ब्डा मेटाफैक्टरी को इसके जेनरेट किए गए वर्गों को नामित निर्देशिका में डंप करने का कारण बनता है (जो पहले से मौजूद होना चाहिए)।

एप्लिकेशन की यादृच्छिक प्रोफ़ाइल प्राप्त करें और इसका निरीक्षण करें। ऐसा करने के कई तरीके हैं; मैंने NetBeans मेमोरी प्रोफाइलर का इस्तेमाल किया। जब मैंने इसे चलाया, तो उसने मुझे बताया कि CaptureTest$$Lambda$9 नामक कक्षा में arg$1 फ़ील्ड द्वारा 10,000,000 तत्वों के साथ एक बाइट [] आयोजित किया गया था। यह ओपी के रूप में है।

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

हालांकि, हमने लैम्बडा मेटाफैक्टरी से अपने वर्गों को डंप करने के लिए कहा है, इसलिए हम यह देखने के लिए इस विशेष कक्षा को देख सकते हैं कि यह क्या करता है। वास्तव में, आउटपुट निर्देशिका में, एक फ़ाइल CaptureTest$$Lambda$9.class है।चल रहा है उस पर javap -c का पता चलता है निम्नलिखित:

final class CaptureTest$$Lambda$9 implements java.util.function.IntSupplier { 
    public int getAsInt(); 
    Code: 
     0: aload_0 
     1: getfield  #15     // Field arg$1:Ljava/lang/Object; 
     4: invokestatic #28     // Method CaptureTest.lambda$bar$2:(Ljava/lang/Object;)I 
     7: ireturn 
} 

आप निरंतर पूल प्रविष्टियों डिकंपाइल सकते हैं, लेकिन javap काम आते हुए bytecodes के अधिकार के लिए टिप्पणी में प्रतीकात्मक नाम डालता है। आप देख सकते हैं कि यह arg$1 फ़ील्ड - अपमानजनक संदर्भ लोड करता है - और इसे विधि CaptureTest.lambda$bar$2 पर भेजता है। यह हमारी स्रोत फ़ाइल में लैम्ब्डा नंबर 2 (शून्य से शुरू) है, और यह bar() विधि के भीतर दो लैम्ब्डा अभिव्यक्तियों में से पहला है। अब आप मूल कक्षा के javap आउटपुट पर वापस जा सकते हैं और स्रोत फ़ाइल में स्थान ढूंढने के लिए लैम्ब्डा स्थिर विधि से लाइन नंबर जानकारी का उपयोग कर सकते हैं। CaptureTest.lambda$bar$2 विधि अंकों की लाइन नंबर जानकारी इस स्थान पर 29 लैम्ब्डा लाइन

() -> o.hashCode() 

जहां o एक नि: शुल्क चर, जो bar() विधि करने के लिए तर्क में से एक का कब्जा है है।

+0

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

+0

@ होल्गर राइट। उदाहरण के लिए बड़ी वस्तु पर कब्जा कर लिया गया उदाहरण 'कैप्चरटेस्ट $$ लैम्ब्डा $ 9' नहीं हो सकता है; उदाहरण के लिए यह 'कैप्चरटेस्ट $$ लैम्ब्डा $ 347' हो सकता है। ढेर डंप आपको बताता है कि यह कौन सा वर्ग है। एक बार आपके पास उस वर्ग के बाद, आप यह पता लगाने के लिए इसे अलग कर सकते हैं कि यह किस स्थिर विधि को कॉल करता है। उस स्थैतिक विधि को स्रोत कोड पर वापस देखा जा सकता है। लैम्ब्डा स्थिर तरीकों का नामकरण करने के नियम निर्दिष्ट नहीं हैं, लेकिन वे स्थिर हैं और काफी अनुमानित हैं। –

+0

@ होल्गर स्थिर विधि डिस्सेप्लर से लाइन नंबर तालिका स्रोत फ़ाइल में सही स्थान पर भी इंगित करती है। मैंने इसे शामिल करने के लिए उत्तर संपादित किया है। –

0

संख्या बहुत बेकार है, क्योंकि यह रनटाइम पर निर्धारित है कि प्रत्येक कक्षा में कहा गया था कि लैम्ब्डा बनाया गया था (सामना करना पड़ा) - यदि यह सही है कि आपके पास उस एकल वर्ग में 100 लैम्ब्डा से अधिक है। देखें What does $$ in javac generated name mean?

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

3

यह एक छोटे से जटिल है, लेकिन आप कोशिश कर सकते हैं:

  • -Djdk.internal.lambda.dumpProxyClasses=/path/to/directory/ के साथ अपने JVM शुरू। विकल्प आपकी पसंद की निर्देशिका में जेवीएम डंप जेनरेट प्रॉक्सी ऑब्जेक्ट्स (क्लास फाइल) बना देगा

  • आप इस तरह से जेनरेट की गई कक्षाओं को अपनाने का प्रयास कर सकते हैं। वहाँ से

    import java.util.function.IntPredicate; 
    
    // $FF: synthetic class 
    final class Test$$Lambda$3 implements IntPredicate { 
        private Test$$Lambda$3() { 
        } 
    
        public boolean test(int var1) { 
         return Test.lambda$bar$1(var1); 
        } 
    } 
    
  • : मैं एक नमूना जावा कोड है कि lambdas इस्तेमाल किया और फिर IntelliJ विचार में और इसे करने के लिए decompiled कर दिया गया है उत्पन्न वर्ग फ़ाइलों में से एक (टेस्ट $$ लैम्ब्डा $ 3.class नाम की फ़ाइल) खोला बनाया है आप (उदाहरण में IntPredicate) लैम्ब्डा के प्रकार के अनुमान लगा सकते हैं, वर्ग के नाम इसमें (Test) परिभाषित किया गया था और विधि का नाम उस में (bar) परिभाषित किया गया था।

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