Showing posts with label jconsole. Show all posts
Showing posts with label jconsole. Show all posts

Tuesday, December 23, 2008

OutOfMemoryError: PermGen space

Recently, I found a problem with our system in which it ran out of PermGen space under heavy usage. The exception from the logs is:
java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)

PermGen Space
The memory a jvm uses is split up into three "generations": young (eden), tenured, and permanent. PermGen refers to the permanent generation which holds meta-data describing user classes and "interned" strings. If you have a large code base and intern lots of strings, more permgen space is used.

Interning Strings
You can intern strings using String.intern() which, from the javadocs:

Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
Interned strings are stored in permgen space, so if you interned all your strings, you would eventually run out of memory. In our application, I found that we were interning string representations of timestamps which wasn't necessary! You should only intern strings which are used frequently.

Increasing PermGen Space
The maximum size is typically 64MB. You can double it by starting your JVM with the following system properties:
-XX:MaxPermSize=128m -XX:PermSize=128m

Using JConsole to Monitor Memory
An easy way to see how much memory your JVM is using is to use jconsole. First, you need to enable monitoring on your JVM by starting it up with the following system properties:
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=<port>
Next start jconsole and point it at this JVM using the host and port (or pid). Select the Memory tab and then choose the Memory Pool "PS Perm Gen" chart. You should now see the usage of your PermGen space as shown in the screenshot below:

Friday, June 13, 2008

Change Logging Levels using JMX [Howto]

This handy example shows you how you can use Java Management Extensions (JMX) to change your application's logging level.

Step 1: Create your management interface which consists of all the attributes which can be read or set and all the operations that can be invoked. In this case, we want to change the logging level of our application.

public interface MyAppMBean{
    public void setLoggingLevel(String level) ;
    public String getLoggingLevel() ;
}
Step 2: Create a class which implements the MBean interface
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.sun.jdmk.comm.HtmlAdaptorServer;

public class MyApp implements MyAppMBean{

    private static Logger logger = Logger.getLogger(MyApp.class);

    public void go() throws Exception{
        while(true){
            logger.debug("DEBUG") ;
            logger.info("INFO") ;
            Thread.sleep(2000);
        }
    }

    public void setLoggingLevel(String level){
        logger.info("Setting logging level to: " + level);
        Level newLevel = Level.toLevel(level, Level.INFO);
        Logger.getRootLogger().setLevel(newLevel);
    }

    public String getLoggingLevel(){
        return Logger.getRootLogger().getLevel().toString() ;
    }
}
Step 3: Register the MBean with the MBeanServer. Also register an HTMLAdaptorServer which allows us to manage an MBeanServer through a web browser.
public static void main(String[] args) throws Exception{
    MyApp app = new MyApp() ;
    ObjectName objName = new ObjectName("MyApp:name=MyApp");
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    server.registerMBean(app, objName);

    int portNumber=9393;
    ObjectName htmlName = new ObjectName(
       "MyApp:name=MyAppHtmlAdaptor,port="+portNumber) ;
    HtmlAdaptorServer html = new HtmlAdaptorServer(portNumber);
    html.setPort(portNumber);
    server.registerMBean(html, htmlName);
    html.start();
    app.go();
}
Step 4: Compile. Make sure you have log4j, jmxtools and a log4j properties file in your classpath

Step 6: Run MyApp. You need to add the following JVM properties:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
Step 5: Open jconsole
  • Click on MyApp and press Connect
  • Go to the MBeans tab and choose MyApp from the tree on the left
  • You can then change your Logging Level while the application is running!
Step 6: Open the web console
  • Go to http://localhost:9393
  • Click on name=MyApp
  • Change the LoggingLevel!