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

发布于 2024-09-03 05:48:28 字数 630 浏览 4 评论 0原文

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

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

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

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

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

Ps

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

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 load them without class loader hackery (URLClassLoaders etc.)

As far as I know classes are loaded dynamically. That means their bytecode representation is only loaded and transformed to a java.lang.Class object when needed.

So shouldn't it be possible to add a JAR or *.class file to the classpath after the JVM started and load those classes, provided they haven't been loaded yet? (To be clear: In this case the classpath is simply folder on the filesystem. "Adding a JAR or *.class file" simply means dropping them in this folder.)

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"?

It would be nice of you if you could point me to some sources in your answers. Preferably the offical SUN documentation: Sun JVM Spec. I have read the spec but could not find anything about the classpath and if it's finalized on JVM startup.

P.s.

This is a theoretical question. I just want to know if it is possible. There is nothing practical I want to achieve. There is just my thirst for knowledge :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

远昼 2024-09-10 05:48:28

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

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

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

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

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

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

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

There are two concepts here that are being intermixed: The classpath and the class files in the classpath.

If you point the classpath to a directory, you will generally have no issue adding a file to the directory and having it picked up as part of the classpath. Due to the potential size of all classes in the classpath it isn't really feasible for a modern JVM to load them all at startup. However this is of limited value as it will not include Jar files.

However, changing the classpath itself (which directories, jars, etc. are searched) on a running JVM will depend very much on the implementation. As far as I know, on standard Sun JVMs there is no documented (as in guaranteed to work) method of accomplishing this.

In general, if this is something you need to do (have a dynamic classpath that changes at runtime) then you want to be implementing a ClassLoader, if for no other reason than to be able to throw it away and make a new one that doesn't reference those classes anymore if they need to be unloaded.

However, for a small amount of dynamic loading there are better ways. In Java 1.6 you can specify all the jar files in a directory (*.jar) so you can tell users to put additional libraries in a specified location (although they have to be there at startup).

You also have the option of including a jar file or other location in the classpath even though you don't need it, as a placeholder for someone to put an optional jar or resource file there (such as a log configuration file).

But if you need serious dynamic class loading and especially unloading while the application is running, a Classloader implementation is required.

羁客 2024-09-10 05:48:28

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

简短的回答:

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

实际上,您可以在 JVM 启动后将类放入类路径中,并且它们将被加载。


长答案:

为了回答这个问题,我遵循了用户未知建议并编写了一个小测试程序。

基本思想是有两个类。第一个类是实例化第二个类的主类。启动时,第二个类不在类路径上。 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。
  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)

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)
方圜几里 2024-09-10 05:48:28

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

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

@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.

甜`诱少女 2024-09-10 05:48:28

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


类路径 final从最字面的意义上来说:< /em>

系统类加载器(从主类路径加载的类加载器)sun rt.jar中的 .misc.Launcher$AppClassLoader

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);
  <...>

但是,即使该字段是最终的,这并不意味着我们不能改变对象本身,如果我们以某种方式访问它。该字段没有访问修饰符 - 这意味着,只要我们从同一个包进行调用,我们就可以访问它。 (以下是 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 将添加到 URLClassPath私有 ArrayList 路径Stack url 中。 urls 是可访问的,但事实证明,它仅在尝试从中加载之前临时保存条目,此时信息将移动到 HashMap lmapArrayList 加载器getURLs() 返回path 的副本。因此,理论上可以通过破解可访问字段来编辑它,但它远不可靠,并且不会影响 getURLs 结果。

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.

-柠檬树下少年和吉他 2024-09-10 05:48:28

我只能根据我自己十年前破解非 Sun JVM 的经验来发表评论,但它确实在启动时扫描了整个类路径,作为一种效率衡量标准。找到的所有类的名称及其位置(目录或 zip/jar 文件)都添加到内部哈希表中。然而,那是十年前的事了,我不禁想知道,考虑到磁盘和内存架构的演变,在大多数设置中这是否仍然是合理的事情。

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.

夏日落 2024-09-10 05:48:28

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

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

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.

七婞 2024-09-10 05:48:28

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

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

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

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

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?

执着的年纪 2024-09-10 05:48:28

我想你可以阅读 TomCat 服务器的文档。该服务器通过自己实现java classpath。因此,当该服务器启动时,您可以部署新的 webApp,只需将 jar 拖放到热的适当文件夹中,无需重新启动服务器,它就会上传您的应用程序。

I think you could read documentation of TomCat server. This server implements java classpapth by its own. So, when this server is started, you can deploy new webApp just drag and drop jar in appropriatefolder in hot, without restart server, and it will upload your app.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文