Search code examples
javamemoryjar

In memory jar / class file execution


I have a jar file (say app.jar) in a certain location available as a file/stream from an http server. The problem is that app.jar is itself a frequently updated jar file being updated regularly at the server. I have another jar file (say load.jar) which is simply downloadable by any user. At runtime, the user runs the load.jar, which is required to load the app.jar in a separate process keeping the app.jar in memory (no cacheing to disk), then execute the app.jar and terminate itself (load.jar). Is it possible to be done? Any help is highly appreciated. Thanks in advance. Regards, KT

---------------------------Update.

Hi,

Thanks all for the reply. However, I guess I have not given a complete picture. An image link text is attached depicting the scenario. 4 jars (the actual common executors) are hosted on a central server. These are updated frequently (probably 3-4 times a day initially down to once a day eventually). A local server at one of the member is hosted which is initialised. The launcher on the local server downloads all the 4 binaries and keeps it in memory - no cacheing to disk. These jar files are self-sufficient and are not dependent on any library on the "Local Server" - they in fact invoke jars from the "Local Server". The "client" jar eventually is hosted with the "Local Server" and forwarded to its clients on demand via webapp by the "Local Server". Also, the Launcher needs to exit by invoking the downloaded Main jar from the server in a separate JVM.

Regards, KT


Solution

  • OK. Before I put the link to the szegedi article into my previous answer (honest), I prototyped a jar-launcher that could handle URLs. Like the author said, it wasn't hard, probably isn't complete. Attached below. I think this is 1/3 of what you need. Your user says (something like):

    java -jar load.jar http://localhost:8080/app.jar
    

    load.jar has two roles: (1) invoke JarLauncher (below) as its main class (2) serve app.jar on a localhost port (before invoking the JarLauncher). load.jar therefore reads its arguments to figure out which app to run.

    Finally (the hard bit): you have to make URLClassLoader not hit the temporary disk. As the szegedi article said, that isn't easy. Alternatively, you can write your own network-aware classloader that doesn't disk-cache, like my first suggestion loading the URL into memory as a byte-stream from a URL connection, decoding it as a JarInputStream, and satisfying calls to loadClass/findResource from that in-memory stream.

    You have quite a lot of work ahead of you. Good luck.

    Apologies for the size of the license text, but it lets you do what you like with the code as long as you don't blame me (BSD).

    --simon.


    /*
     * One-JAR(TM) (http://www.simontuffs.com/one-jar).  Copyright (c) 2004-2010, 
     * P. Simon Tuffs (simon@simontuffs.com).   All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     * Redistributions of source code must retain the above copyright notice, this
     * list of conditions and the following disclaimer.
     *
     * Redistributions in binary form must reproduce the above copyright notice,
     * this list of conditions and the following disclaimer in the documentation
     * and/or other materials provided with the distribution.  
     *
     * Neither the name of P. Simon Tuffs, nor the names of any contributors, 
     * nor the name One-JAR may be used to endorse or promote products derived 
     * from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     *
     * Including this file inside the built One-JAR file conforms with these terms.
     */
    
    import java.lang.reflect.Method;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.jar.JarInputStream;
    
    /**
     * Programmatic equivalent of what happens when you say "java -jar <jar-file.jar>".
     * A better solution to debugging/running Jar files inside an IDE.
     * @author simon
     *
     */
    public class JarLauncher {
    
        public static URL getURL(String string) throws MalformedURLException {
            try {
                return new URL(string);
            } catch (MalformedURLException x) {
                return new URL("file:" + string);
            }
        }
        
        public static void main(String args[]) throws Exception {
            if (args.length < 1) {
                System.out.println("Usage: java [-Dkey=value...] JarLauncher <jar-file.jar>");
                System.exit(1);
            }
            String jar = args[0];
            args = Arrays.copyOfRange(args, 1, args.length);
            List<URL> urls = new ArrayList&lt;URL>();
            // Main jar on the URL path.
            // Dig out the main class.
            urls.add(getURL(jar));
            URL jarurl = urls.get(0);
            JarInputStream jis = new JarInputStream(jarurl.openStream());
            String main = jis.getManifest().getMainAttributes().getValue("Main-Class");
            // OK to split on space, because embedded space is %20
            String classpaths[] = jis.getManifest().getMainAttributes().getValue("Class-Path").split(" ");
            for (String classpath: classpaths) {
                urls.add(getURL(classpath));
            }
            URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[0]));
            Class<?> cls = loader.loadClass(main);
            Thread.currentThread().setContextClassLoader(loader);
            Method m = cls.getMethod("main", new Class[]{new String[0].getClass()});
            m.invoke(null, new Object[]{args});
            
        }
        
    }