2014-10-29 18 views
16

में दृढ़ता से पहुंचने योग्य ऑब्जेक्ट पर कॉल किया गया है (हमने हाल ही में जावा 7 से जावा 8 तक हमारे संदेश प्रसंस्करण एप्लिकेशन को अपग्रेड किया है। अपग्रेड के बाद से, हमें कभी-कभी अपवाद मिलता है कि इसे पढ़ने के दौरान एक धारा बंद कर दी गई है। लॉगिंग से पता चलता है कि फाइनलज़र थ्रेड उस ऑब्जेक्ट पर finalize() पर कॉल कर रहा है जिसमें स्ट्रीम है (जो बदले में स्ट्रीम बंद कर देता है)।जावा 8

कोड की बुनियादी रूपरेखा इस प्रकार है:

MIMEWriter writer = new MIMEWriter(out); 
in = new InflaterInputStream(databaseBlobInputStream); 
MIMEBodyPart attachmentPart = new MIMEBodyPart(in); 
writer.writePart(attachmentPart); 

MIMEWriter और MIMEBodyPart एक देसी माइम/HTTP पुस्तकालय का हिस्सा हैं। MIMEBodyPartHTTPMessage फैली हुई है, जो निम्नलिखित:

public void close() throws IOException 
{ 
    if (m_stream != null) 
    { 
     m_stream.close(); 
    } 
} 

protected void finalize() 
{ 
    try 
    { 
     close(); 
    } 
    catch (final Exception ignored) { } 
} 

अपवाद MIMEWriter.writePart के आह्वान चेन, है जो इस प्रकार में होता है:

  1. MIMEWriter.writePart() भाग के लिए हेडर लिखते हैं, तो कॉल part.writeBodyPartContent(this)
  2. MIMEBodyPart.writeBodyPartContent() आउटपुट
  3. MIMEBodyPart.getContentStream() jus पर सामग्री स्ट्रीम करने के लिए हमारी उपयोगिता विधि IOUtil.copy(getContentStream(), out) पर कॉल करता है टी नियंत्रक में पारित इनपुट स्ट्रीम (
  4. IOUtil.copy में एक लूप है जो इनपुट स्ट्रीम से 8K खंड पढ़ता है और इनपुट स्ट्रीम खाली होने तक आउटपुट स्ट्रीम में लिखता है।

MIMEBodyPart.finalize() कहा जाता है, जबकि IOUtil.copy चल रहा है, और यह निम्न अपवाद हो जाता है:

java.io.IOException: Stream closed 
    at java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67) 
    at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142) 
    at java.io.FilterInputStream.read(FilterInputStream.java:107) 
    at com.blah.util.IOUtil.copy(IOUtil.java:153) 
    at com.blah.core.net.MIMEBodyPart.writeBodyPartContent(MIMEBodyPart.java:75) 
    at com.blah.core.net.MIMEWriter.writePart(MIMEWriter.java:65) 

हम HTTPMessage.close() विधि है कि फोन करने वाले की स्टैक ट्रेस लॉग इन और साबित कर दिया है कि में कुछ लॉगिंग डाल निश्चित रूप से फाइनलज़र थ्रेड जो HTTPMessage.finalize() का आह्वान कर रहा है जबकि IOUtil.copy() चल रहा है।

MIMEBodyPart ऑब्जेक्ट वर्तमान थ्रेड के ढेर से thisMIMEBodyPart.writeBodyPartContent के लिए स्टैक फ्रेम में निश्चित रूप से पहुंच योग्य है। मुझे समझ में नहीं आता कि क्यों JVM finalize() पर कॉल करेगा।

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

कामकाज के रूप में, मैंने जावा मेल एमआईएम लाइब्रेरी का उपयोग करके प्रभावित कोड को फिर से लिखा है और समस्या दूर हो गई है (संभवतः जावा मेल finalize() का उपयोग नहीं करता है)। हालांकि, मुझे चिंता है कि आवेदन में अन्य finalize() विधियों को गलत तरीके से बुलाया जा सकता है, या जावा उन वस्तुओं को कचरा-संग्रह करने की कोशिश कर रहा है जो अभी भी उपयोग में हैं।

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

+2

मुझे आश्चर्य होगा अगर इसमें कोई अन्य स्पष्टीकरण नहीं है। मौजूदा धागे हमेशा संग्राहक के लिए लाइव ऑब्जेक्ट्स की पहचान करने के लिए "रूट" होता है। आप यह सुनिश्चित करने के लिए कैसे जानते हैं कि * आपके IOUtils.copy() रिटर्न से पहले * अंतिमकरण का आह्वान किया जा रहा है? – Bhaskar

+0

यह एक जेआईटी बग की तरह लगता है। मैं जेआईटी डीबगिंग चालू कर दूंगा और देख सकता हूं कि वहां कोई पैटर्न है या नहीं। – chrylis

+0

@ भास्कर, अपवाद साबित करता है कि स्ट्रीम बंद है जबकि 'IOUtil.copy()' निष्पादित हो रहा है। – Nathan

उत्तर

19

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

this other answer देखें कि किसी ऑब्जेक्ट को जीसीड किया जा सकता है, जबकि एक स्थानीय चर संदर्भ में यह अभी भी गुंजाइश है।

class FinalizeThis { 
    protected void finalize() { 
     System.out.println("finalized!"); 
    } 

    void loop() { 
     System.out.println("loop() called"); 
     for (int i = 0; i < 1_000_000_000; i++) { 
      if (i % 1_000_000 == 0) 
       System.gc(); 
     } 
     System.out.println("loop() returns"); 
    } 

    public static void main(String[] args) { 
     new FinalizeThis().loop(); 
    } 
} 

जबकि loop() विधि सक्रिय है, वहाँ के संदर्भ में कुछ भी करने से किसी भी कोड की कोई संभावना नहीं है:

यहाँ कैसे एक वस्तु को अंतिम रूप दिया जा सकता है जबकि एक उदाहरण विधि कॉल सक्रिय है, इसका एक उदाहरण FinalizeThis ऑब्जेक्ट, तो यह पहुंच योग्य नहीं है। और इसलिए इसे अंतिम रूप दिया जा सकता है और जीसी'एड किया जा सकता है। जेडीके 8 जीए पर, यह निम्न प्रिंट करता है:

loop() called 
finalized! 
loop() returns 

हर बार।

कुछ समान हो सकता है MimeBodyPart के साथ। क्या यह एक स्थानीय चर में संग्रहीत किया जा रहा है? (यह बहुत लगता है, के बाद से कोड एक परंपरा है कि खेतों एक m_ उपसर्ग के साथ नाम हैं का पालन करने लगता है।)

अद्यतन

टिप्पणियों में, ओपी निम्नलिखित परिवर्तन करने का सुझाव दिया:

public static void main(String[] args) { 
     FinalizeThis finalizeThis = new FinalizeThis(); 
     finalizeThis.loop(); 
    } 
इस बदलाव वह अंतिम रूप देने का पालन नहीं किया था के साथ

, और न तो, हालांकि मैं ऐसा करता है, तो यह आगे परिवर्तन किया जाता है:

public static void main(String[] args) { 
     FinalizeThis finalizeThis = new FinalizeThis(); 
     for (int i = 0; i < 1_000_000; i++) 
      Thread.yield(); 
     finalizeThis.loop(); 
    } 

अंतिम बार एक बार फिर होता है। मुझे संदेह है कि लूप के बिना, main() विधि का संकलन नहीं किया गया है, संकलित नहीं किया गया है। दुभाषिया संभवतः पहुंच क्षमता के बारे में कम आक्रामक है। जगह में उपज लूप के साथ, main() विधि संकलित हो जाती है, और जेआईटी कंपाइलर का पता लगाता है कि finalizeThis पहुंच योग्य नहीं है जबकि loop() विधि निष्पादित हो रही है।

इस व्यवहार को ट्रिगर करने का एक और तरीका है JVM के लिए -Xcomp विकल्प का उपयोग करना, जो निष्पादन से पहले जेआईटी-संकलित होने के तरीकों को मजबूर करता है। मैं इस तरह से एक पूरा आवेदन नहीं चलाऊंगा - जेआईटी-संकलन सबकुछ काफी धीमा हो सकता है और बहुत सी जगह ले सकता है - लेकिन यह लूप के साथ टंकण करने के बजाय, छोटे परीक्षण कार्यक्रमों में इस तरह के मामलों को फिसलने के लिए उपयोगी है।

+2

धन्यवाद @ स्टुअर्ट मार्क्स - आपने स्टैक ओवरफ़्लो में अपना विश्वास बहाल कर दिया है। आपके प्रोग्राम के बारे में दिलचस्प सोच यह है कि यह मुझे जावा 7 और जावा 8 पर भी देता है। मेरा मुद्दा केवल तब दिखाई देता है जब हमने जावा 8 पर स्विच किया था। आपका विश्लेषण हालांकि समझ में आता है, और आगे हमें 'अंतिम()' को हटाने के लिए प्रेरित करता है कोड बेस – Nathan

