網頁

2019/3/28

Tomcat 7 JDK7 java.lang.OutOfMemoryError: PermGen space

Tomcat 7執行中發生了java.lang.OutOfMemoryError: PermGen space的錯誤,也就是記憶體空間不足所導致。

解決辦法是在Tomcat安裝目錄下的bin資料夾新增一個setenv.bat檔(Windows系統)或setenv.sh(Linux系統),並在裡面設定以下內容。

JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"

上面的設定是把PermSizeMaxPermSize的值增加,例如設為-XX:PermSize=512M -XX:MaxPermSize=1024m


如果Tomcat運行的JVM版本為JDK7,則試著加入
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC

上面設定牽涉到JDK7的永久世代記憶體區塊(Permanent Generation space)的垃圾回收(GC)機制的問題(JDK 8以後PerGem被移除,改為Metaspace),而垃圾回收的發生可能會進入STW(stop-the-world) GC而影響效能,所以使用前必須對JVN的垃圾回收器及回收機制有一定的認識。

PermGen(Permanent Generation space)是JVM中配置的一塊記憶體位置,用來存放Java Class資訊及meta資料,所以當程式中有太多的Class且PermGen的大小又不夠的話就會發生java.lang.OutOfMemoryError: PermGen space的錯誤。

因此上述的方法分別為調高PermGen的上限,啟用CMS垃圾回收器對PermGen中的Class進行回收。



關於setenv.bat檔請見以下引述自Tocmat的catalina.bat中的註解

Do not set the variables in this script. Instead put them into a script setenv.bat in CATALINA_BASE/bin to keep your customizations separate.


引述Tomcat的RUNNUNG.txt說明:

(3.4) Using the "setenv" script (optional, recommended)

Apart from CATALINA_HOME and CATALINA_BASE, all environment variables can be specified in the "setenv" script. The script is placed either into CATALINA_BASE/bin or into CATALINA_HOME/bin directory and is named setenv.bat (on Windows) or setenv.sh (on *nix). The file has to be readable.

By default the setenv script file is absent. If the script file is present both in CATALINA_BASE and in CATALINA_HOME, the one in CATALINA_BASE is preferred.

...

The CATALINA_HOME and CATALINA_BASE variables cannot be configured in the setenv script, because they are used to locate that file.

All the environment variables described here and the "setenv" script are used only if you use the standard scripts to launch Tomcat. For example, if you have installed Tomcat as a service on Windows, the service wrapper launches Java directly and does not use the script files.


下面程式模擬java.lang.OutOfMemoryError: PermGen space的錯誤。

程式中有用到Javassist函式庫ClassPool類,可以透過在Maven的pom.xml加入下面依賴

<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>
import java.util.concurrent.TimeUnit;

import javassist.CannotCompileException;
import javassist.ClassPool;

public class App {
    
    /**
     * 這個static block是用來先初始化OutOfMemoryError,
     * 因為真正發生java.lang.OutOfMemoryError: PermGen space 時,
     * 已經沒有記憶體來建立OutOfMemoryError了,所以要先初始化
     */
    static {
        new OutOfMemoryError().printStackTrace();
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("=====================");
            System.out.println("建立OutOfMemoryError");
            System.out.println("=====================");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {

        System.out.println("開始動態產生Class.....\n");
        for (int i = 0; i < 100000; i++) {
            Class clazz = createClass("MyClass" + i);
            // 觀察大約有多少個Class被建立
            if(i % 50 == 0) System.out.println(clazz);
        }

        
    }

    private static Class createClass(String className) throws CannotCompileException {
        ClassPool pool = ClassPool.getDefault();
        return pool.makeClass(className).toClass();
    }
}


沒有留言:

張貼留言