ओपी का अनुमान है कि 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()
विधि करने के लिए तर्क में से एक का कब्जा है है।
एक पकड़ है। जैसा कि आपने कहा था, लैम्ब्डा कक्षा के नामों की संख्या रनटाइम पीढ़ी के क्रम की क्रम संख्या है। तो वास्तविक लैम्ब्डा अभिव्यक्तियों के लिए उनका मानचित्रण लैम्ब्डा अभिव्यक्तियों के सटीक मूल्यांकन आदेश पर निर्भर करता है, जो जटिल कार्यक्रमों के लिए अलग-अलग रनों में भिन्न हो सकता है। – Holger
@ होल्गर राइट। उदाहरण के लिए बड़ी वस्तु पर कब्जा कर लिया गया उदाहरण 'कैप्चरटेस्ट $$ लैम्ब्डा $ 9' नहीं हो सकता है; उदाहरण के लिए यह 'कैप्चरटेस्ट $$ लैम्ब्डा $ 347' हो सकता है। ढेर डंप आपको बताता है कि यह कौन सा वर्ग है। एक बार आपके पास उस वर्ग के बाद, आप यह पता लगाने के लिए इसे अलग कर सकते हैं कि यह किस स्थिर विधि को कॉल करता है। उस स्थैतिक विधि को स्रोत कोड पर वापस देखा जा सकता है। लैम्ब्डा स्थिर तरीकों का नामकरण करने के नियम निर्दिष्ट नहीं हैं, लेकिन वे स्थिर हैं और काफी अनुमानित हैं। –
@ होल्गर स्थिर विधि डिस्सेप्लर से लाइन नंबर तालिका स्रोत फ़ाइल में सही स्थान पर भी इंगित करती है। मैंने इसे शामिल करने के लिए उत्तर संपादित किया है। –