กรณีและการวิเคราะห์
พื้นหลังปัญหา
ใน Tomcat รหัสต่อไปนี้ทั้งหมดอยู่ใน WebApp ซึ่งจะทำให้ WebappClassLoader รั่วไหลและไม่สามารถรีไซเคิลได้
คลาสสาธารณะ myCounter {count int ส่วนตัว = 0; การเพิ่มขึ้นของโมฆะสาธารณะ () {count ++; } public int getCount () {นับคืน; }} คลาสสาธารณะ MyThreadLocal ขยาย threadLocal <MyCounter> {} คลาสสาธารณะที่รั่วไหลออกมาขยาย httpservlet {ส่วนตัวคงที่ MythreadLocal MythreadLocal = ใหม่ mythreadLocal (); Void Doget ที่ได้รับการป้องกัน (คำขอ httpservletRequest, การตอบสนอง httpservletResponse) พ่น servletexception, ioexception {mycounter counter = mythreadlocal.get (); if (counter == null) {counter = new myCounter (); mythreadlocal.set (ตัวนับ); } response.getWriter (). println ("เธรดปัจจุบันทำหน้าที่ servlet นี้" + counter.getCount () + "เวลา"); counter.increment (); - ในรหัสข้างต้นตราบใดที่ LeakingServlet ถูกเรียกครั้งเดียวและเธรดที่ดำเนินการจะไม่หยุดการรั่วไหล WebappClassLoader จะเกิดขึ้น ทุกครั้งที่คุณโหลดแอปพลิเคชัน reload คุณจะมีอินสแตนซ์ WebappClassLoader เพิ่มเติมซึ่งในที่สุดจะนำไปสู่ PermGen OutOfMemoryException
แก้ปัญหา
ทีนี้ลองคิดดูสิ: ทำไม subclass ThreadLocal ด้านบนทำให้หน่วยความจำรั่วไหล
webappclassloader
ก่อนอื่นเราต้องคิดออกว่า WebappClassLoader คืออะไร?
สำหรับเว็บแอปพลิเคชันที่ทำงานในคอนเทนเนอร์ Java EE การใช้งานของรถตักคลาสนั้นแตกต่างจากแอปพลิเคชัน Java ทั่วไป คอนเทนเนอร์เว็บที่แตกต่างกันจะถูกนำไปใช้แตกต่างกัน ใน Apache Tomcat แต่ละเว็บแอปพลิเคชันมีอินสแตนซ์ตัวโหลดคลาสที่สอดคล้องกัน ตัวโหลดคลาสนี้ยังใช้โหมดพร็อกซีความแตกต่างคือครั้งแรกที่พยายามโหลดคลาสที่แน่นอนจากนั้นพร็อกซีไปยังตัวโหลดคลาสแม่หากไม่สามารถพบได้ นี่คือสิ่งที่ตรงกันข้ามกับลำดับของรถตักคลาสทั่วไป นี่เป็นแนวทางปฏิบัติที่แนะนำในข้อกำหนดของ Java Servlet และวัตถุประสงค์คือการทำให้คลาสของเว็บแอปพลิเคชันจัดลำดับความสำคัญสูงกว่าที่จัดทำโดยเว็บคอนเทนเนอร์ ข้อยกเว้นสำหรับรูปแบบพร็อกซีนี้คือคลาสในไลบรารี Java Core ไม่ได้อยู่ในขอบเขตของการค้นหา นี่คือเพื่อให้แน่ใจว่าประเภทความปลอดภัยของห้องสมุดหลัก Java
กล่าวอีกนัยหนึ่ง WebappClassLoader เป็นตัวโหลดคลาสที่กำหนดเองสำหรับ Tomcat เพื่อโหลด WebApps คลาสโหลดเดอร์ของแต่ละ Webapp นั้นแตกต่างกัน นี่คือการแยกคลาสที่โหลดโดยแอปพลิเคชันที่แตกต่างกัน
ดังนั้นคุณสมบัติของ WebappClassLoader เกี่ยวข้องกับการรั่วไหลของหน่วยความจำอย่างไร ยังไม่สามารถมองเห็นได้ แต่เป็นคุณสมบัติที่สำคัญมากที่สมควรได้รับความสนใจของเรา: แต่ละ WebApp มี WebappClassLoader ของตัวเองซึ่งแตกต่างจากตัวโหลดคลาส Java Core
เรารู้ว่าการรั่วไหล WebappClassLoader ต้องเป็นเพราะมีการอ้างอิงอย่างมากจากวัตถุอื่น ๆ ดังนั้นเราจึงสามารถพยายามวาดแผนภาพความสัมพันธ์อ้างอิงของพวกเขา ฯลฯ ตัวโหลดคลาสทำงานอะไรกันแน่? ทำไมจึงถูกบังคับให้อ้าง?
วงจรชีวิตของชั้นเรียนและตัวโหลดชั้นเรียน
เพื่อแก้ปัญหาข้างต้นเราต้องศึกษาความสัมพันธ์ระหว่างวงจรชีวิตของชั้นเรียนและตัวโหลดชั้นเรียน
สิ่งสำคัญที่เกี่ยวข้องกับกรณีของเราคือการถอนการติดตั้งในชั้นเรียน:
หลังจากใช้คลาสหากพบกับสถานการณ์ต่อไปนี้คลาสจะถูกถอนการติดตั้ง:
1. อินสแตนซ์ทั้งหมดของคลาสนี้ได้รับการรีไซเคิลนั่นคือไม่มีกรณีของคลาสนี้อยู่ในกอง Java
2. การโหลด ClassLoader คลาสนี้ได้รับการรีไซเคิล
3. วัตถุ java.lang.Class ที่สอดคล้องกับคลาสนี้ไม่ได้อ้างอิงทุกที่และไม่มีวิธีการเข้าถึงคลาสผ่านการสะท้อนกลับได้ทุกที่
หากเป็นไปตามเงื่อนไขทั้งสามข้างต้น JVM จะถอนการติดตั้งชั้นเรียนระหว่างการเก็บขยะในพื้นที่วิธีการ กระบวนการถอนการติดตั้งชั้นเรียนนั้นจริง ๆ แล้วเพื่อล้างข้อมูลชั้นเรียนในพื้นที่วิธีการและวงจรชีวิตทั้งหมดของคลาส Java สิ้นสุดลง
คลาสที่โหลดโดยตัวโหลดคลาสที่มาพร้อมกับเครื่องเสมือน Java จะไม่ถูกถอนการติดตั้งในระหว่างวงจรชีวิตของเครื่องเสมือน รถตักคลาสที่มาพร้อมกับเครื่องเสมือน Java รวมถึงตัวตักรูทคลาสรถตักคลาสส่วนขยายและตัวโหลดคลาสของระบบ เครื่องเสมือน Java นั้นมักจะอ้างอิงตัวโหลดคลาสเหล่านี้เสมอและตัวโหลดคลาสเหล่านี้มักจะอ้างถึงวัตถุคลาสของคลาสที่พวกเขาโหลดดังนั้นวัตถุคลาสเหล่านี้สามารถเข้าถึงได้เสมอ
คลาสที่โหลดโดยตัวโหลดคลาสที่ผู้ใช้กำหนดสามารถขนถ่ายได้
สังเกตประโยคข้างต้น หากการรั่วไหล WebappClassLoader หมายความว่าคลาสที่โหลดไม่สามารถถอนการติดตั้งได้ซึ่งอธิบายว่าทำไมรหัสข้างต้นทำให้เกิด PermGen OutOfMemoryException
ดูภาพด้านล่างที่จุดสำคัญ
เราสามารถพบได้ว่า วัตถุคลาสโหลดเดอร์นั้นเชื่อมโยงแบบสองทิศทางกับวัตถุคลาสที่โหลด ซึ่งหมายความว่าวัตถุคลาสอาจเป็นผู้กระทำผิดของการอ้างอิงบังคับให้ WebappClassLoader ทำให้มันรั่วไหล
แผนผังความสัมพันธ์
หลังจากทำความเข้าใจความสัมพันธ์ระหว่างตัวโหลดคลาสและวงจรชีวิตของชั้นเรียนเราสามารถเริ่มวาดแผนภาพความสัมพันธ์อ้างอิงได้ ( LeakingServlet.class และ myThreadLocal ในรูปไม่เข้มงวดในการวาดภาพอ้างอิงส่วนใหญ่เพื่อแสดงว่า myThreadLocal เป็นตัวแปรคลาส)
ด้านล่างเราวิเคราะห์สาเหตุของการรั่วไหล WebappClassLoader ตามรูปด้านบน
1. LeakingServlet ถือ MyThreadLocal static ที่ส่งผลให้วงจรชีวิตของ myThreadLocal ตราบใดที่วงจรชีวิตของคลาส LeakingServlet ซึ่งหมายความว่า myThreadLocal จะไม่ถูกรีไซเคิลและการอ้างอิงที่อ่อนแอนั้นไร้ประโยชน์ดังนั้นเธรดปัจจุบันไม่สามารถล้างการอ้างอิงที่แข็งแกร่งของตัวนับผ่านมาตรการป้องกันของ ThreadLocalMap
2. ห่วงโซ่อ้างอิงที่แข็งแกร่ง: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader ทำให้ WebappClassLoader รั่วไหล
สรุป
การรั่วไหลของหน่วยความจำนั้นยากที่จะตรวจจับและมักเกิดจากเหตุผลหลายประการ Threadlocal ได้กลายเป็นผู้เข้าชมบ่อยครั้งที่มีการรั่วไหลของหน่วยความจำเนื่องจากวงจรชีวิตที่ถูกผูกไว้กับเธรดและความประมาทเล็กน้อยจะนำไปสู่ภัยพิบัติ บทความนี้เป็นเพียงการวิเคราะห์กรณีเฉพาะ หากคุณสามารถใช้สิ่งนี้เพื่อนำไปใช้กับกรณีอื่น ๆ มันจะยอดเยี่ยม หวังว่าบทความนี้จะเป็นประโยชน์กับทุกคน