Is the Java classpath final after JVM startup?

后端 未结 7 582
灰色年华
灰色年华 2021-02-02 11:11

I have read a lot about the Java class loading process lately. Often I came across texts that claimed that it is not possible to add classes to the classpath during runtime and

相关标签:
7条回答
  • 2021-02-02 11:28

    @Jen I don't think your experiment can prove your theory, because it is more about object instantiation: your printline happens when an object of this class is instantiated, but not necessarily telling that JVM knows your code, the class, just when it is instantiating.

    My opinion is that all Java classes are loaded when JVM is up, and it is possible to plug in more classes into JVM while it is running: this technique is called: Hot deployment.

    0 讨论(0)
  • 2021-02-02 11:31

    So shouldn't it be possible to add a JAR or *.class file to the classpath after the JVM started ...

    You add jars and directories to the classpath, not classes. The classes are in either the directory, or the jar.

    And if not, does that mean that the classpath is searched on JVM startup and all fully qualified names of the found classes are cached in an internal "list"?

    That could be easily tested: Set the classpath, start your program, move a new class into the CP, call 'Class.forName ("NewClass") from your program. Does it find the new class?

    0 讨论(0)
  • 2021-02-02 11:33

    I can only comment from what I remember of my own experience of hacking a non-sun JVM ten years ago, but it did scan the entire classpath on startup, as an efficiency measure. The names of all classes found were added to an internal hashtable along with their locations (directory or zip/jar file). However, that was ten years ago and I can't help but wonder whether this would still be a reasonable thing to do in most settings considering how disk and memory architectures have evolved.

    0 讨论(0)
  • Bottom line: it is possible to add entries to the system classpath at runtime, and is shown how. This, however, has irreversible side-effects and relies on Sun JVM implementation details.


    Class path is final, in the most literal sense:

    The system class loader (the one that loads from the main class path) is sun.misc.Launcher$AppClassLoader in rt.jar.

    rt.jar:sun/misc/Launcher.class (sources are generated with Java Decompiler):

    public class Launcher
    {
     <...>
     static class AppClassLoader
        extends URLClassLoader
      {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
    <...>
    

    rt.jar:sun/misc/URLClassLoader.class:

    protected Class<?> findClass(final String paramString)
        throws ClassNotFoundException
      {
        <...>
              String str = paramString.replace('.', '/').concat(".class");
              Resource localResource = URLClassLoader.this.ucp.getResource(str, false);
      <...>
    

    But, even if the field is final, this doesn't mean we can't mutate the object itself if we somehow get access to it. The field is without an access modifier - which means, we can access it if only we make the call from the same package. (the following is IPython with JPype; the commands are readable enough to easily derive their Java counterparts)

    #jpype doesn't automatically add top-level packages except `java' and `javax'
    In [28]: jpype.sun=jpype._jpackage.JPackage("sun")
    
    In [32]: jpype.sun.misc.Launcher
    Out[32]: jpype._jclass.sun.misc.Launcher
    
    In [35]: jpype.sun.misc.Launcher.getLauncher().getClassLoader()
    Out[35]: <jpype._jclass.sun.misc.Launcher$AppClassLoader at 0x19e23b0>    
    
    In [36]: acl=_
    
    In [37]: acl.ucp
    Out[37]: <jpype._jclass.sun.misc.URLClassPath at 0x19e2c90>
    
    In [48]: [u.toString() for u in acl.ucp.getURLs()]
    Out[48]: [u'file:/C:/Documents%20and%20Settings/User/']
    

    Now, URLClassPath has a public addURL method. Let's try it out and see what happens:

    #normally, this is done with Launcher.getFileURL but we can't call it directly
    #public static URLClassPath.pathToURLs also does the same, but it returns an array
    In [72]: jpype.sun.net.www.ParseUtil.fileToEncodedURL(
                 jpype.java.io.File(r"c:\Ivan\downloads\dom4j-2.0.0-RC1.jar")
                 .getCanonicalFile())
    Out[72]: <jpype._jclass.java.net.URL at 0x1a04b50>
    
    In [73]: _.toString()
    Out[73]: u'file:/C:/Ivan/downloads/dom4j-2.0.0-RC1.jar'
    
    In [74]: acl.ucp.addURL(_72)
    
    In [75]: [u.toString() for u in acl.ucp.getURLs()]
    Out[75]:
    [u'file:/C:/Documents%20and%20Settings/User/',
     u'file:/C:/Ivan/downloads/dom4j-2.0.0-RC1.jar']
    

    Now, let's try to load some class from the .jar:

    In [78]: jpype.org=jpype._jpackage.JPackage("org")
    
    In [79]: jpype.org.dom4j.Entity
    Out[79]: jpype._jclass.org.dom4j.Entity 
    

    Success!

    This will probably fail from a sandbox or such where there are custom class loaders or security settings in the way (AppClassLoader.loadClass does security checks before calling super).

    Further code inspection shows that addURL also disables the URLClassPath's lookup cache (implemented in a few native methods), and this is irreversible. Initially, the lookupCacheEnabled flag is set to the value of the sun.cds.enableSharedLookupCache system property.

    The interface provides no way to edit the entries. URLs are added to URLClassPath's private ArrayList path and Stack urls. urls is accessible, but it turns out, it only holds entries temporarily, before it's attempted to load from it, at which point the information moves to HashMap lmap and ArrayList loaders. getURLs() returns a copy of path. So, it's theoretically possible to edit it by hacking the accessible fields, but it's nowhere near reliable and won't affect getURLs result.

    0 讨论(0)
  • 2021-02-02 11:50

    Since nobody could give my a definite answer nor a link to a corresponding part of the documentation I provide a answer myself. Nevertheless I would like to thank everybody that tried to answer the question.

    Short answer:

    The classpath is not final upon JVM start.

    You actually can put classes in the classpath after the JVM started and they will be loaded.


    Long answer:

    To answer this question I went after user unknowns suggestion and wrote a little test program.

    The basic idea is to have two classes. One is the main class which instantiates the second class. On startup the second class is not on the classpath. After the cli program started it'll prompt you to press enter. Before you press enter you copy the second class on the classpath. After you press enter the second class is instantiated. If the classpath would be final on JVM startup this would throw an Exception. But it doesn't. So I assume the classpath is not final on JVM startup.

    Here are the source codes:

    JVMTest.java

    package jvmtest;
    
    import java.io.Console;
    import jvmtest.MyClass;
    
    public class JVMTest {
      public static void main(String[] args) {
        System.out.println("JVMTest started ...");
    
        Console c = System.console();
        String enter = c.readLine("Press Enter to proceed");
        MyClass myClass = new MyClass();
        System.out.println("Bye Bye");
      }
    }
    

    MyClass.java

    package jvmtest;
    
    public class MyClass {
      public MyClass() {
        System.out.println("MyClass v2");
      }
    }
    

    The folder structure looks like this:

    jvmtest/
      JVMTest.class
      MyClass.class
    

    I started the cli program with this command:

    > java -cp /tmp/ jvmtest.JVMTest
    

    As you can see I had my jvmtest folder in /tmp/jvmtest. You obviously have to change this according to where you put the classes.

    So here are the steps I performed:

    1. Make sure only JVMTest.class is in jvmtest.
    2. Start the program with the command from above.
    3. Just to be sure press enter. You should see an Exception telling you that no class could be found.
    4. Now start the program again.
    5. After the program started and you are prompted to press enter copy the MyClass file into the jvmtest folder.
    6. Press enter. You should see "MyClass v1".

    Additional notes:

    This also worked when I packed the MyClass class in a jar and run the test above.

    I ran this on my Macbook Pro running Mac OS X 10.6.3

    > Java -version
    

    results in:

    java version "1.6.0_20"
    Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)
    Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode)
    
    0 讨论(0)
  • 2021-02-02 11:50

    I believe that the classpath is taken to be static and the result of changing the files is undefined.

    If you really want to be able to add and remove classes at runtime, consider doing so in your own classloader. This is what web containers do.

    0 讨论(0)
提交回复
热议问题