为什么JIT在启动时编译一些方法?

3

我正在研究一段非常简单的代码块在JIT上的行为:

public class PlayWithAssembly {

    public static void main(String[] args) {
        Random random = new Random();
        random.nextInt();
    }
}

实际上,对于我的问题来说,main方法的内容完全无关紧要。我在Ubuntu 16.04.5上使用OpenJDK 10.0.1运行以下代码,并使用以下命令:

java -Xbatch -XX:+PrintCompilation -XX:CompileThreshold=1000000 -cp target/classes com.xxx.playground.internal.bytecode.PlayWithAssembly

由于CompileThreshold设置为非常高的值,我不希望进行任何JIT编译,我希望在实践中JVM完全以解释模式运行此示例。但是运行上述命令时,我收到了以下已编译方法的列表(它们都是JDK的一部分):

 47    1    b  3       java.lang.StringLatin1::hashCode (42 bytes)
 50    2    b  3       java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)
 51    3     n 0       jdk.internal.misc.Unsafe::getObjectVolatile (native)   
 55    4    b  3       java.lang.Object::<init> (1 bytes)
 56    5    b  3       java.lang.String::isLatin1 (19 bytes)
 56    6    b  3       java.lang.String::hashCode (49 bytes)
 57    7    b  3       java.lang.String::coder (15 bytes)
 58    8    b  3       java.lang.Math::floorMod (10 bytes)
 59    9    b  3       java.util.ImmutableCollections$SetN::probe (60 bytes)
 62   10    b  1       java.util.ImmutableCollections$Set0::hashCode (2 bytes)
 62   11    b  3       java.lang.String::equals (65 bytes)
 64   12    b  1       java.util.Collections$EmptySet::hashCode (2 bytes)
 65   13    b  3       java.lang.StringLatin1::equals (36 bytes)
 66   14    b  3       java.util.Collections::emptySet (4 bytes)
 66   15    b  3       java.lang.module.ModuleDescriptor$Exports::<init> (10 bytes)
 67   16    b  4       java.lang.StringLatin1::hashCode (42 bytes)
 71    1       3       java.lang.StringLatin1::hashCode (42 bytes)   made not entrant
 72   17    b  3       java.lang.module.ModuleDescriptor$Exports::hashCode (38 bytes)
 73   18    b  3       java.util.Objects::equals (23 bytes)
 73   19    b  3       java.util.Objects::requireNonNull (14 bytes)
 74   20    b  3       java.util.AbstractCollection::<init> (5 bytes)
 76   21    b  3       java.util.AbstractSet::<init> (5 bytes)
 76   22    b  3       java.util.ImmutableCollections$AbstractImmutableSet::<init> (5 bytes)
 77   23    b  1       java.lang.Object::<init> (1 bytes)
 77    4       3       java.lang.Object::<init> (1 bytes)   made not entrant
 81   24    b  1       java.lang.module.ModuleDescriptor::name (5 bytes)
 82   25    b  1       java.lang.module.ModuleReference::descriptor (5 bytes)
 88   26    b  3       java.lang.String::charAt (25 bytes)
 93   27    b  3       java.util.concurrent.ConcurrentHashMap::spread (10 bytes)
 94   28    b  3       java.util.ImmutableCollections$SetN$1::hasNext (47 bytes)
 95   29    b  3       java.util.ImmutableCollections$SetN$1::next (35 bytes)
 96   30    b  3       java.util.Set::of (66 bytes)
 98   31    b  1       java.util.KeyValueHolder::getKey (5 bytes)
 99   32    b  1       java.util.KeyValueHolder::getValue (5 bytes)
