Showing posts with label jvm hotswap. Show all posts
Showing posts with label jvm hotswap. Show all posts

Saturday, April 7, 2012

Class reloading-3 using Apache commons-jci-fam

Apache commons-jci-fam

This is a popular apache project based on JSR http://commons.apache.org/jci/usage.html
This project JCI is a (java compiler interface) and FAM is (FilesystemAlterationMonitor)

 The most important parts of the code that we are going to use are as below. This does the trick of class reloading.
ReloadingClassLoader classloader = new ReloadingClassLoader(this.getClass().getClassLoader());
ReloadingListener listener = new ReloadingListener();

listener.addReloadNotificationListener(classloader);

FilesystemAlterationMonitor fam = new FilesystemAlterationMonitor();
fam.addListener(directory, listener);
fam.start();



It took me a while to set up their example which I downloaded. I don't remember where I downloaded the example sources from. But running it was interesting. All the examples ran without a worry. Except for this ServerPageServlet.java which required one tricky configuration which I found out the hard way.
Well the curious and excited ones just checkout project from SVN repo http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/samjci.
Configure web.xml
 
  <servlet>
        <servlet-name>samjciservlet</servlet-name>
        <servlet-class>org.apache.commons.jci.examples.serverpages.ServerPageServlet</servlet-class>
        <init-param>serverpagesDir</param-name>
        <param-value>jsptest</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
Excerpts from ServerPageServlet.java showing some important lines that helps in compilation and class reloading.
This defines the directory for monitoring of jsp files that will need to be loaded.
final File serverpagesDir = new File(getServletContext().getRealPath("/") + getInitParameter("serverpagesDir"));
This store class actually stores the class in byte array form.

final TransactionalResourceStore store = new TransactionalResourceStore(new MemoryResourceStore()) {
    public void onStart() {..}

    /**
    * Gets called soon after onStart() by compiler. Conpiler writes the compiled class bytes in the store.
    * Soon after this onStop() gets called in which the class bytes are picked up and
    * a new class instance is created
    */
    public void write(String pResourceName, byte[] pResourceData) {
     ...
    }

    public void onStop() {
          ....
          final Class clazz = classloader.loadClass(clazzName);

          if (!HttpServlet.class.isAssignableFrom(clazz)) {
                log(clazzName + " is not a servlet");
                continue;
          }
          // create new instance of jsp page
          final HttpServlet servlet = (HttpServlet) clazz.newInstance();
          ....
    }
    
}


Although compilation is out of scope for this blog, but just for the sake of completeness, I am writing about it here.
jspListener = new CompilingListener(new JavaCompilerFactory().createCompiler("eclipse"), store) {

            private final JspGenerator transformer = new JspGenerator();
            private final Map sources = new HashMap();
            private final Set resourceToCompile = new HashSet();

            public void onStart(FilesystemAlterationObserver pObserver) {
                super.onStart(pObserver);

                resourceToCompile.clear();
            }


            public void onFileChange(File pFile) {
                if (pFile.getName().endsWith(".ss")) {
                    final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java";

                    log("Updating " + resourceName);
                    byte ar[] = transformer.generateJavaSource(resourceName, pFile);
                    sources.put(resourceName, ar);
                    System.out.println(new String(ar));
                    resourceToCompile.add(resourceName);
                }
                super.onFileChange(pFile);
            }


            public void onFileCreate(File pFile) {
                if (pFile.getName().endsWith(".ss")) {
                    final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java";

                    log("Creating " + resourceName);

                    sources.put(resourceName, transformer.generateJavaSource(resourceName, pFile));

                    resourceToCompile.add(resourceName);
                }
                super.onFileCreate(pFile);
            }


            public String[] getResourcesToCompile(FilesystemAlterationObserver pObserver) {
                // we only want to compile the jsp pages
                final String[] resourceNames = new String[resourceToCompile.size()];
                resourceToCompile.toArray(resourceNames);
                return resourceNames;
            }


            public ResourceReader getReader( final FilesystemAlterationObserver pObserver ) {
                return new JspReader(sources, super.getReader(pObserver));
            }
        };
        jspListener.addReloadNotificationListener(classloader);
        
        fam = new FilesystemAlterationMonitor();
        fam.addListener(serverpagesDir, jspListener);
        fam.setInterval(3000);
        fam.start();
  

