เมื่อค้นหาปัญหาประสิทธิภาพ JVM คุณอาจพบการรั่วไหลของหน่วยความจำและทำให้ JVM outofmemory หากพารามิเตอร์ reloadable = "true" ถูกตั้งค่าเมื่อใช้คอนเทนเนอร์ Tomcat คุณอาจพบหน่วยความจำล้นเมื่อปรับใช้แอปพลิเคชันบ่อยครั้ง หลักการของการปรับใช้ Hot Tomcat คือการตรวจพบว่าไฟล์ใน Web-Inf/classes หรือไดเรกทอรี Web-Inf/LIB มีการเปลี่ยนแปลงและแอปพลิเคชันจะหยุดก่อนแล้วจึงเริ่ม เนื่องจาก Tomcat กำหนด webappclassloader ให้กับแต่ละแอปพลิเคชันโดยค่าเริ่มต้นหลักการของการเปลี่ยนร้อนคือการสร้างคลาสโหลดใหม่เพื่อโหลดคลาส เนื่องจากความเป็นเอกลักษณ์ของคลาสใน JVM ถูกกำหนดโดยไฟล์คลาสและตัวโหลดคลาสของมันการโหลดคลาสใหม่สามารถบรรลุวัตถุประสงค์ของการเปลี่ยนร้อน เมื่อจำนวนการปรับใช้ HOT นั้นบ่อยขึ้นมันจะทำให้คลาสที่โหลดโดย JVM มากขึ้น หากคลาสก่อนหน้าไม่ได้ขนถ่ายในเวลาด้วยเหตุผลบางอย่าง (เช่นการรั่วไหลของหน่วยความจำ) อาจนำไปสู่การสร้างถาวรหรือ metaspace outofmemory บทความนี้แนะนำสถานการณ์สั้น ๆ ที่ ThreadLocal และ classloader นำไปสู่การรั่วไหลของหน่วยความจำและในที่สุด outofmemory ผ่านการสาธิต
ถอนการติดตั้งชั้นเรียน
หลังจากใช้ชั้นเรียนหากสถานการณ์ต่อไปนี้เป็นที่พอใจจะถูกถอนการติดตั้ง:
1. อินสแตนซ์ทั้งหมดของคลาสนี้ในฮีปได้รับการรีไซเคิลนั่นคือวัตถุอินสแตนซ์ของคลาสนี้ไม่มีอยู่ในกอง
2. การโหลด classloader คลาสนี้ได้รับการรีไซเคิล
3. วัตถุคลาสที่สอดคล้องกับคลาสนี้ไม่สามารถอ้างอิงได้ทุกที่และวัตถุคลาสไม่สามารถเข้าถึงได้ผ่านการสะท้อนกลับ
หากชั้นเรียนเป็นไปตามเงื่อนไขการถอนการติดตั้ง JVM จะถอนการติดตั้งคลาสเมื่ออยู่ใน GC นั่นคือล้างข้อมูลคลาสในพื้นที่วิธีการ
บทนำฉาก
ในบทความก่อนหน้านี้ฉันแนะนำหลักการของ Threadlocal แต่ละเธรดมี ThreadlocalMap หากวงจรชีวิตของเธรดค่อนข้างยาวรายการใน ThreadLocalMap อาจไม่ได้รีไซเคิล วัตถุ ThreadLocal ได้รับการอ้างอิงอย่างมากโดยเธรด เนื่องจากวัตถุอินสแตนซ์จะเก็บข้อมูลอ้างอิงของวัตถุคลาสวัตถุคลาสจะเก็บข้อมูลอ้างอิงของคลาสโหลดที่โหลดซึ่งจะทำให้คลาสถูกขนถ่าย เมื่อมีการโหลดคลาสเพียงพออาจมีการสร้างหน่วยความจำแบบถาวรหรือ Metaspace Memory Overflow หากชั้นเรียนมีวัตถุขนาดใหญ่เช่นอาร์เรย์ไบต์ขนาดใหญ่มันจะทำให้หน่วยความจำล้นของพื้นที่กอง Java
การแนะนำซอร์สโค้ด
นี่คือชั้นเรียนภายใน คลาสด้านในมีวัตถุ ThreadLocal แบบคงที่ซึ่งส่วนใหญ่จะใช้ในการทำให้เธรดมีการอ้างอิงที่แข็งแกร่งไปยังคลาสด้านในเพื่อให้คลาสภายในไม่สามารถรีไซเคิลได้ ตัวโหลดคลาสที่กำหนดเองถูกกำหนดให้โหลดคลาสด้านในดังที่แสดงด้านล่าง:
หน่วยความจำระดับสาธารณะ {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// เนื่องจากเธรดทำงานอยู่ตลอดเวลาวัตถุภายในใน ThreadLocalMap ได้รับการอ้างอิงอย่างมากโดยเธรดเธรดใหม่ (ใหม่ runnable () {@Override void run () {true) ("load1", memoryLeak.class.getClassLoader (), "com.ezlippi.memoryleak $ inner", "com.ezlippi.memoryleak $ inner $ 1"); GC สำหรับการประมวลผลอ้างอิง innerclass = null; } // เพื่อที่จะไปถึงพื้นที่ฮีปได้เร็วขึ้นชั้นเรียนคงที่สาธารณะ {ไบต์ส่วนตัว [] mb = ไบต์ใหม่ [1024 * 1024]; Static ThreadLocal <inner> threadLocal = new ThreadLocal <Nner> () {@Override ปกป้อง initialValue () {return new Inner (); - // เพื่อเรียก threadlocal.get () เพื่อเริ่มต้นวัตถุภายใน {threadlocal.get (); } public inner () {}} // ซอร์สโค้ดละเว้นคลาสคงที่คลาสส่วนตัว customClassLoader ขยาย classloader {}หน่วยความจำในพื้นที่กองล้น
เพื่อที่จะทริกเกอร์หน่วยความจำฮีปล้นฉันตั้งค่าอาร์เรย์ 1MB ไบต์ในคลาสด้านในและในเวลาเดียวกันฉันต้องเรียก threadlocal.get () ในบล็อกคงที่ เฉพาะการโทรเท่านั้นที่จะทริกเกอร์ initialValue () เพื่อเริ่มต้นวัตถุภายในมิฉะนั้นฉันจะสร้างวัตถุ ThreadLocal ที่ว่างเปล่าและไม่มีข้อมูลใน ThreadLocalMap
พารามิเตอร์ JVM มีดังนี้:
-xMS100M -XMX100M -XX:+USEPARNEWGC -XX:+USECONCMARKSWEEPGC -XX:+PRINTGCDETAILS -XX:+PRINTEAPATGC -XX:+PRINTCLASSHISTOGRAM -XX:+HEAPDUMPONOUTOFMEMOROROR
หลังจากการประหารชีวิต 814 ครั้งล่าสุดหน่วยความจำพื้นที่ JVM Heap Memory Overflows ดังที่แสดงด้านล่าง:
java.lang.outofMemoryError: java heap spacedumping heap ไปยัง java_pid11824.hprof ... ใช้ 99% [0x00000000F9C000000, 0x00000000FB6AD450, 0x00000000FB6B0000) จากอวกาศ 3392K, 90% ใช้ [0x0000000000FB6B0000, 0x00000000FB9B0030, 0x0000000000 [0x0000000000FBA00000, 0x00000000FBD500000) การสร้างมาร์ค-สเตรทพร้อมกันทั้งหมด 68288K ใช้ 67600K [0x000000000FBD500000, 0x0000001000000000, 0x0000000100000) พื้นที่ชั้นเรียนใช้ 474K, ความจุ 578K, มุ่งมั่น 640K, สงวนไว้ 1048576KException ในด้าย "Thread-0" java.lang.outofMemoryError: Java Heap Space ที่ com.ezlippi.MemoryLeak $ inner. sun.reflect.nativeConstructorAccessorimpl.newinstance0 (วิธีการดั้งเดิม) ที่ sun.reflect.nativeconstructoraccessorimpl.newinstance (แหล่งที่ไม่รู้จัก) ที่ sun.reflect.delegating java.lang.reflect.constructor.newinstance (แหล่งที่มาที่ไม่รู้จัก)) ที่ java.lang.class.newinstance (แหล่งที่ไม่รู้จัก) ที่ com.ezlippi.memoryleak $ 1.Run (MemoryLeak.java:20) ที่ java.lang.thread.
คุณจะเห็นว่า JVM ไม่มีหน่วยความจำในการสร้างวัตถุภายในใหม่เนื่องจากพื้นที่ฮีปเก็บอาร์เรย์ 1MB หลายอาร์เรย์ ที่นี่ฉันพิมพ์ฮิสโตแกรมของชั้นเรียน (รูปต่อไปนี้เป็นฉากที่มีขนาดฮีป 1024m) โดยละเว้นคลาสที่ไม่มีนัยสำคัญ จะเห็นได้ว่าอาร์เรย์ไบต์มีพื้นที่ 855 เมตรและ 814 อินสแตนซ์ของ com.ezlippi.memoryleak $ customclassloader ถูกสร้างขึ้นซึ่งโดยทั่วไปตรงกับขนาดของอาร์เรย์ไบต์:
num #instances #bytes class ชื่อ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - com.ezlippi.MemoryLeak $ customclassLoader 12: 820 53088 [ljava.util.hashtable $ entry; 15: 817 39216 java.util.hashtable 16: 915 36600 Java.lang.ref.softreference 17: 543 34752 Java.net.url 18: 697 33456 Java.nio.Heapcharbuffer 19: 817 32680 32680 java.util.treemap $ entry 21: 928 29696 java.util.hashtable $ รายการ 22: 1802 28832 java.util.hashset 23: 817 26144 Java.Security.Codesource 24: 814 26048 Java.lang
Metaspace ล้น
เพื่อที่จะทำให้ Metaspace ล้นคุณต้องลดพื้นที่ของ Metaspace เล็กน้อยและโหลดคลาสเพียงพอก่อนที่กองล้น ดังนั้นฉันจึงปรับพารามิเตอร์ JVM และปรับขนาดของอาร์เรย์ไบต์เป็น 1KB ดังแสดงด้านล่าง:
ไบต์ส่วนตัว [] kb = ไบต์ใหม่ [1024]; -xms100m -xmx100m -xx:+USEPARNEWGC -XX:+USECONCMARKSWEEPGC -XX:+PRINTGCDETAILS -XX:+PRINTHEAPATGC -XX:
จากบันทึก GC เราจะเห็นได้ว่าเมื่อ Meraspace มาถึงเกณฑ์ GC (นั่นคือขนาดของการกำหนดค่า maxmetaspacesize) fullgc จะถูกเรียกใช้:
java.lang.outofMemoryError: metaspace << ไม่มีการติดตามสแต็ก >> {heap ก่อนการเรียกใช้ GC = 20 (เต็ม 20): PAR รุ่นใหม่ทั้งหมด 30720K, ใช้ 0K [0x000000000F9C000000, 0x00000000FBD500000) 0x00000000F9C00000, 0x0000000F9C00000, 0x0000000F9C00000, 0x0000000FB6B0000) จากอวกาศ 3392K, 0% ใช้ [0x0000000000FB6B0000, 0x00000000FB6B0000, 0x0000000000 [0x0000000000FBA00000, 0x000000000FBD500000) ไปยังอวกาศ 3392K, 0% ใช้ [0x0000000000FBA00000, 0x000000010000000000) METASPACE ใช้ 1806K, ความจุ 1988K 1048576K [Full GC (Metadata GC Threshold) [CMSProcess เสร็จสิ้นด้วยรหัสออก 1จากตัวอย่างข้างต้นเราจะเห็นได้ว่าหากมีการใช้ตัวโหลดคลาสและ ThreadLocal อย่างไม่เหมาะสมมันจะนำไปสู่การรั่วไหลของหน่วยความจำ ซอร์สโค้ดที่สมบูรณ์อยู่ใน GitHub