Tuesday, October 18, 2011

Java Class loader and static variable and JVM memory management.

We always say that there is only one copy of static fields of a class in JVM memory. This is incorrect. At least, it is not precise. The precise statement should be "there is only one copy of static fields of class for each class loader in one JVM". In other words, we will have multiple copies of static fields in one JVM memory if we use different ClassLoaders to load same class into one JVM.

Different JVM vendor may have different ways to do JVM memory management. But, I think it will be common for JVM to have class initializer and instance initializer. static fields in class is initialized in class initializer. Using hotspot as example, "instanceKlass" in JVM contains static fields. To understand more about java memory management, these articles may be helpful. Presenting the Permanent Generation , OpenJDK Storage management, and JVM spec.

But, we are not guys who are able to create JVM. So, let's use our ways to determine this. What we will do here are
  1. Create our own class loader.
  2. Create a simple Java class which has static field for testing.
  3. Create two instances of our own class loader and let them to load class.
  4. Changing static field value in one loaded class instance and see if the rest one has changed field or not. 
  5. We will say that static fields defined in same class have different copies in memory if their containing class is loaded into memory by different classloader.

getting a class loader from JavaBlogging and slightly modified it as below.

package jia.blog;

import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * Our custom implementation of the ClassLoader.
 * For any of classes from "javablogging" package
 * it will use its {@link CustomClassLoader#getClass()}
 * method to load it from the specific .class file. For any
 * other class it will use the super.loadClass() method
 * from ClassLoader, which will eventually pass the
 * request to the parent.
 *
 */
public class JiaClassLoader extends ClassLoader {

    /**
     * Parent ClassLoader passed to this constructor
     * will be used if this ClassLoader can not resolve a
     * particular class.
     *
     * @param parent Parent ClassLoader
     *              (may be from getClass().getClassLoader())
     */
    public JiaClassLoader() {
        super(JiaClassLoader.class.getClassLoader());
    }

    /**
     * Loads a given class from .class file just like
     * the default ClassLoader. This method could be
     * changed to load the class over network from some
     * other server or from the database.
     *
     * @param name Full class name
     */
    private synchronized  Class getClass(String name)
        throws ClassNotFoundException {

        // is this class already loaded?
        Class cls = findLoadedClass(name);
        if (cls != null) {
            System.out.println("class " + name + "has been loaded.");
            return cls;
        } else {
            System.out.println("class " + name + " has not been loaded. Loading now.");
        }


        // We are getting a name that looks like
        // javablogging.package.ClassToLoad
        // and we have to convert it into the .class file name
        // like javablogging/package/ClassToLoad.class
        String file = name.replace('.', File.separatorChar)
            + ".class";
        byte[] b = null;
        try {
            // This loads the byte code data from the file
            b = loadClassData(file);
            // defineClass is inherited from the ClassLoader class
            // and converts the byte array into a Class
            cls = defineClass(name, b, 0, b.length);
            resolveClass(cls);
            return cls;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Every request for a class passes through this method.
     * If the requested class is in "javablogging" package,
     * it will load it using the
     * {@link CustomClassLoader#getClass()} method.
     * If not, it will use the super.loadClass() method
     * which in turn will pass the request to the parent.
     *
     * @param name
     *            Full class name
     */
    @Override
    public Class loadClass(String name)
        throws ClassNotFoundException {
        System.out.println("loading class '" + name + "'");
        if (name.startsWith("jia.")) {
            return getClass(name);
        }
        return super.loadClass(name);
    }

    /**
     * Loads a given file (presumably .class) into a byte array.
     * The file should be accessible as a resource, for example
     * it could be located on the classpath.
     *
     * @param name File name to load
     * @return Byte array read from the file
     * @throws IOException Is thrown when there
     *               was some problem reading the file
     */
    private byte[] loadClassData(String name) throws IOException {
        // Opening the file
        InputStream stream = getClass().getClassLoader()
            .getResourceAsStream(name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        // Reading the binary data
        in.readFully(buff);
        in.close();
        return buff;
    }
}

Create a very simple class having static field.

package jia.blog;

/**
 *
 * @author Yiyu Jia
 */
public class AStatic {

    public static int yiyu = -1;

    public static int getYiyu() {
        return yiyu;
    }

    public static void setYiyu(int yiyu) {
        AStatic.yiyu = yiyu;
    }

    public static void printYiyu(String instanceName){
        System.out.println("The static member yiyu's value in " 
                + instanceName + " is " + AStatic.yiyu);
    }

}

Now, we write a testing class


package jia.blog;

/**
 *
 * @author Yiyu Jia
 */

public class TestStatic  {
    public static void main(String[] args) throws Exception {
        
        JiaClassLoader loader1 = new JiaClassLoader();
        JiaClassLoader loader2 = new JiaClassLoader();
        if(loader1.equals(loader2)){
            System.out.println("two classloaders are same");
        }
        else {
            System.out.println("two classloaders are different");
        }

        Class clsA = Class.forName("jia.blog.AStatic", true, loader1);
        Class clsB = Class.forName("jia.blog.AStatic", true, loader2);

        System.out.println("jia.blog.AStatic class: " + clsA);

        Object instanceA = clsA.newInstance();
        Object instanceB = clsB.newInstance();


        if (clsA.equals(clsB)) {
            System.out.println("class loaded in different customer classloader are same");
        }
        else {
            System.out.println("class loaded in different customer classloader are different.");
        }

        clsA.getMethod("setYiyu",int.class).invoke(instanceA, 1);
        clsA.getMethod("printYiyu", String.class).invoke(instanceA, "instanceA");
        clsB.getMethod("printYiyu", String.class).invoke(instanceB, "instanceB");
       
    }
}

Running testing code, we get result as below

two classloaders are different
loading class 'jia.blog.AStatic'
class jia.blog.AStatic has not been loaded. Loading now.
loading class 'java.lang.Object'
loading class 'jia.blog.AStatic'
class jia.blog.AStatic has not been loaded. Loading now.
loading class 'java.lang.Object'
jia.blog.AStatic class: class jia.blog.AStatic
class loaded in different customer classloader are different.
loading class 'java.lang.String'
loading class 'java.lang.System'
loading class 'java.lang.StringBuilder'
loading class 'java.io.PrintStream'
The static member yiyu's value in instanceA is 1
loading class 'java.lang.String'
loading class 'java.lang.System'
loading class 'java.lang.StringBuilder'
loading class 'java.io.PrintStream'
The static member yiyu's value in instanceB is -1

Having this knowledge in mind, we can explore more about how to use static and thread safe issue. In real world, we can see customized class loader in java application server, loading class from network, Web application is a born multi thread environment. It will be necessary to have a clear view about class loader architecture, thread safe, and static members.

Also, there are more interesting discussion on this link.

4 comments:

  1. hi,
    i have a static member in class in web application.
    my question is that static member shared for all user of this web application?

    ReplyDelete
  2. Simple answer is Yes.

    To be precisely, I think you mean servlet instance when you say "web appliation". A static member of class used in Servlet class will be shared by all instances of that servlet class. Note: it is not just all user (or user's request). One servlet can be configured to have multiple instances in servlet engine.

    If you only want to share variable among different user access to one servlet instance, you do not need to declare it as static. You simplly put it as global variable in your servlet class. This is topic about thread safe servlet programming.

    You can also store your variable in ServletContext if you want to share variable among different instances of servlet and you do not mind other servlets in same contex to see it.

    Please share with me if you see any specific servlet engine performs in different way than what I state here.

    Here are more article about servlet engine class loader system:
    http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html

    http://zeroturnaround.com/blog/rjc301/

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete