我对类加载的理解是一个类在第一次需要时被加载(以非常简单的方式放置).使用-verbose:class和Iterators类的修改版本运行以下示例,该类在调用其clinit时打印消息我观察到了一些我无法解释的内容:
public class IteratorsTest
{
public static void main(String[] args)
{
com.google.common.collect.Iterators.forArray(1, 2, 3);
}
}
Run Code Online (Sandbox Code Playgroud)
(清理)输出如下:
[Loaded com.google.common.collect.Iterators from file:...]
[Loaded com.google.common.collect.Iterators$1 from file:...]
---------> Iterators <clinit>
Run Code Online (Sandbox Code Playgroud)
为什么在调用clinit之前加载Iterators $ 1?它只在临床中定义,不是吗?
static final UnmodifiableListIterator<Object> EMPTY_LIST_ITERATOR =
new UnmodifiableListIterator<Object>() {
...
}
Run Code Online (Sandbox Code Playgroud)
这导致以下字节代码:
static <clinit>()V
L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "---------> Iterators clinit --------------"**
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
NEW com/google/common/collect/Iterators$1
DUP
INVOKESPECIAL com/google/common/collect/Iterators$1.<init> ()V
L2
PUTSTATIC com/google/common/collect/Iterators.EMPTY_LIST_ITERATOR : Lcom/google/common/collect/UnmodifiableListIterator;
Run Code Online (Sandbox Code Playgroud)
为了让我更加困惑,我还有一个样本(太复杂了,无法在此处发布),其中与上面主要内容相同的代码行导致以下输出:
[Loaded com.google.common.collect.Iterators from file:...]
---------> Iterators <clinit>
[Loaded com.google.common.collect.Iterators$1 from …Run Code Online (Sandbox Code Playgroud) -Xbootclasspath/p:path我正在尝试在 Java 11 中做同样的事情,这些事情可以在 java 9 之前的版本中完成。
作为一个简单的示例,我修改了其中一种valueOf方法java.lang.Integer并使用以下方法编译了该项目:
javac --module-source-path=src/java.base --patch-module java.base=src/java.base -d mods $(find src -name '*.java')
然后我使用以下命令运行了一个简单的示例:
java --patch-module java.base=<pathToMyModifiedJavaBaseClasses> -p lib -m my.moduleA/my.moduleA.Main
这很有效,我看到显示的修改(我从 中所做的简单打印valueOf)。
然而,当我尝试做同样的事情时,java.lang.ClassLoader执行程序时出现以下错误(编译有效):
Error occurred during initialization of boot layer
java.lang.LinkageError: loader 'bootstrap' attempted duplicate class definition for java.lang.invoke.SimpleMethodHandle.
我什至不需要进行更改java.lang.ClassLoader。我的补丁文件夹中该类的绝对存在似乎引发了此错误。(我只想在类的底部添加一个字段)
ClassLoader注意:我只是认为当使用 Eclipse 编译该类时它可以工作。我知道的少数差异之一是 Eclipse 编译器似乎尚未遵循JEP 280。invokedynamic但是字节码中也有指令javac,所以我怀疑这就是问题所在。
java classpath java-platform-module-system java-module java-11
我们假设我们有以下代码:
public static void check() {
if (Config.initialized) {
...
}
}
Run Code Online (Sandbox Code Playgroud)
Config.initialized为开头假,只有在某个点的方法已经被JIT已经编译后变为真实.价值永远不会回归假.
我"知道"有很多非常复杂的优化正在进行(循环展开,分支预测,内联,逃逸分析等),虽然我远远没有详细了解它们,但我主要对以下内容感兴趣目前:
JIT编译器是否有办法在某个时间点之后检测到if将始终为true,以便可以完全跳过检查?完全我的意思是没有变量访问,没有条件检查/ jne等...
如果JIT没有办法摆脱(从某个点开始)不必要的样本检查(我不知道它怎么可能)有什么我可以做的来支持它?我唯一的想法是重新创建类,并在初始化事件发生后从字节代码中删除不必要的代码.
我知道这是完全微观优化,即使使用像JMH这样的工具也可能很难保证,但我仍然想知道和理解.
最后但并非最不重要的:
check方法需要重新编译?如果我正确理解我的JitWatch测试结果,上述问题的答案应该是:
java.lang.ref.Reference每次调用时,我都修补了类调用自定义本机方法get().修补后的类被添加到引导类路径中.
当我开始一个示例程序时,我看到很多打印输出来自我的本机方法(正如预期的那样),因为ReferenceJDK内部使用了很多s.
奇怪的行为从我的main方法开始.我正在做的就是创建一个WeakReference对象,删除强对象并调用get().无论出于何种原因,我添加的本机方法似乎都没有在运行模式下调用,并且我没有得到任何打印输出,也没有在本机内发生的其他事件.
如果我在调试模式下启动程序,一切都按预期工作,即调用本机方法.
如果我将其更改WeakReference为a SoftReference它始终有效,也可以在正常运行模式下运行.
我甚至尝试添加其他代码(如System.out.println到get()),但也没有工作.WeakReference在非调试模式下运行时,打印以某种方式停止.在调试中它始终有效.
有时我甚至会Finalizer在关闭期间从我的主要事件中获得很多事件/打印输出.所以看起来这个Reference类看起来有点不同.
java ×4
jvm ×3
bytecode ×1
classloader ×1
classpath ×1
debugging ×1
java-11 ×1
java-module ×1
java-platform-module-system ×1
jit ×1
jvm-hotspot ×1
reference ×1