Whenever a new *.ss file gets created OnFileChange() or onFileCreate() gets called, which writes the resource names in resourceToCompile list. Super class of CompilationListener is ReloadingListener. This is part of ReloadingListener.java

public void onStop( final FilesystemAlterationObserver pObserver ) {
        
        
        if (store instanceof Transactional) {
            ((Transactional)store).onStart();
        }

        final boolean reload = isReloadRequired(pObserver);

        if (store instanceof Transactional) {
            ((Transactional)store).onStop();
        }
        
        if (reload) {
            notifyReloadNotificationListeners();
        }
        
        super.onStop(pObserver);
    }

The onStart() method actually just clears various flags of compiler and flags of stores. The real execution starts when onStop() gets called. onStop() method of ReloadingListener has the high level steps of compilation and followed by reload of classes.
It has one method final boolean reload = isReloadRequired(pObserver); which logs the changes resources
  log.debug("created:" + created.size() + " changed:" + changed.size() + " deleted:" + deleted.size() + " resources");, then it calls
  final String[] resourcesToCompile = getResourcesToCompile(pObserver);.
 After this actual compilation happens for each source that got changed
  final CompilationResult result = compiler.compile(resourcesToCompile, reader, transactionalStore); 
At the end it logs the errors and warnings of compilation
  log.debug(errors.length + " errors, " + warnings.length + " warnings"); Once back from isReloadingRequired(), it is sure that class compilation is complete and class bytes are available in the store. The only thing remains is the read those bytes and load them into a class. ....
The following log gets written automatically when the ss.ss file is edited using a notepad. Just hit Ctrl+S after modification and see the result.


created:0 changed:0 deleted:0 resources
check signal
check signal
onStart F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onStart F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onFileChange F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest\ss.ss
onFileChange F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest\ss.ss
Apr 08, 2012 1:33:14 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Updating ss.java
import java.io.PrintWriter;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
public class ss extends HttpServlet {
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final PrintWriter out = response.getWriter();
System.out.println("Custom jsp servlet:"+this.getClass().getName());    out.write("this is ss changed ");
    out.close();
    out.flush();
  }
}

onStop F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onStop F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
created:0 changed:1 deleted:0 resources
created:0 changed:1 deleted:0 resources
1 classes to compile
1 classes to compile
className=ss
className=ss
fileName=ss.java
fileName=ss.java
typeName=ss
typeName=ss
compiling ss.java
...
writing resource ss.class(1314)
0 errors, 1 warnings
0 errors, 1 warnings
reading resource ss.class
reading resource ss.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:

reading resource ss.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] found class: ss (1314 bytes)
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] found class: ss (1314 bytes)
reading resource javax/servlet/http/HttpServlet.class
reading resource javax/servlet/http/HttpServlet.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] loaded from store: ss
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] loaded from store: ss
reading resource java/lang/Object.class
reading resource java/lang/Object.class
Apr 08, 2012 1:37:50 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Activating new map of servlets {ss=ss@cb70f0}
...
...
If the newly created and loaded servlet is loaded then the following log comes. The url invoked is http://localhost:6060/samjci/samjci/ss the new servlet getscalled. This line comes from the System.out.println() written dynamically in the servlet "Custom jsp servlet:ss".
request.getpathInfo:/ss
Custom jsp servlet:ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Request /samjci/samjci/ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Checking for serverpage ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Delegating request to ss

Now everything got cleared from the logs. Hope this inspires new ideas. You can find the project in google code SVN repository here http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/samjci

Class Reloading-2 using AgentSmith

Class Reloading using AgentSmith

AgentSmith uses jvm HotSwap method for class reloading. The project is simple and consists of a few classes. The application works by java instrumentation API. We will break down the working of each of the components in it and see how it all fits together to enable automatic class reloading.


Setup:

This is what worked for me. SVN checkout from the below repository. https://svn.java.net/svn/agentsmith~svn/tags/smith-1.0
Compile using
ant dist

This will create smith-1.0.jar file. This jar file needs to be used to instrument the jvm of tomcat which will be running the web application. The following code does exactly that:
Edit catalina.sh or catalina.bat to add the following to JAVA_OPTS variables.

-javaagent:E:/samarjit/tomcat-6.0.35/lib/smith-1.0.jar=classes=E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/classes,jars=E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/lib,period=1000,loglevel=INFO