+0

एक अन्य प्रश्न - मैंने आपके प्रोग्राम में मुख्य विधि को 'अंतिम रूप दिया है FinalizeThis = नया FinalizeThis(); finalizeThis.loop(); '। एक बार मैंने ऐसा करने के बाद, वस्तु को कभी अंतिम रूप दिया नहीं गया था। मुझे यकीन नहीं है कि यह अलग क्यों होगा। आपके पास कोई विचार है? – Nathan

+2

@ नेथन मैंने आपकी टिप्पणी के जवाब में अपना जवाब अपडेट कर दिया है। इस बारे में निश्चित नहीं है कि आप केवल जावा पर अपने ऐप में समस्या क्यों देखते हैं। जेआईटी हेरिस्टिक्स का एक गुच्छा शायद 7 से 8 के बीच बदल गया है, जो इस तरह के व्यवहार मतभेदों के लिए जिम्मेदार हो सकता है। साथ ही, यदि आप फाइनलज़र को हटाते हैं, तो सुनिश्चित करें कि कुछ अंततः स्ट्रीम को बंद कर देता है। शायद संसाधनों के साथ प्रयास करें। –

2

आपका फ़ाइनलाइज़र सही नहीं है।

सबसे पहले, इसे कैच ब्लॉक की आवश्यकता नहीं है, और इसे अपने finally{} ब्लॉक में super.finalize() पर कॉल करना होगा।

protected void finalize() throws Throwable 
{ 
    try 
    { 
     // do stuff 
    } 
    finally 
    { 
     super.finalize(); 
    } 
} 

दूसरे, आप यह सोचते हैं कर रहे हैं आप m_stream को केवल संदर्भ है, जो या सही नहीं हो सकता पकड़: एक finalizer के विहित प्रपत्र इस प्रकार है। m_stream सदस्य को खुद को अंतिम रूप देना चाहिए। लेकिन आपको इसे पूरा करने के लिए कुछ भी करने की आवश्यकता नहीं है। अंततः m_streamFileInputStream या FileOutputStream या सॉकेट स्ट्रीम होगा, और वे पहले से ही स्वयं को सही तरीके से अंतिम रूप देंगे।

मैं इसे हटा दूंगा।

+0

मैं 'super.finalize()' को कॉल करने के सिद्धांत में सहमत हूं लेकिन इस मामले में 'HTTPMessage'' ऑब्जेक्ट 'बढ़ाता है जिसमें खाली' अंतिमकरण ') है। मैं मानता हूं कि जिस कोड के रूप में यह खड़ा है वह इष्टतम नहीं है, लेकिन मुझे यकीन नहीं है कि यह प्रासंगिक है। समस्या यह है कि 'अंतिम() 'जावा 8 के तहत गलत तरीके से कहा जाता है लेकिन यह जावा 7 के तहत नहीं था। – Nathan

+0

मैं अभी भी इसे हटा दूंगा और देखें कि क्या होता है। आपको इसकी आवश्यकता नहीं है और यह मेरे द्वारा बताए गए सभी कारणों से त्रुटि का एक संभावित स्रोत है। – EJP

+0

मैं इस बात से सहमत हूं कि इस मामले में 'अंतिमकरण() 'की आवश्यकता नहीं है (हम कोड में सीधे' in.close() 'को कॉल करते हैं)। हालांकि, हमारी HTTP/MIME लाइब्रेरी का उपयोग अन्य परिदृश्यों में किया जाता है जहां 'अंतिम() 'की आवश्यकता हो सकती है। जैसा कि मैंने उल्लेख किया है, मैं भविष्य में पुस्तकालय और भविष्य में अपने सभी उपयोगों को फिर से समाप्त करने के लिए 'अंतिमकरण() 'को हटा दूंगा। समस्या से बचने के लिए मैंने प्रश्न में कोड को पहले से ही लिखा है। हमारे पास हमारे कोड के अन्य क्षेत्र हैं जिनके पास 'अंतिमकरण() 'विधियां भी हैं। मेरी चिंता यह है कि हम अपने कोड के अन्य हिस्सों में इसी मुद्दे पर भाग सकते हैं। – Nathan