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

xnifntxz  于 2022-12-17  发布在  Java
关注(0)|答案(8)|浏览(433)

最近我读了很多关于Java类加载过程的文章,经常看到一些文章声称,在运行时向类路径中添加类并在没有类加载器的情况下加载类是不可能的(URLClassLoaders等)。
据我所知,类是动态加载的,这意味着它们的字节码表示只在需要时才加载并转换为java. lang. Class对象。
那么,在JVM启动之后,如果类还没有加载,那么是否可以将JAR或 *.class文件添加到类路径中并加载这些类呢?(需要说明的是:在这种情况下,类路径只是文件系统上的一个文件夹,“添加JAR或 *.class文件”就是将它们放到这个文件夹中。)
如果没有,这是否意味着在JVM启动时搜索类路径,并将找到的类的所有完全限定名缓存在内部“列表”中?
如果你能在你的回答中指出一些来源,那就太好了。最好是官方的SUN文档:Sun JVM Spec。我已经阅读了规范,但找不到任何关于类路径的信息,也找不到它是否在JVM启动时完成。
附言
这是一个理论上的问题,我只想知道有没有可能,没有什么实际的东西是我想达到的,有的只是我对知识的渴望:)

zrfyljdw

zrfyljdw1#

这里有两个概念被混合在一起:类路径和类路径中的类文件。
如果你把类路径指向一个目录,你通常不会在目录中添加一个文件,并把它作为类路径的一部分。由于类路径中所有类的潜在大小,现代JVM在启动时加载所有类是不可行的。然而,这是有限的价值,因为它不包括Jar文件。
然而,在运行的JVM上更改类路径本身(搜索哪些目录、jar等)在很大程度上取决于实现,据我所知,在标准的Sun JVM上,没有文档化的(保证工作的)方法来完成这一点。
一般来说,如果这是您需要做的事情(拥有一个在运行时更改的动态类路径),那么您希望实现一个ClassLoader,如果没有其他原因,只是为了能够丢弃它,并创建一个新的类,在需要卸载这些类时不再引用它们。
然而,对于少量的动态加载,有更好的方法。在Java 1.6中,你可以指定一个目录(*.jar)中的所有jar文件,这样你就可以告诉用户把额外的库放在一个指定的位置(尽管它们在启动时必须在那里)。
您还可以选择在类路径中包含一个jar文件或其他位置,即使您不需要它,也可以将其作为占位符,以便某人将可选的jar或资源文件(如日志配置文件)放在那里。
但是,如果您需要在应用程序运行时进行严格的动态类加载,特别是卸载,则需要Classloader实现。

kxe2p93d

kxe2p93d2#

由于没有人能给予我一个明确的答案,也没有文件相应部分的链接,我自己提供了答案。尽管如此,我还是要感谢每一个试图回答这个问题的人。
简短回答:

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

实际上,您可以在JVM启动后将类放入类路径中,然后加载它们。
长回答:
为了回答这个问题,我按照user unknowns的建议编写了一个小测试程序。
基本思想是有两个类。一个是主类,它示例化第二个类。启动时,第二个类不在类路径上。cli程序启动后,它会提示您按Enter键。按Enter键之前,您要复制类路径上的第二个类。按Enter键后,第二个类被示例化。如果类路径在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。
1.用上面的命令启动程序。
1.只是为了确保按下回车键,你应该看到一个异常,告诉你没有类可以找到。
1.现在再次启动程序。
1.在程序启动并提示您按Enter键之后,将MyClass文件复制到jvmtest文件夹中。
1.按Enter键。您应该看到“MyClass v1”。
附加说明:
当我将MyClass类打包到一个jar中并运行上面的测试时,也是如此。
我在运行MacOSX 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)
zzzyeukh

zzzyeukh3#

@Jen我不认为你的实验能证明你的理论,因为它更多的是关于对象示例化:当这个类的一个对象被示例化时,你的打印行就发生了,但不一定告诉JVM知道你的代码,这个类,只是在它被示例化的时候。
我的观点是所有的Java类都是在JVM启动时加载的,并且可以在JVM运行时插入更多的类:这种技术被称为:紧急部署。

jogvjijk

jogvjijk4#

底线:可以在运行时向系统类路径中添加条目,并显示了如何添加。但是,这会产生不可逆的副作用,并且依赖于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结果。

qeeaahzv

qeeaahzv5#

我只能根据我自己十年前对非sunJVM进行黑客攻击的经验来进行评论,但是作为一种效率措施,它确实在启动时扫描了整个类路径。(目录或zip/jar文件)。但是,那是十年前的事了,考虑到磁盘和内存体系结构的发展,我不禁想知道在大多数情况下这样做是否仍然是合理的。

slwdgvem

slwdgvem6#

我认为类路径是静态的,更改文件的结果是未定义的。
如果你真的希望能够在运行时添加和删除类,考虑在你自己的类加载器中这样做,这就是web容器的作用。

dgsult0t

dgsult0t7#

因此,在JVM启动之后,是否可以将JAR或 *.class文件添加到类路径中...
您可以向类路径添加jar和目录,而不是类。类可以在目录中,也可以在jar中。
如果没有,这是否意味着在JVM启动时搜索类路径,并将找到的类的所有完全限定名缓存在内部“列表”中?
这很容易测试:设置类路径,启动你的程序,移动一个新的类到CP中,从你的程序中调用'Class.forName(“NewClass”)。它找到新的类了吗?

vbopmzt1

vbopmzt18#

我想你可以看一下TomCat服务器的文档,这个服务器自己实现了java类路径,所以当这个服务器启动后,你可以部署新的webApp,只需要把jar拖放到hot中相应的文件夹里,而不需要重新启动服务器,它就会上传你的App。

相关问题