If you start the tomcat now it will show the debug messages, which will show the path AgentSmith is looking for class changes E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/classes and library changes.in E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/lib.

To test if it really works deploy a webapplication in tomcat outside eclipse. Now recompile some class file after modifying some contents of a method in it. Then invoke that servlet. You can instantly see the effect.

Some excerpts form Smith.java

public static void premain(String agentArgs, Instrumentation inst) {
  System.out.println("Premain()");
  initialize(agentArgs, inst);
 }


The portion that starts off the monitor is here:

/**
  * Creates and starts a new Smith agent. Please note that periods smaller than
  * 500 (milliseconds) won't be considered.
  * 
  * @param inst
  *          the instrumentation implementation
  * @param args
  *          the {@link SmithArgs} instance
  */
 public Smith(Instrumentation inst, SmithArgs args) {
  this.inst = inst;
  this.classFolder = args.getClassFolder();
  this.jarFolder = args.getJarFolder();
  int monitorPeriod = MONITOR_PERIOD_MIN_VALUE;
  if (args.getPeriod() > monitorPeriod) {
   monitorPeriod = args.getPeriod();
  }
  log.setUseParentHandlers(false);
  log.setLevel(args.getLogLevel());
  ConsoleHandler consoleHandler = new ConsoleHandler();
  System.out.println("Log level:"+args.getLogLevel());
  consoleHandler.setLevel(args.getLogLevel());
  log.addHandler(consoleHandler);

  service = Executors.newScheduledThreadPool(2);

  FileMonitor fileMonitor = new FileMonitor(classFolder, "class");
  fileMonitor.addModifiedListener(this);
  service.scheduleWithFixedDelay(fileMonitor, 0, monitorPeriod,
    TimeUnit.MILLISECONDS);

  if (jarFolder != null) {
   JarMonitor jarMonitor = new JarMonitor(jarFolder);
   jarMonitor.addJarModifiedListener(this);
   service.scheduleWithFixedDelay(jarMonitor, 0,
                                        monitorPeriod,
     TimeUnit.MILLISECONDS);
  }

  log.info("Smith: watching class folder: " + classFolder);
  log.info("Smith: watching jars folder: " + jarFolder);
  log.info("Smith: period between checks (ms): " + monitorPeriod);
  log.info("Smith: log level: " + log.getLevel());
 }


The most important that does the HotSwap is here: There is a call to inst.redefineClasses(...) which does the trick.

/**
  * Redefines the specified class
  * 
  * @param className
  *          the class name to redefine
  * @param event
  *         the event which contains the info to access the modified class
  *          files
  * @throws IOException
  *           if the inputstream is someway unreadable
  * @throws ClassNotFoundException
  *           if the class name cannot be found
  * @throws UnmodifiableClassException
  *           if the class is unmodifiable
  */
 protected void redefineClass(String className, EventObject event) {
  Class[] loadedClasses = inst.getAllLoadedClasses();
  for (Class clazz : loadedClasses) {
   if (clazz.getName().equals(className)) {
    try {
       ClassDefinition definition = 
                            new ClassDefinition(clazz, getByteArrayOutOf(event));
       inst.redefineClasses( new ClassDefinition[] { definition });
       if (log.isLoggable(Level.FINE)) {
         log.log(Level.FINE, "Redefined: "                                                + clazz.getName());
       }
      } catch (Exception e) {
       log.log(Level.SEVERE, "error", e);
      }
   }
  }
 }


Once this class redefinition is done, the updated class automatically gets reflected when invoked. Till here all the changes done are framework agnostic. But if you are using a framework like struts, you will probably need to update the framework related configurations.

Thursday, April 5, 2012

Class Reloading-1 Webapp in java

Imagine writing java code hit Ctrl+S and then click refresh on browser, voila the changed java code actually works!! Or in server environment you made a slight change to a class file. Wish it were loaded without require of a server restart. Or you changed some resource properties file, did you imagine that your changed resource properties file will also require a server restart. It would be nice if all web applications worked this way out of box.