100   33    b  3       java.util.ImmutableCollections$MapN::probe (64 bytes)
101   34    b  3       java.util.KeyValueHolder::<init> (21 bytes)
102   35    b  3       java.util.ImmutableCollections$MapN::get (21 bytes)
103   36     n 0       java.lang.Object::hashCode (native)   
103   37    b  3       jdk.internal.module.ModuleReferenceImpl::hashCode (56 bytes)
105   38    b  3       java.util.HashMap::hash (20 bytes)
106   39   !b  3       java.util.concurrent.ConcurrentHashMap::putVal (432 bytes)
112   40     n 0       jdk.internal.misc.Unsafe::compareAndSetLong (native)   
112   41    b  3       java.util.concurrent.ConcurrentHashMap::putIfAbsent (8 bytes)
112   42    b  1       java.lang.module.ResolvedModule::reference (5 bytes)
113   43    b  3       java.util.concurrent.ConcurrentHashMap::addCount (289 bytes)
115    2       3       java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)   made not entrant
115   39   !   3       java.util.concurrent.ConcurrentHashMap::putVal (432 bytes)   made not entrant
115   44    b  3       jdk.internal.misc.Unsafe::getObjectAcquire (7 bytes)
116   45    b  3       java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)
116   46     n 0       jdk.internal.misc.Unsafe::compareAndSetObject (native)   
117   47    b  3       java.util.concurrent.ConcurrentHashMap$Node::<init> (20 bytes)
117   48   !b  3       java.util.concurrent.ConcurrentHashMap::putVal (432 bytes)
120   49    b  3       java.util.concurrent.ConcurrentHashMap::casTabAt (21 bytes)
122   50    b  3       java.util.HashMap::getNode (148 bytes)
124   51    b  3       java.lang.String::length (11 bytes)
125   52    b  3       java.lang.StringLatin1::canEncode (13 bytes)
126   53    b  3       java.util.HashMap::put (13 bytes)
127   54     n 0       java.lang.System::arraycopy (native)   (static)
128   55    b  3       java.util.HashMap$Node::<init> (26 bytes)
128   56    b  3       java.util.HashMap::newNode (13 bytes)
129   57    b  3       java.util.HashMap::afterNodeInsertion (1 bytes)
129   58    b  3       java.util.Optional::ofNullable (15 bytes)
131   59    b  3       java.util.HashMap::get (23 bytes)
132   60    b  3       java.util.HashMap::putVal (300 bytes)
135   61    b  1       java.lang.module.ModuleDescriptor$Exports::source (5 bytes)
135   62    b  1       java.util.Collections$1::hasNext (5 bytes)
136   63    b  3       java.lang.module.ResolvedModule::name (11 bytes)
137   64    b  3       java.util.HashSet::add (20 bytes)
137   65    b  1       java.util.Collections$EmptySet::isEmpty (2 bytes)
138   66    b  3       java.lang.module.ResolvedModule::hashCode (16 bytes)
139   67    b  3       java.lang.module.ModuleDescriptor$Exports::isQualified (18 bytes)
140   68    b  1       java.lang.module.ModuleDescriptor::isAutomatic (5 bytes)
140   69    b  3       java.util.AbstractMap::<init> (5 bytes)
141   70    b  1       java.lang.module.ModuleDescriptor$Exports::targets (5 bytes)
141   71    b  1       java.lang.module.ResolvedModule::configuration (5 bytes)
142   72    b  3       java.util.HashMap::<init> (11 bytes)
142   73    b  3       java.util.ImmutableCollections$Set2$1::hasNext (14 bytes)
143   74    b  4       java.util.ImmutableCollections$SetN$1::hasNext (47 bytes)
148   28       3       java.util.ImmutableCollections$SetN$1::hasNext (47 bytes)   made not entrant
149   75    b  1       java.util.ImmutableCollections$Set1::size (2 bytes)
150   76    b  3       java.lang.Math::min (11 bytes)
152   77    b  3       java.util.AbstractCollection::isEmpty (13 bytes)
153   78    b  4       java.lang.String::hashCode (49 bytes)
160    6       3       java.lang.String::hashCode (49 bytes)   made not entrant
162   79    b  3       java.util.Map::entry (10 bytes)
165   80    b  1       java.lang.module.ModuleDescriptor::isOpen (5 bytes)
167   81    b  1       java.util.HashMap::afterNodeInsertion (1 bytes)
167   57       3       java.util.HashMap::afterNodeInsertion (1 bytes)   made not entrant
168   82    b  3       jdk.internal.module.ModuleBootstrap$2::hasNext (30 bytes)
169   83    b  3       java.util.HashMap::resize (356 bytes)
171   84    b  3       java.util.Collections$UnmodifiableCollection$1::hasNext (10 bytes)
172   85    b  3       jdk.internal.module.ModuleBootstrap$2::next (52 bytes)
173   86    b  3       java.util.HashMap::putIfAbsent (13 bytes)
174   87     n 0       java.lang.Module::addExportsToAllUnnamed0 (native)   (static)
175   88    b  1       java.lang.Module::getDescriptor (5 bytes)
182   78       4       java.lang.String::hashCode (49 bytes)   made not entrant
185   89    b  3       java.lang.StringLatin1::indexOf (61 bytes)
187   23       1       java.lang.Object::<init> (1 bytes)   made not entrant
195   90    b  1       java.lang.Object::<init> (1 bytes)
197   91    b  3       java.lang.String::hashCode (49 bytes) 

我试图将这些方法与内置函数列表进行匹配,但它们不匹配,所以我的问题是:为什么编译了这些方法(而其他方法没有),我是否有任何控制权?


JVM在启动时会进行大量处理,这些方法被调用了10,000多次(或循环了10,000次)。在第一次使用(或之前)触发方法进行编译并没有太大的好处。 - Peter Lawrey
@PeterLawrey,是的,我完全意识到立即编译方法所涉及的权衡。我将CompileThreshold设置为1进行了测试,启用后台编译,代码运行了4秒以上。您有任何关于挖掘JVM运行我的“main”方法的主题的起点吗? - MateuszPrzybyla
我会查看Launcher类。https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/misc/Launcher.java - Peter Lawrey
1个回答

6
根据-XX:CompileThreshold的文档
当启用分层编译时,此选项将被忽略;请参阅选项-XX:-TieredCompilation
因此,当指定-XX:-TieredCompilation时,大多数这些条目将消失,但某些条目仍可能免于基于计数器的编译决策。

1
注意:就JIT而言,在调用main()之前调用和编译的代码没有什么特别之处。它们被编译的原因与调用main()之后的代码编译的原因相同+1。 - Peter Lawrey
就是这样!我添加了“-XX:-TieredCompilation”,现在我感觉我真的控制了阈值。现在吸引我的注意力的是,即使将“CompileThreshold”设置为“40000”,方法“java.lang.StringLatin1 :: hashCode”仍然会编译!这意味着在运行“main()”方法之前,计算字符串哈希码的许多事情正在发生!实际上,对于我的环境,正好是45955次调用。 - MateuszPrzybyla
有关编译策略的更多信息:https://dev59.com/QVsV5IYBdhLWcg3w8iqr#35614237 - apangin
@MateuszPrzybyla 嗯,字符串哈希经常发生。想想系统属性(不仅是已存在的属性,每个查询的键也需要进行哈希),所有加载的类,区域设置数据,文件系统缓存等等。我不知道,也许甚至字符串池化也会使用Java端的哈希码实现。虽然在启动时不太可能发生,但即使垃圾收集器激活了去重功能,它也可能对字符串进行哈希处理... - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接