package com.stc.test;
import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Log at a custom level
Level customLevel = new Level("OOPS", 555) {};
Logger.getLogger("test").log(customLevel, "doGet() called");
}
}
ลอง redeploy ตัวอย่างข้างบน บน application server ซ้ำๆซักพักนึงดู พนันได้เลยว่าจะพบกับ java.lang.OutOfMemoryError: PermGen space error ในเวลาไม่นาน
ถ้าคุณอยากทำความเข้าใจว่ามันเกิดอะไรขึ้นลองอ่านหัวข้อถัดไปดู
อะไรคือสาเหตุของปัญหา
Application server หลายๆตัว อย่างเช่น Glassfish จะอนุญาติให้คุณ เขียนแอพลิเคชั่น (.ear, .war, etc) และ ติดตั้งมันร่วมกับ แอพลิเคชั่นอื่นๆ บนตัวมันเอง
ทีนี้เวลาที่คุณต้องปรับแก้ program ของคุณ คุณก็คิดแค่ว่าแก้ source code , compile แล้วก็ redeploy ขึ้นไปบน application server ทุกอย่างก็น่าจะจบ โดยที่มันจะไม่มีผลข้างเคียงกับ application อื่นๆที่กำลังทำงานอยู่บน application server ได้เลย และไม่จำเป็นต้อง restart application server ด้วย
ครับสิ่งที่คุณคิดมันถูกต้อง! กลไกนี้ทำงานได้ดีและไม่มีปัญหาอะไรเลยบน Glassfish และหรือ application server อื่นๆ (เช่น Java CAPS Integration Server)
ซึ่งวิธีการนี้จะได้ผลเป็นปกติดีก็ต่อเมื่อ แอพลเคชั่นแต่ละตัว ถูก load ด้วย classloader ของตัวมันเอง
โดย classloader จะเป็น class พิเศษที่ทำหน้าที่ load ไฟล์ .class ทั้งหลายมาจาก jar file
และเมื่อคุณสั่ง undeploy ตัว class loader จะถูกทิ้ง และ class ทั้งหมดที่ถูก class loader อ่านเข้ามาก็จะถูกทิ้งไปด้วย ซึ่งควรส่งผลให้ garbage colloector (gc) ทำการเก็บกวาด
มันทิ้งไปเพื่อคืน memory ในเวลาต่อมาซึ่งอาจจะช้าหรือเร็วก็ขึ้นอยู่กัย gc เอง
แต่บางทีมันก็ไม่เป็นเช่นนั้น ด้วยว่ามีบางสิ่งบางอย่าง ยังคงยึดหรือ มีการอ้างอิงไปหา memory ของ class loader อยู่ ซึ่งนั่นจะเป็นการขัดขวางการเก็บกวาดของ gc และนั่นเอง
ที่เป็นสาเหตุของ [java.lang.OutOfMemoryError: PermGen space exception]
PermGen space
อะไรคือ PermGen space ?
ก่อนอื่นต้องเข้าใจก่อนว่าหน่วยความจำใน Virtual Machine จะถูกแบ่งเป็นขอบเขตต่างๆ ซึ่งหนึ่งในนั้นชื่อ PermGen โดยมันเป็นพื้นที่สำหรับ load พวก class file (ยังมีอย่างอื่นด้วย)
ขนาดของ PermGen จะถูกกำหนดไว้คงที่ตั้งแต่เริ่ม start ตัว VM และมันจะมีขนาดเท่าเดิมตลอดเวลาหลังจากที่ VM เริ่มทำงานแล้ว คุณสามารถกำหนดขนาดของ PermGen ได้ด้วย
Parameter ของ VM ดังนี้ -XX:MaxPermSize โดยค่า default ของมันจะเป็น 64Mb สำหรับ VM ของ Sun
ตราบใดที่ปัญหายังเกี่ยวข้องกับการให้ GC เก็บกวาด class และคุณยังจำเป็นต้อง Load Class เพิ่มอยู่ VM ก็จะใช้พื้นที่นี้จนเต็มอยู่ดี แม้ว่าคุณจะเพิ่มขนาดของ Heap Memory ไปมาก
แค่ไหนก็ตาม การใช้ VM Parameter -Xmx ไม่ได้ช่วยแก้ปัญหานี้เลย เพราะมันกำหนดขนาดสูงสุดของ Heap ไม่ใช่ขนาดของ PermGen
Garbage collecting and classloaders
เมื่อคุณพยายามเขียน code แย่ๆแบบนี้
private void x1() {
for (;;) {
List c = new ArrayList();
}
}
มันเป็นการพยายามจองพื้นที่ใน memory ให้ object "c" อย่างต่อเนื่อง แต่ยังไงโปรแกรมก็ยังไม่ out of memory เพราะพื้นที่ memory ของ object "c" จะถูกจัดเป็น garbage
เพื่อคืนค่า memory ทำให้คุณยังสามารถจองพื้นที่ให้ object ใหม่ได้เรื่อยๆ โดยพื้นที่ memory ของ object จะถูกจัดเป็น garbage ก็ต่อเมื่อ "พื้นที่" นั้นมีสภาพเป็น unreachable
คือไม่มีทางที่จะ access หรือ อ้างถึงพื้นที่นั้น ได้จากที่ไหนในโปรแกรมได้อีกแล้ว
ลองมาดูเป็นรูปภาพของ memory จากตัวอย่าง servlet กัน
package com.stc.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class Servlet1 extends HttpServlet {
private static final String STATICNAME = "Simple";
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
เมื่อ Servlet นี้ถูก Load แล้ว Object ต่างๆจะอยู่ใน Memory ดังรูป (เราจำกัดเฉพาะส่วนที่อยู่ในประเด็นของเราเท่านั้น)
ส่วนสีเหลืองคือ instance ที่ถูก Load โดย app class loader และจากภาพเราจะเห็นว่า container จะมีการอ้างอิงไปหา application classloader ซึ่งมันถูกสร้างมาเฉพาะ application นี้เท่านั้น นอกจากนี้ container ยังอ้างอิงไปหา Servlet Instance เพื่อให้มันสามารถเรียกเมธอด doGet() ใน servlet ให้ทำงานได้ เมื่อมี http request เกิดขึ้น ส่วน STATICNAME จะถูกอ้างอิงได้จาก Servlet Class Object เพียงตัวเดียวเท่านั้น (Servlet1 instance จะ access ไปหา STATICNAME ได้โดยผ่าน Servlet Class Object)
:ข้อสังเกตุ:
// 1. Servlet Instance จะมีการอ้างอิงถึง Class ของมันเอง (Class Object)
// ซึ่งทุกๆ Object ก็เป็นแบบนี้เสมอ เช่น
Class clsObj = obj.class;
// 2. Class Object จะมีอ้างอิงถึง ClassLoader ที่ Load มันขึ้นมาเสมอ เช่น
ClassLoader clsLdr = clsObj.getClassLoader();
// 3. ClassLoader จะมีอ้างอิงถึง ClassObject ที่มัน Load ขึ้นมาเสมอ เช่น
Class clsObj = clsLdr.loadClass("ClassName");
จากภาพเมื่อเรา Undeploy แอพลิเคชั่น ทำให้ Container ไม่อ้างอิงถึง instance ของ Servlet1 และ AppClassloader อีกต่อไป และทำให้มี object ที่ไม่สามารถอ้างอิงถึงใน VM ได้อีก(unreachable objects คือส่วนที่เป็นสีจางๆ) และพวกมันจะถูกจัดเป็นขยะ (garbage) ทำให้ gc สามารถคืน memory กลับมาให้เราได้ คราวนี้มาดูกันว่าจะเกิดอะไรขึ้นถ้าเรานำตัวอย่างเดิมมาปรับให้ใช้คลาส Level ในโค๊ดด้วย
package com.stc.test;
import java.io.*;
import java.net.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LeakServlet extends HttpServlet {
private static final String STATICNAME = "This leaks!";
private static final Level CUSTOMLEVEL = new Level("test", 550) { // anon class!
};
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Logger.getLogger("test").log(CUSTOMLEVEL, "doGet called");
}
}
* object class ชื่อ CUSTOMLEVEL เป็น anonymous class และเนื่องจาก Constructor ของ Level เป็นชนิด protected ทำให้เราจำเป็นต้องประกาศแบบนี้
เรามาดูแผนภาพของ memory ที่จะเกิดขึ้นกัน
จากภาพคุณคงจะเห็นบางสิ่งที่คุณคาดไม่ถึง คลาส Level จะมี static member ของ Level Instance ทุกๆตัวที่สร้างขึ้นมา(รวมถึง CUSTOMLEVEL ของคุณด้วย) ซึ่งนี่เป็น constructor ของคลาส Level ที่อยู่ใน JDK
protected Level(String name, int value) {
this.name = name;
this.value = value;
synchronized (Level.class) {
known.add(this);
}
}
ใน code ข้างต้น "known" คือ static ArrayList ในคลาส Level ซึ่งถูก implement เอาไว้แบบนี้
ทีนี้เรามาดูกันต่อว่าจะเกิดอะไรขึ้นเมื่อเรา undeploy
จะมีเฉาะ Instance ของ LeakServlet เท่านั้นที่ถูกจัดเป็น Garbage เพราะยังคงมีการอ้าถึง Object CUSTOMLEVEL จากภายนอกของ AppClassloader อยู่ สิ่งที่สำคัญของเรื่องนี้ก็คือ เมื่อใดก็ตามที่มี Object นอก AppClassloader ยังคงอ้างถึง Object ซึ่งถูก Load ด้วย AppClassloader อยู่ Object ที่เราสนใจนั้นก็จะไม่มีทางถูกจัดเป็น Garbage เลยครับ
ที่มา : https://kiwidaddy.wordpress.com




ไม่มีความคิดเห็น:
แสดงความคิดเห็น