Well, there is a way to make web applications reload each time it detects a change in resources. There are two ways an app server can help in reloading changes.
  • Auto-reload (Reloads the changed files and classes and then re-initialize the application, which may take another 30sec to 1 min. All the servlets that load on startup are restarted, caches cleared and reloaded. This is almost takes the same time as restarting tomcat.)
  • Hot Swap of classes - Provided by JDK class redefining without class reloading, but this works only in debug mode Java Platform Debugger Architecture (JPDA) . This can be done in tomcat running inside eclipse or other IDE. This requires tomcat to run in debug mode in eclipse). This article http://blog.redfin.com/devblog/2009/09/how_to_set_up_hot_code_replacement_with_tomcat_and_eclipse.html describes the setup of doing this with eclipse and tomcat.
  • FastSwap (websphere) only available in WebLogic (http://docs.oracle.com/cd/E15051_01/wls/docs103/deployment/deployunits.html ). This is better than Hot Swap, it is not only limited to changes in the method body. It also does not user a new classloader. I am not sure how it does the trick, I have never used it also, but wish it was there in tomcat too :( .
But there are ways to reload only specific parts of an application in tomcat without disrupting the other applications. For the curious ones I would first list down some some frameworks and tools that does class reloading.
Tools that do class reloading:



Frameworks which helps or does class reloading:Grails, play framework, tapestry, spring scripted beans, struts2 by using struts-spring-plugin, OSGi, struts2 OSGi plugin.


Non Java frameworks that does class reloading: Ruby on Rails, PHP.

Other ways to achieve class reloading:JSP - Create the full application in JSP. Of course some would say mixing views with models with controller is not a good idea. But this problem has been solved in PHP world many times. But frankly speaking if JSPs can be compiled and loaded by tomcat, why cannot tomcat load other classes? I greatly feel app servers should provide this and hope it would not be too difficult.


Oracle's PL/SQL- Make the front end generic, everything in configuration files, and push all the logic including gui validation to PL/SQL. Believe me this is possible and I have seen it. PL/SQLs can be compiled anytime.


Class Reloading Methods

(disclaimer: I am not an expert in jvm, neither I am expert in class loaders. So whatever I am going to say is based on my research of few other blogs, inspection of code struts spring plugin which does class reloading.)
Most I have seen two ways of class reloading, which are using java (a) HotSwap or (b) java ClassLoaders.
The main problems that makes class reloading hard in jvm level are bytecode optimisers and jvm JIT compiler and multi generation garbage collection. I will not go too much deep into jvm internals but I would certainly point to a blog which explains about class reloading in webapps, OSGi in general http://zeroturnaround.com/blog/rjc301/. This is another blog which discusses about why jvm hotswap is limited only to method bodies modification http://zeroturnaround.com/blog/reloading_java_classes_401_hotswap_jrebel/, this blog has a really inspiring video "A video presentation 'Do you really get class loaders' by Jevgeni Kabanov followed by a nice example of the very basic class reloading in action.

While implementing the various class reloading methods I faced some issues so I intend to show the complete setup here so that you can get a feel about it.


2. Fakereplace- Using fakereplace in a webapp is really simple. Just add the following to JAVA_OPTS in catalina.sh or catalina.bat if you are running tomcat. -javaagent:C:/Users/Samarjit/Desktop/fakereplace-parent-0.9-bin/fakereplace/fakereplace.jar -Dorg.fakereplace.packages=view
3. Simple Class Reloading - as explained by Jevgeni Kabanov in the above blog. I just created a small project in google code, which you can directly checkout and see it in action. URL: http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/ClassLoaderTest
4. Struts2 Spring plugin with class reloading- This is simple and words well out of box as per instructions. I figured out that it is using apache-commons-jci-fam. But they have integrated with springs so tightly that you will not get who is doing the trick of class reloading. After digging deep into it I decided to give apache-commons-jci-fam a try without even a sprinkle of spring. That makes my next project
5. Apache-commons-fam-jci - They have examples but those requires a bit of configuration to get them running. So I created a project that can be just checked out and run. They simulate JSP container. It reads JSP from plain text HTML then created a java source of it and compiles and finally loads the class, all of this happens on the fly and you do not need to restart your servlet container. If they can work with JSP we can apply the same to normal java classes and servlets too. URL: http://classreloadingwebapp.blogspot.com/2012/04/class-reloading-using-apache-commons.html
5. Struts2 OSGi plugin - Again this plugin seems not to be used by anyone. As there are hardly any documentation, also the plugins page does not show how to implement it with and without springs. Somehow its mixed up in the document. So I created a project completely without springs. Where you can get a feel of just the OSGi plugin. URL: http://classreloadingwebapp.blogspot.com/2012/04/class-reloading-4-using-struts2-osgi.html