代码之家  ›  专栏  ›  技术社区  ›  Jens

JVM启动后的Java类路径是最终的吗?

  •  22
  • Jens  · 技术社区  · 16 年前

    最近我读了很多关于Java类加载过程的文章。我经常遇到这样的文本,它们声称在运行时不可能将类添加到类路径,并且在没有类加载器黑客(urlcllassloaders等)的情况下加载它们。

    据我所知,类是动态加载的。这意味着它们的字节码表示只能在需要时加载并转换为java.lang.class对象。

    那么,如果还没有加载jar或*.class文件,那么在jvm启动并加载这些类之后,不应该向类路径添加jar或*.class文件吗?(要清楚:在这种情况下,类路径只是文件系统上的文件夹)。添加jar或*.class文件“只意味着将它们放到这个文件夹中。)

    如果不是,这是否意味着在JVM启动时搜索类路径,并且找到的类的所有完全限定名都缓存在内部“列表”中?

    如果你能给我指出你答案中的一些来源,那你会很高兴的。最好是官方的Sun文档: Sun JVM Spec . 我已经阅读了规范,但是找不到关于类路径的任何内容,如果它是在JVM启动时完成的。

    附笔。

    这是一个理论问题。我只是想知道是否有可能。我不想实现任何实际目标。只是我对知识的渴望:)

    7 回复  |  直到 10 年前
        1
  •  7
  •   Community Mohan Dere    9 年前

    因为没有人能给我一个明确的答案,也没有人链接到文档的相应部分,所以我自己也提供了一个答案。不过,我还是要感谢所有试图回答这个问题的人。

    简短回答:

    类路径在JVM启动时不是最终的。

    实际上,可以在JVM启动后将类放入类路径中,并将其加载。


    长回答:

    为了回答这个问题,我追求 user unknowns 建议并写了一个小测试程序。

    基本的想法是有两个班。一个是实例化第二个类的主类。启动时,第二个类不在类路径上。启动CLI程序后,它将提示您按Enter键。在按Enter键之前,请复制类路径上的第二个类。按Enter键后,将实例化第二个类。如果类路径在JVM启动时是最终的,则会引发异常。但事实并非如此,所以我假设类路径在JVM启动时不是最终的。

    以下是源代码:

    JavaTest.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");
      }
    }
    

    MyCase.java

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

    文件夹结构如下:

    jvmtest/
      JVMTest.class
      MyClass.class
    

    我用以下命令启动了CLI程序:

    > java -cp /tmp/ jvmtest.JVMTest
    

    如您所见,我的jvmtest文件夹在/tmp/jvmtest中。很明显,你必须根据你上课的地点来改变这一点。

    下面是我执行的步骤:

    1. 确保只有jvmtest.class在jvmtest中。
    2. 用上面的命令启动程序。
    3. 只是要确保按Enter键。您应该看到一个异常,告诉您找不到类。
    4. 现在重新启动程序。
    5. 程序启动后,系统会提示您按Enter键,将MyClass文件复制到jvmtest文件夹中。
    6. 按回车键。你应该看到“MyClass v1”。

    附加说明:

    当我将myClass类打包到一个jar中并运行上面的测试时,这也很有效。

    我在运行Mac OS X 10.6.3的MacBook Pro上运行了这个

    > Java -version
    

    结果:

    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)
    
        2
  •  6
  •   Yishai    16 年前

    这里有两个混合的概念:类路径和类路径中的类文件。

    如果您将类路径指向一个目录,那么通常向该目录添加一个文件并将其作为类路径的一部分进行提取是没有问题的。由于类路径中所有类的潜在大小,现代JVM在启动时加载所有类实际上是不可行的。然而,这是有限的价值,因为它将不包括JAR文件。

    但是,在运行的JVM上更改类路径本身(搜索哪些目录、jar等)将非常依赖于实现。据我所知,在标准的Sun JVM上,没有实现这一点的文档化方法(如保证有效的方法)。

    一般来说,如果这是您需要做的事情(有一个在运行时更改的动态类路径),那么您希望实现一个类加载器,如果不是因为其他原因,而是能够扔掉它,并创建一个新的类,如果需要卸载这些类,它将不再引用这些类。

    然而,对于少量的动载荷,有更好的方法。在爪哇1.6中,您可以在目录(*.jar)中指定所有的JAR文件,这样您就可以告诉用户在指定的位置添加额外的库(尽管它们在启动时必须在那里)。

    您还可以选择在类路径中包含JAR文件或其他位置(即使您不需要它),作为一个占位符,供某人在其中放置可选的JAR或资源文件(例如日志配置文件)。

    但是,如果您需要严重的动态类加载,特别是在应用程序运行时卸载,则需要类加载程序实现。

        3
  •  1
  •   Tim Zheng    11 年前

    @Jen我认为你的实验不能证明你的理论,因为它更多的是关于对象实例化:当这个类的一个对象被实例化时,你的打印行就会发生,但不一定告诉JVM知道你的代码,类,就在它被实例化时。

    我的观点是,当JVM上升时,所有Java类都被加载,并且在运行时可以将更多的类插入到JVM中:这种技术被称为:热部署。

        4
  •  1
  •   ivan_pozdeev RenanSS    10 年前

    底线:可以在运行时向系统类路径添加条目,并显示了如何添加条目。然而,这有不可逆转的副作用,并且依赖于Sun JVM实现细节。


    类路径 final , 从字面意义上讲:

    系统类加载程序(从主类路径加载的加载程序) is sun.misc.Launcher$AppClassLoader in rt.jar .

    rt.jar:sun/misc/Launcher.class (来源是用 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);
      <...>
    

    但是,即使字段是最终字段,这并不意味着如果我们以某种方式访问对象,就不能改变对象本身。该字段没有访问修饰符-这意味着,只有从同一个包中进行调用,才能访问它。(以下是 IPython 具有 JPype 这些命令是可读的,足以容易地导出Java对应的命令。

    #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/']
    

    现在, URLClassPath 有公众 addURL 方法。让我们试试看会发生什么:

    #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']
    

    现在,让我们尝试从 .jar :

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

    成功!

    这可能会在沙盒或其他有自定义类加载器或安全设置的地方失败。( AppClassLoader.loadClass 打电话前安全检查吗 super )

    进一步的代码检查表明 附加地址 同时禁用 URL类路径 的查找缓存(在 native 方法),这是不可逆的。最初, lookupCacheEnabled 标志设置为 sun.cds.enableSharedLookupCache 系统属性。

    接口无法提供 编辑 参赛作品。URL添加到 URL类路径 private ArrayList path Stack urls . urls 是可访问的,但事实证明,在试图从中加载之前,它只是临时保存条目,此时信息将移动到 HashMap lmap ArrayList loaders . getURLs() 返回的副本 path . 因此,理论上可以通过黑客攻击可访问的字段来编辑它,但是它几乎不可靠,也不会影响 getURLs 结果。

        5
  •  0
  •   crazyscot    16 年前

    我只能根据十年前我自己对一个非Sun JVM进行黑客攻击的经历来评论,但它确实在启动时扫描了整个类路径,作为一种效率度量。找到的所有类的名称都与它们的位置(目录或zip/jar文件)一起添加到内部哈希表中。然而,那是十年前的事了,考虑到磁盘和内存体系结构是如何发展的,我不禁想知道在大多数情况下,这是否仍然是一件合理的事情。

        6
  •  0
  •   Thorbjørn Ravn Andersen    16 年前

    我相信类路径是静态的,更改文件的结果是未定义的。

    如果你 真正地 为了能够在运行时添加和删除类,可以考虑在自己的类加载器中这样做。这就是Web容器所做的。

        7
  •  0
  •   user unknown    16 年前

    所以不应该增加一个 类路径的jar或*.class文件 在JVM启动之后…

    将jar和目录添加到类路径,而不是类。这些类要么在目录中,要么在JAR中。

    如果不是,这是否意味着 在JVM启动时搜索类路径 以及 找到的类缓存在 内部“列表”?

    这很容易测试:设置类路径,启动程序,将一个新类移到CP中,从程序中调用'class.forname(“new class”)。它找到新的班级了吗?