【问题标题】:Is the Java classpath final after JVM startup?JVM启动后Java类路径是最终的吗?
【发布时间】:2010-05-31 09:16:36
【问题描述】:

我最近阅读了很多关于 Java 类加载过程的内容。我经常遇到声称在运行时无法将类添加到类路径并在没有类加载器骇客(URLClassLoaders 等)的情况下加载它们的文本

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

那么在 JVM 启动后是否可以将 JAR 或 *.class 文件添加到类路径并加载这些类,前提是它们尚未加载? (要明确:在这种情况下,类路径只是文件系统上的文件夹。“添加 JAR 或 *.class 文件”只是意味着将它们放入此文件夹中。)

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

如果你能在你的答案中指出我的一些来源,那就太好了。最好是官方 SUN 文档:Sun JVM Spec。我已阅读规范,但找不到有关类路径的任何信息,以及它是否在 JVM 启动时完成。

附言

这是一个理论问题。我只是想知道这是否可能。我没有什么实际想要实现的。这只是我对知识的渴望:)

【问题讨论】:

  • 那不是类加载器hackery。

标签: java classpath classloader


【解决方案1】:

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

如果您将类路径指向一个目录,通常将文件添加到该目录并将其作为类路径的一部分拾取是没有问题的。由于类路径中所有类的潜在大小,现代 JVM 在启动时将它们全部加载是不可行的。但是,它的价值有限,因为它不包含 Jar 文件。

但是,在运行的 JVM 上更改类路径本身(搜索哪些目录、jar 等)将在很大程度上取决于实现。据我所知,在标准 Sun JVM 上,没有记录(如保证工作)实现此目的的方法。

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

但是,对于少量的动态加载,有更好的方法。在 Java 1.6 中,您可以在一个目录 (*.jar) 中指定所有 jar 文件,这样您就可以告诉用户将其他库放在指定位置(尽管它们必须在启动时就在那里)。

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

但如果您需要在应用程序运行时进行严格的动态类加载,尤其是卸载,则需要实现 Classloader。

【讨论】:

    【解决方案2】:

    由于没有人可以给我一个明确的答案,也没有人可以提供指向文档相应部分的链接,我自己提供了一个答案。尽管如此,我还是要感谢所有试图回答这个问题的人。

    简答:

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

    你实际上可以在JVM启动后将类放在类路径中,它们会被加载。


    长答案:

    为了回答这个问题,我根据user unknowns 的建议编写了一个小测试程序。

    基本思想是有两个类。一个是实例化第二个类的主类。启动时,第二个类不在类路径上。 cli程序启动后会提示你按回车。在你按下回车键之前,你复制类路径中的第二个类。按下回车后,第二个类被实例化。如果类路径在 JVM 启动时是最终路径,则会抛出异常。但事实并非如此。所以我假设类路径在 JVM 启动时不是最终的。

    以下是源代码:

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

    文件夹结构如下:

    jvmtest/
      JVMTest.class
      MyClass.class
    

    我用这个命令启动了 cli 程序:

    > java -cp /tmp/ jvmtest.JVMTest
    

    如您所见,我的 jvmtest 文件夹位于 /tmp/jvmtest 中。你显然必须根据你放置类的位置来改变它。

    以下是我执行的步骤:

    1. 确保 jvmtest 中只有 JVMTest.class。
    2. 使用上面的命令启动程序。
    3. 只是为了确保按下回车键。您应该会看到一个异常,告诉您找不到类。
    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)
    

    【讨论】:

    • 您的回答和“解决方案”具有误导性。您没有修改类路径,而是修改了类路径的 contents。此外,这将被视为不好的做法。
    【解决方案3】:

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

    我的观点是,所有的Java类都是在JVM启动的时候加载的,并且可以在JVM运行的时候将更多的类插入到JVM中:这种技术叫做:热部署。

    【讨论】:

      【解决方案4】:

      底线:可以在运行时将条目添加到系统类路径,并展示了如何。然而,这具有不可逆转的副作用,并且依赖于 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);
        <...>
      

      但是,即使该字段是 final 的,这并不意味着如果我们以某种方式访问​​它,我们就不能改变对象本身。该字段没有访问修饰符 - 这意味着,只要我们从同一个包中进行调用,我们就可以访问它。 (以下是IPythonJPype;这些命令的可读性足以轻松导出它们的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 之前进行安全检查)。

      进一步的代码检查表明addURL 还禁用了URLClassPath 的查找缓存(在一些native 方法中实现),这是不可逆的。最初,lookupCacheEnabled 标志设置为sun.cds.enableSharedLookupCache 系统属性的值。

      该界面无法编辑条目。 URL 被添加到URLClassPathprivate ArrayList pathStack urlsurls 是可访问的,但事实证明,它只是在尝试从中加载之前临时保存条目,此时信息将移动到 HashMap lmapArrayList loadersgetURLs() 返回path 的副本。因此,理论上可以通过破解可访问字段来对其进行编辑,但它远非可靠并且不会影响getURLs 结果。

      【讨论】:

        【解决方案5】:

        我只能从我记得的十年前破解非 Sun JVM 的经历中发表评论,但它确实在启动时扫描了整个类路径,作为一种效率措施。找到的所有类的名称连同它们的位置(目录或 zip/jar 文件)一起添加到内部哈希表中。然而,那是十年前的事了,考虑到磁盘和内存架构的演变,我不禁想知道在大多数情况下这是否仍然是合理的做法。

        【讨论】:

        • 我也这么认为。我对 Java 比较陌生,如果类路径确实只在启动时被“扫描”,我会感到惊讶。这听起来是错误的。或者这样做是有充分理由的。但到目前为止,感谢您的回答!
        【解决方案6】:

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

        如果您真的希望能够在运行时添加和删除类,请考虑在您自己的类加载器中这样做。这就是 Web 容器的作用。

        【讨论】:

          【解决方案7】:

          所以不应该添加一个 JAR 或 *.class 文件到类路径 JVM启动后...

          您将 jar 和目录添加到类路径,而不是类。这些类位于目录或 jar 中。

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

          这很容易测试:设置类路径,启动程序,将新类移动到 CP 中,从程序中调用 'Class.forName ("NewClass")。它找到新班级了吗?

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多