问题

这段 Java 代码中的局部变量能够被提前回收吗?编译器或 VM 能够实现如下的人工优化吗?

回答
这段 Java 代码中的局部变量,理论上确实存在被提前回收的可能性。不过,这里的“提前回收”并非我们直观理解的,在代码执行完毕前就完全从内存中消失。更准确的说法是,这些局部变量的内存占用可以在其生命周期结束后,但不等到方法执行结束就被JVM判定为“无用”,从而有机会被垃圾回收器(Garbage Collector, GC)在后续的某个时机回收。

编译器和JVM确实会进行各种各样的优化,以提高程序性能,其中就包括对局部变量内存使用的优化。这种优化并非“人工”的,而是JVM强大的内在机制和编译器智能分析的体现。

我们来深入剖析一下:

局部变量的生命周期与垃圾回收

首先要明确,Java 中局部变量的生命周期是与它们所在的代码块(通常是方法)紧密关联的。一个局部变量在声明时创建,在它超出作用域时,它就不再可达。可达性是垃圾回收器判断对象是否仍然被使用的关键。

当一个局部变量(比如一个对象引用)不再被任何活动的代码路径所引用时,它就被认为是“不可达”的。即便如此,JVM并不会立即释放这块内存。垃圾回收是一个异步过程,它会在合适的时机(通常是在内存紧张或者GC线程被触发时)扫描堆内存,查找并回收那些不可达的对象所占用的内存。

编译器和JVM是如何实现“提前回收”的?

这里的“提前回收”主要是指JVM的垃圾回收器能够更早地识别出某些局部变量的生命周期已经结束,并将其占用的内存标记为可回收,而不是等到整个方法执行完毕。

1. 作用域分析与指针压缩:
编译器在编译阶段会进行作用域分析。它会精确地知道一个局部变量在哪个代码段内是有效的。一旦代码执行离开了这个变量的有效作用域,即使方法还没结束,这个变量的引用就已经不存在了。
JVM会维护一个“局部变量表”(Local Variable Table),其中存储了方法执行时所有局部变量的信息。当一个局部变量不再被引用时,其对应的局部变量表中的槽位可以被重用。虽然这不直接是“内存回收”,但它减少了对这部分存储空间的活跃占用。
更关键的是,当一个局部变量引用的对象不再被栈中的任何引用指向时,JVM就可以将其引用的对象标记为“潜在的可回收对象”。GC线程在扫描堆内存时,会发现这个对象,并根据其是否仍然被其他对象引用来决定是否回收。

2. 栈上分配(Stack Allocation):
对于一些小的、生命周期短的对象,JVM(特别是其JIT编译器)有时会尝试将它们直接分配在栈帧(Stack Frame)上,而不是堆(Heap)上。
栈上的对象生命周期与栈帧一样,当方法返回时,整个栈帧都会被销毁。如果对象在栈上分配,那么它的“回收”就非常迅速,几乎是随着栈帧的销毁而同步发生。
代码示例场景:
```java
public void processData() {
int count = 10; // 这是一个原始类型局部变量,内存直接在栈上管理,随方法结束而销毁
String tempString = new String("temporary"); // 这是一个对象引用
// ... do something with count and tempString ...
if (condition) {
// tempString 在此作用域后就不再被引用
}
// 即使condition为true,count和tempString的内存占用(如果tempString指向的对象在堆上)
// 理论上可以在此之后就被GC考虑,而不是等到processData方法结束。
// 如果tempString的对象很小,且GC策略允许,也可能被优化为栈上分配。
}
```
对于 `tempString` 这样的对象引用,如果 `tempString` 在 `if` 语句块之后就再也没有被其他地方引用,那么 `tempString` 这个引用本身(在局部变量表中)就失效了。而它指向的那个 `new String("temporary")` 对象,如果不再被其他任何活动对象引用,GC就可以在执行到方法末尾之前(甚至方法中间的某个GC周期)将其回收。

3. 逃逸分析(Escape Analysis):
这是一种非常重要的JIT编译器优化技术。逃逸分析会分析一个对象的引用是否会“逃逸”出其创建的局部作用域。
如果一个对象的引用没有逃逸(即只在方法内部被使用,不会被返回给调用者,也不会被作为全局变量持有,更不会被其他线程访问),那么JVM就可以对其进行优化。
优化手段包括:
栈上分配: 如果对象没有逃逸,JVM可以直接将其分配到栈帧上。当方法结束时,栈帧销毁,对象内存也随之释放。
锁消除(Lock Elimination): 如果一个对象只在同步代码块中使用,并且其引用不会逃逸,JVM可以消除这个对象的同步开销。
对局部变量的意义: 如果一个局部变量指向的对象通过逃逸分析被判定为“不逃逸”,那么它很可能被分配在栈上,或者其占用的堆内存可以在局部变量失效后尽早被GC回收。

编译器“人工优化”的说法

“人工优化”这个词容易引起误解。JVM的优化是基于复杂的算法和对程序行为的深入分析,而不是由程序员手动插入某些指令来实现的。编译器和JVM的优化是自动进行的,它们的目标是找到最高效的执行路径。

总结

Java 中的局部变量,其引用的对象可以在其声明的作用域结束后,但不一定等到方法执行结束,就被JVM的垃圾回收器标记为可回收。这得益于JVM精细的作用域管理、垃圾回收机制以及JIT编译器强大的优化能力,特别是栈上分配和逃逸分析。这些优化并非“人工”干预,而是JVM在幕后默默完成的,旨在提升程序运行效率。因此,与其说是“提前回收”,不如说是“尽早被GC判定为可回收并有机会被回收”。

网友意见

user avatar

以前写过一些,请参考传送门:

答复: HotSpot VM 内存堆的两个Survivor区

下面稍微新写点字吧。不知道有多少人能耐心读完,反正我先写着 >_<

===================================================

简单回答题主的问题:

lo 指向的对象能够在在循环过程中被回收吗?

能。JVM规范没有说不能,所以具体实现有选择的自由。主流JVM在做完从Java字节码到机器码的编译后,都能做适当的优化来让题主例中的'lo'变量所指向的对象可以被回收。

做到这种效果的编译优化技术叫做“活跃分析”(liveness analysis)。一个变量只有被使用的地方才是“活跃”的;如果没有(继续)被使用,那么一个变量就“死”掉了。例子请参考这个传送门:

如果变量在后面的代码中不再被引用, 在生存期内, 它的寄存器可以被编译器挪为他用吗? - RednaxelaFX 的回答

活跃分析是一种any-path backward data-flow analysis——信息是从后向前传播的,变量只要在任意路径上是活的那就是活的。

那么JVM的(JIT)编译器在做了活跃分析优化之后,是如何让GC知道某个引用类型的局部变量已经不重要了呢?很简单,通过一种叫做“GC map”的数据结构。请参考这个传送门:

找出栈上的指针/引用

以HotSpot VM为具体例子看题主的代码例子。HotSpot VM里,解释执行的方法可以在任意字节码边界上进入GC,但JIT编译后的代码并不能在“任意位置”进入GC。可以进入GC的“特定位置”叫做“GC safepoint”,或者简称“safepoint”。这些位置是:

  • 主动safepoint:由方法里的代码通过主动轮询去发现需要进入safepoint。有两种情况:
    • 循环回跳处(loop backedge)
    • 临返回处(return)
  • 被动safepoint:调用别的方法的调用点。之所以叫做“被动”是因为并不是该方法主动发现要进入safepoint的,而是某个被调用的方法主动进入了safepoint,导致其整条调用链上的调用者都被动的进入了safepoint。

回头看题主的例子,假设代码里的调用点都没有被内联的话:

       public static void main() {   LargeObject lo = new LargeObject(); // safepoint 1/2: 被动safepoint   lo.doSomeThing();                   // safepoint 3: 被动safepoint    while (true) {     whatever();                       // safepoint 4: 被动safepoint      // 由于循环体里面有未内联的方法调用,也就是说已经有被动safepoint,HotSpot会优化掉循环回跳位置的主动safepoint。假设没有被优化掉的话,此处会有:     // safepoint 5: 主动safepoint:循环回跳轮询(backedge poll)   }   // 上面循环是无限循环,所以下面如果有代码都属于不可到达的代码(unreachable code)    // 如果上面的循环不是无限循环的话,则会有:   // safepoint 6: 主动safepoint:返回前轮询(return poll) }      

简单讲解这6个safepoint分别是什么:

  • safepoint 1是由new带来的safepoint。new字节码要做的事情是给新对象分配空间,在HotSpot VM的JIT编译过的代码里,分配空间分快速路径和慢速路径两边:
    • 快速路径是通过TLAB分配,整个快速路径内联在JIT编译的代码里,不需要做额外的方法调用。如果能成功从TLAB分配到足够空间,则执行完快速路径就好了,否则会进一步进入慢速路径。
    • 慢速路径会调用到JVM runtime里,做若干不同尝试来分配空间。这会做一个VM runtime call,有一个函数调用——safepoint 1就在这个调用的位置上。VM里实现慢速路径的逻辑会尝试分配新的TLAB,或者触发GC清理掉无用对象后再尝试分配空间。由于做GC的决定并不是main()方法自己发现,而是调用进VM runtime里由JVM决定的,所以这个safepoint算被动而不是主动safepoint。
  • safepoint 2是由构造器调用的invokespecial带来的被动safepoint。要留意,Java语言层面的new Object()在字节码里是两条指令,new和invokespecial——new只负责分配空间和做默认初始化,invokespecial才会调用构造器。请参考传送门:实例构造器是不是静态方法?
  • safepoint 3、4都是方法调用的被动safepoint。跟safepoint 2一样,都是调用别的Java方法的调用点上有可能被动进入safepoint。例如说,如果main()方法本身在主动执行的时候并没有发现要进safepoint,而在lo.doSomeThing()执行的时候后者主动发现要进入safepoint的话,那调用者main()也必须进入safepoint,允许GC扫描其栈帧里的引用类型的局部变量。
  • safepoint 5是在循环末尾要跳回到循环开头处(backedge)的主动safepoint。这里HotSpot VM会生成代码检测VM是否发出了通知说要进入GC了,如果有通知的话就会在这个位置进入GC。
  • safepoint 6是在临返回前(return)的主动safepoint。跟上一个一样,也会有代码主动去检测是否要GC。

再多废话几句:大家都知道System.gc()可以让Java代码主动触发GC,但从HotSpot VM的角度看,讨论safepoint与OopMap时,System.gc()的调用点其实是个“被动safepoint”。

专门指出这个是为了避免读者误会这里说的主动/被动。强调一下,这里说的:

  • 主动:有显式代码轮询是否要GC;
  • 被动:在一个未内联的调用点,为被调用方法可能进入GC而做好准备,调用方并没有主动轮询是否要GC的代码。

既然只有这些safepoint的地方可能进入GC,就JVM只需要在这些地方提高足够信息让GC知道栈帧里什么位置有引用类型的局部变量。在HotSpot VM里,这种“信息”——前面说的“GC map”——通过名为OopMap的数据结构记录。关于OopMap具体是个怎样的东西,请参考前面提到过的传送门:

找出栈上的指针/引用

回到题主的例子,6个safepoint对应的OopMap信息分别会是:

       public static void main() {   LargeObject lo = new LargeObject(); // safepoint 1/2: 被动safepoint   // OopMap 1: 空。   // 该栈帧里尚未有任何引用类型的局部变量是活跃的——new还没执行完   // OopMap 2: 记录了一个变量:刚分配的对象的引用是活的,在OopMap里;不过这还不是局部变量lo而是求值栈上的一个slot   lo.doSomeThing();                   // safepoint 3: 被动safepoint   // OopMap 3: 空。局部变量lo的最后一次使用是作为上面方法的"this"参数传递出去;   // 维持"this"的存活是被调用方法的责任而不是调用方法的责任。此后局部变量lo再也没有被使用过,所以对main()来说lo在此处已死。    while (true) {     whatever();                       // safepoint 4: 被动safepoint     // OopMap 4: 空。      // safepoint 5: 主动safepoint:循环回跳轮询(backedge poll)     // OopMap 5: 空。   }   // 上面循环是无限循环,所以下面如果有代码都属于不可到达的代码(unreachable code)    // 如果上面的循环不是无限循环的话,则:   // safepoint 6: 主动safepoint:返回前轮询(return poll)   // OopMap 6: 空 }      

这样,编译器通过活跃分析得知lo具体被使用的范围,就可以向OopMap填入相应的信息,让局部变量'lo'只在需要存活的时候被GC扫描到——只要OopMap没有记录它的存在,GC就不会扫描它,而它所引用的对象就不会因为'lo'这个局部变量而被认为是活的,因而有机会被回收(假如没有其它活引用继续指向那个LargeObject对象的话。)

为了证明以上描述不是忽悠,下面给出HotSpot VM在上例各safepoint具体计算出的OopMap情况。测试代码、具体方法和完整日志附在本回答的最后,仅供参考。

safepoint 1:

       087    call,static  wrapper for: _new_instance_Java         # LargeObject::main @ bci:0  L[0]=_         # OopMap{off=140}      

这里L[0]=_的意思是局部变量区slot 0还没有有效值(以'_'表示)。

OopMap里没有记录任何Java代码中能看到的引用,所以上面描述为“空”是正确的。off=140指的是该OopMap关联的指令在生成代码中的相对方法起始地址的偏移量。

safepoint 2:

       05b    call,static  LargeObject::<init>         # LargeObject::main @ bci:4  L[0]=_ STK[0]=RBP         # OopMap{rbp=Oop off=96}      

这里L[0]仍然为'_',因为刚创建的对象的引用尚未赋值给局部变量'lo'。但是有一个活的引用需要记录在OopMap里:STK[0]是求值栈的栈顶,此时持有新创建的对象的引用。

OopMap里记录下了rbp=Oop,就是说rbp寄存器此时持有一个GC需要扫描的对象引用。

safepoint 3:

       063    call,static  LargeObject::doSomeThing         # LargeObject::main @ bci:9  L[0]=_         # OopMap{off=104}      

不用多解释了。对JIT编译器来说,局部变量'lo'的生命期到此已结束,因而L[0]又是'_'。OopMap又是空的。

safepoint 4:

       073    call,static  LargeObject::whatever         # LargeObject::main @ bci:12  L[0]=_         # OopMap{off=120}      

跟上一个safepoint一样。

safepoint 5和6实际并不存在,所以也没有对应的日志。

===================================================

上面的描述跟题主后面举的两种“优化”形式的代码例子其实都不一样。

         public static void main() {   LargeObject lo = new LargeObject();   lo.doSomeThing();   lo = null;    while (true) {     whatever();   } }       

HotSpot VM的JIT编译器做的优化,硬要说的话效果跟这个类似。这个是显式把局部变量'lo'置为null从而切断其对LargeObject对象的引用,而实际发生的状况是JIT编译器在doSomething()调用之后就不把局部变量'lo'包含在OopMap里了,于是GC根本看不到这个变量,也不关心它引用了谁,自然的切断了引用。

这个显式置null版本的Java代码,在实际运行中能够可靠的切断局部变量'lo'对对象的引用,其实有时候还是可以推荐用的——虽然它对JIT编译的代码不会有任何额外好处——但并不是所有Java方法都会被JIT编译。如果有个Java方法没有被JIT编译但里面仍然有代码会执行比较长时间,那么在那段会执行长时间的代码前显式将不需要的引用类型局部变量置null是可取的,可以谨慎使用。

===================================================

而后面一个版本:

         public static void main() {   {     LargeObject lo = new LargeObject();     lo.doSomeThing();   }    while (true) {     whatever();   } }       

从Java语言层面的作用域看似乎跟前面说的“活跃分析”达到了一样的效果——限制了局部变量'lo'的可见范围。但在实际执行的层面上这里却有个陷阱:

JVM规范并没有对GC的行为、JVM与GC的交互做多少规定,也没有讨论过局部变量在离开作用域之后JVM要如何处理它。这些都给JVM的实现留下了自由度——换句话说,JVM实现并没有义务在一个引用类型的局部变量离开作用域之后把它置为null。

这就允许了HotSpot VM在解释模式里有略为奇怪的表现。请看下面例子:

       public class TestGC {   public static void main(String[] args) {     {       // 'o' in local variable slot 1       Object o = new byte[4*1024*1024];       o.hashCode();     }     // o is out of scope now, but...     System.gc(); // the 4M byte[] is kept alive in this GC     {       // 'i' in local variable slot 1       int i = 0; // "null out" local variable slot 1     }     System.gc(); // the 4M byte[] is freed in this GC   } }      

用Oracle JDK8来运行它:

       $ java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode) $ java -XX:+PrintGCDetails -XX:+UseSerialGC -Xmx20m -Xmn10m TestGC [Full GC (System.gc()) [Tenured: 0K->4530K(10240K), 0.0041590 secs] 5098K->4530K(19456K), [Metaspace: 2716K->2716K(1056768K)], 0.0041940 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]  [Full GC (System.gc()) [Tenured: 4530K->434K(10240K), 0.0014000 secs] 4530K->434K(19456K), [Metaspace: 2716K->2716K(1056768K)], 0.0014180 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  Heap  def new generation   total 9216K, used 246K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)   eden space 8192K,   3% used [0x00000007bec00000, 0x00000007bec3d8e0, 0x00000007bf400000)   from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)   to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)  tenured generation   total 10240K, used 434K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)    the space 10240K,   4% used [0x00000007bf600000, 0x00000007bf66c928, 0x00000007bf66ca00, 0x00000007c0000000)  Metaspace       used 2726K, capacity 4486K, committed 4864K, reserved 1056768K   class space    used 298K, capacity 386K, committed 512K, reserved 1048576K      

可以看到第一次调用System.gc()的时候,例子里创建的4M大的byte[]对象并没有被GC掉,而是晋升到old gen了。

此时明明已经离开了局部变量'o'的作用域,而那个4M大的byte[]只被这个局部变量引用,为何HotSpot VM的GC没能回收它?

——因为HotSpot VM的解释器与GC之间的交互有点傻:当被解释执行的方法需要被GC时,计算OopMap的逻辑并没有彻底计算变量的liveness,导致已经无用的局部变量的生命期可能超过其在源码中的作用域范围。

看这个例子对应的字节码:

         public static void main(java.lang.String[]);     Code:       stack=1, locals=2, args_size=1          0: ldc           #2                  // int 4194304          2: newarray       byte          4: astore_1                5: aload_1                 6: invokevirtual #3                  // Method java/lang/Object.hashCode:()I          9: pop                    10: invokestatic  #4                  // Method java/lang/System.gc:()V         13: iconst_0               14: istore_1               15: invokestatic  #4                  // Method java/lang/System.gc:()V         18: return               LineNumberTable:         line 4: 0         line 5: 5         line 7: 10         line 9: 13         line 11: 15         line 12: 18       LocalVariableTable:         Start  Length  Slot  Name   Signature                5       5     1     o   Ljava/lang/Object;               15       0     1     i   I                0      19     0  args   [Ljava/lang/String;      

然后通过在一个debug build的HotSpot VM上用 -XX:+TraceNewOopMapGenerationDetailed 参数来跟踪解释器计算OopMap的结果:

       java -XX:+TraceNewOopMapGenerationDetailed -XX:+PrintGCDetails -XX:+UseSerialGC -Xmx20m -Xmn10m TestGC     

相关日志:

       static void TestGC.main(jobject)          0 vars     = ( r  |slot0)(   u|Top)    ldc           stack    =            monitors =          2 vars     = ( r  |slot0)(   u|Top)    newarray           stack    = (  v |Top)           monitors =          4 vars     = ( r  |slot0)(   u|Top)    astore_1           stack    = ( r  |line2)           monitors =          5 vars     = ( r  |slot0)( r  |line2)    aload_1           stack    =            monitors =          6 vars     = ( r  |slot0)( r  |line2)    invokevirtual()I           stack    = ( r  |line2)           monitors =          9 vars     = ( r  |slot0)( r  |line2)    pop           stack    = (  v |Top)           monitors =         10 vars     = ( r  |slot0)( r  |line2)    invokestatic()V           stack    =            monitors =         13 vars     = ( r  |slot0)( r  |line2)    iconst_0           stack    =            monitors =         14 vars     = ( r  |slot0)( r  |line2)    istore_1           stack    = (  v |Top)           monitors =         15 vars     = ( r  |slot0)(  v |Top)    invokestatic()V           stack    =            monitors =         18 vars     = ( r  |slot0)(  v |Top)    return           stack    =            monitors =        

其中这行日志:

       10 vars     = ( r  |slot0)( r  |line2)    invokestatic()V      

说明这个计算OopMap的逻辑认为在第一次调用System.gc()的地方,局部变量区的slot 0(String[] args)与slot 1(Object o)都还是活的引用类型变量(日志中的标记'r'表示reference)。前面提到了,HotSpot VM并不会在局部变量离开作用域之后对其做显式的清理动作,所以此时slot 1里的值还是指向4M byte[]的引用,导致该数组对象在第一次调用System.gc()时没能被回收。

而第二次调用System.gc()之前,例子通过对复用slot 1的int i变量赋值来达到了“清理引用”的效果,到真正第二次调用System.gc()的地方,计算出来的OopMap是:

       15 vars     = ( r  |slot0)(  v |Top)    invokestatic()V      

这里就只有slot 0还是活引用(标记'r')了,slot 1变为了GC不关心的内容(标记'v'表示void)。

上面例子演示了HotSpot VM在解释执行方法时可能会超越局部变量的静态作用域持有无用的引用,从而有可能隐式延长了对象的存活时间。这恐怕是题主以及许多用HotSpot VM跑Java程序的同学意想不到的行为吧。

===================================================

附录

测试代码:

       public class LargeObject {   public void doSomeThing() { }   public static void whatever() { }    public static void main() {     LargeObject lo = new LargeObject();     lo.doSomeThing();      while (true) {       whatever();     }   }    public static void main(String[] args) throws Exception {     main();   } }      

Java版本:Mac OS X上的Oracle JDK8 / OpenJDK8

       java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) OpenJDK 64-Bit Server VM (build 25.0-b66-internal-fastdebug, mixed mode)      

命令行参数:

       java -XX:+PrintOptoAssembly -XX:-TieredCompilation -Xcomp -XX:CompileCommand=compileonly,LargeObject,main -XX:CompileCommand=dontinline,LargeObject,main -XX:+PrintAssembly LargeObject      

-XX:+PrintOptoAssembly的日志:

       {method}  - this oop:          0x0000000119686458  - method holder:     'LargeObject'  - constants:         0x0000000119686060 constant pool [35] {0x0000000119686060} for 'LargeObject' cache=0x0000000119686580  - access:            0xc1000009  public static   - name:              'main'  - signature:         '()V'  - max stack:         3  - max locals:        1  - size of params:    0  - method size:       12  - vtable index:      -2  - i2i entry:         0x000000010c7f16e0  - adapters:          AHE@0x00007fad0a8600c8: 0x i2c: 0x000000010c8d9820 c2i: 0x000000010c8d9930 c2iUV: 0x000000010c8d9903  - compiled entry     0x000000010c8d9930  - code size:         18  - code start:        0x0000000119686430  - code end (excl):   0x0000000119686442  - method data:       0x00000001196866f0  - checked ex length: 0  - linenumber start:  0x0000000119686442  - localvar length:   1  - localvar start:    0x000000011968644a # #  void (  ) # # -- Old rsp -- Framesize: 32 -- #r191 rsp+28: in_preserve #r190 rsp+24: return address #r189 rsp+20: in_preserve #r188 rsp+16: saved fp register #r187 rsp+12: pad2, stack alignment #r186 rsp+ 8: pad2, stack alignment #r185 rsp+ 4: Fixed slot 1 #r184 rsp+ 0: Fixed slot 0 # abababab   N1: # B1 <- B13  Freq: 1 abababab 000   B1: # B7 B2 <- BLOCK HEAD IS JUNK   Freq: 1 000    # stack bang  pushq   rbp # Save rbp  subq    rsp, #16 # Create frame  00c    # TLS is in R15 00c    movq    RAX, [R15 + #120 (8-bit)] # ptr 010    movq    R10, RAX # spill 013    addq    R10, #16 # ptr 017    cmpq    R10, [R15 + #136 (32-bit)] # raw ptr 01e    jnb,us  B7  P=0.000100 C=-1.000000 01e 020   B2: # B3 <- B1  Freq: 0.9999 020    movq    [R15 + #120 (8-bit)], R10 # ptr 024    PREFETCHNTA [R10 + #192 (32-bit)] # Prefetch allocation to non-temporal cache for write 02c    movl    R11, narrowklass: precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact * # compressed klass ptr 032    decode_klass_not_null R10,R11 040    movq    R10, [R10 + #176 (32-bit)] # ptr 047    movq    [RAX], R10 # ptr 04a    movl    [RAX + #8 (8-bit)], narrowklass: precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact * # compressed klass ptr 051    movl    [RAX + #12 (8-bit)], R12 # int (R12_heapbase==0) 051 055   B3: # B11 B4 <- B8 B2  Freq: 1 055     055    movq    RBP, RAX # spill 058    # checkcastPP of RBP 058    movq    RSI, RBP # spill 05b    call,static  LargeObject::<init>         # LargeObject::main @ bci:4  L[0]=_ STK[0]=RBP         # OopMap{rbp=Oop off=96} 060 060   B4: # B10 B5 <- B3  Freq: 0.99998         # Block is sole successor of call 060    movq    RSI, RBP # spill 063    call,static  LargeObject::doSomeThing         # LargeObject::main @ bci:9  L[0]=_         # OopMap{off=104}        nop  # 8 bytes pad for loops and calls  070   B5: # B12 B6 <- B4 B6  Loop: B5-B6 inner  Freq: 99996        nop  # 3 bytes pad for loops and calls 073    call,static  LargeObject::whatever         # LargeObject::main @ bci:12  L[0]=_         # OopMap{off=120} 078 078   B6: # B5 <- B5  Freq: 99994         # Block is sole successor of call 078    jmp,s   B5 078 07a   B7: # B9 B8 <- B1  Freq: 0.000100017 07a    movq    RSI, precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact * # ptr        nop  # 3 bytes pad for loops and calls 087    call,static  wrapper for: _new_instance_Java         # LargeObject::main @ bci:0  L[0]=_         # OopMap{off=140} 08c 08c   B8: # B3 <- B7  Freq: 0.000100015         # Block is sole successor of call 08c    jmp,s   B3 08c 08e   B9: # B13 <- B7  Freq: 1.00017e-09 08e    # exception oop is in rax; no code emitted 08e    movq    RSI, RAX # spill 091    jmp,s   B13 091 093   B10: # B13 <- B4  Freq: 9.9998e-06 093    # exception oop is in rax; no code emitted 093    movq    RSI, RAX # spill 096    jmp,s   B13 096 098   B11: # B13 <- B3  Freq: 1e-05 098    # exception oop is in rax; no code emitted 098    movq    RSI, RAX # spill 09b    jmp,s   B13 09b 09d   B12: # B13 <- B5  Freq: 0.99996 09d    # exception oop is in rax; no code emitted 09d    movq    RSI, RAX # spill 09d 0a0   B13: # N1 <- B9 B11 B10 B12  Freq: 0.99998 0a0    addq    rsp, 16 # Destroy frame  popq   rbp  0a5    jmp     rethrow_stub 0a5      

-XX:+PrintAssembly的日志:

       Decoding compiled method 0x000000010c938950: Code: [Entry Point] [Verified Entry Point] [Constants]   # {method} {0x0000000119686458} 'main' '()V' in 'LargeObject'   #           [sp+0x20]  (sp of caller)   ;; N1: # B1 <- B13  Freq: 1    ;; B1: # B7 B2 <- BLOCK HEAD IS JUNK   Freq: 1    0x000000010c938ac0: mov    %eax,-0x16000(%rsp)   0x000000010c938ac7: push   %rbp   0x000000010c938ac8: sub    $0x10,%rsp         ;*synchronization entry                                                 ; - LargeObject::main@-1 (line 6)    0x000000010c938acc: mov    0x78(%r15),%rax   0x000000010c938ad0: mov    %rax,%r10   0x000000010c938ad3: add    $0x10,%r10   0x000000010c938ad7: cmp    0x88(%r15),%r10   0x000000010c938ade: jae    0x000000010c938b3a   ;; B2: # B3 <- B1  Freq: 0.9999    0x000000010c938ae0: mov    %r10,0x78(%r15)   0x000000010c938ae4: prefetchnta 0xc0(%r10)   0x000000010c938aec: mov    $0xf800c006,%r11d  ;   {metadata('LargeObject')}   0x000000010c938af2: movabs $0x0,%r10   0x000000010c938afc: lea    (%r10,%r11,8),%r10   0x000000010c938b00: mov    0xb0(%r10),%r10   0x000000010c938b07: mov    %r10,(%rax)   0x000000010c938b0a: movl   $0xf800c006,0x8(%rax)  ;   {metadata('LargeObject')}   0x000000010c938b11: mov    %r12d,0xc(%rax)   ;; B3: # B11 B4 <- B8 B2  Freq: 1    0x000000010c938b15: mov    %rax,%rbp          ;*new  ; - LargeObject::main@0 (line 6)    0x000000010c938b18: mov    %rbp,%rsi   0x000000010c938b1b: callq  0x000000010c8d8de0  ; OopMap{rbp=Oop off=96}                                                 ;*invokespecial <init>                                                 ; - LargeObject::main@4 (line 6)                                                 ;   {optimized virtual_call}   ;; B4: # B10 B5 <- B3  Freq: 0.99998    0x000000010c938b20: mov    %rbp,%rsi   0x000000010c938b23: callq  0x000000010c8d8de0  ; OopMap{off=104}                                                 ;*invokevirtual doSomeThing                                                 ; - LargeObject::main@9 (line 7)                                                 ;   {optimized virtual_call}   0x000000010c938b28: nop   0x000000010c938b29: nop   0x000000010c938b2a: nop   0x000000010c938b2b: nop   0x000000010c938b2c: nop   0x000000010c938b2d: nop   0x000000010c938b2e: nop   0x000000010c938b2f: nop   ;; B5: # B12 B6 <- B4 B6  Loop: B5-B6 inner  Freq: 99996    0x000000010c938b30: nop   0x000000010c938b31: nop   0x000000010c938b32: nop   0x000000010c938b33: callq  0x000000010c8d91e0  ; OopMap{off=120}                                                 ;*invokestatic whatever                                                 ; - LargeObject::main@12 (line 10)                                                 ;   {static_call}   ;; B6: # B5 <- B5  Freq: 99994    0x000000010c938b38: jmp    0x000000010c938b30   ;; B7: # B9 B8 <- B1  Freq: 0.000100017    0x000000010c938b3a: movabs $0x7c0060030,%rsi  ;   {metadata('LargeObject')}   0x000000010c938b44: nop   0x000000010c938b45: nop   0x000000010c938b46: nop   0x000000010c938b47: callq  0x000000010c8d93e0  ; OopMap{off=140}                                                 ;*new  ; - LargeObject::main@0 (line 6)                                                 ;   {runtime_call}   ;; B8: # B3 <- B7  Freq: 0.000100015    0x000000010c938b4c: jmp    0x000000010c938b15  ;*new                                                 ; - LargeObject::main@0 (line 6)    ;; B9: # B13 <- B7  Freq: 1.00017e-09    0x000000010c938b4e: mov    %rax,%rsi   0x000000010c938b51: jmp    0x000000010c938b60  ;*invokevirtual doSomeThing                                                 ; - LargeObject::main@9 (line 7)    ;; B10: # B13 <- B4  Freq: 9.9998e-06    0x000000010c938b53: mov    %rax,%rsi   0x000000010c938b56: jmp    0x000000010c938b60  ;*invokespecial <init>                                                 ; - LargeObject::main@4 (line 6)    ;; B11: # B13 <- B3  Freq: 1e-05    0x000000010c938b58: mov    %rax,%rsi   0x000000010c938b5b: jmp    0x000000010c938b60  ;*invokestatic whatever                                                 ; - LargeObject::main@12 (line 10)    ;; B12: # B13 <- B5  Freq: 0.99996    0x000000010c938b5d: mov    %rax,%rsi          ;*new  ; - LargeObject::main@0 (line 6)    ;; B13: # N1 <- B9 B11 B10 B12  Freq: 0.99998    0x000000010c938b60: add    $0x10,%rsp   0x000000010c938b64: pop    %rbp   0x000000010c938b65: jmpq   0x000000010c907da0  ;   {runtime_call}   0x000000010c938b6a: hlt       0x000000010c938b6b: hlt       0x000000010c938b6c: hlt       0x000000010c938b6d: hlt       0x000000010c938b6e: hlt       0x000000010c938b6f: hlt       0x000000010c938b70: hlt       0x000000010c938b71: hlt       0x000000010c938b72: hlt       0x000000010c938b73: hlt       0x000000010c938b74: hlt       0x000000010c938b75: hlt       0x000000010c938b76: hlt       0x000000010c938b77: hlt       0x000000010c938b78: hlt       0x000000010c938b79: hlt       0x000000010c938b7a: hlt       0x000000010c938b7b: hlt       0x000000010c938b7c: hlt       0x000000010c938b7d: hlt       0x000000010c938b7e: hlt       0x000000010c938b7f: hlt     [Stub Code]   0x000000010c938b80: movabs $0x0,%rbx          ;   {no_reloc}   0x000000010c938b8a: jmpq   0x000000010c938b8a  ;   {runtime_call}   0x000000010c938b8f: movabs $0x0,%rbx          ;   {static_stub}   0x000000010c938b99: jmpq   0x000000010c938b99  ;   {runtime_call}   0x000000010c938b9e: movabs $0x0,%rbx          ;   {static_stub}   0x000000010c938ba8: jmpq   0x000000010c938ba8  ;   {runtime_call} [Exception Handler]   0x000000010c938bad: jmpq   0x000000010c9076e0  ;   {runtime_call} [Deopt Handler Code]   0x000000010c938bb2: callq  0x000000010c938bb7   0x000000010c938bb7: subq   $0x5,(%rsp)   0x000000010c938bbc: jmpq   0x000000010c8d9da0  ;   {runtime_call}   0x000000010c938bc1: hlt       0x000000010c938bc2: hlt       0x000000010c938bc3: hlt       0x000000010c938bc4: hlt       0x000000010c938bc5: hlt       0x000000010c938bc6: hlt       0x000000010c938bc7: hlt          

类似的话题

  • 回答
    这段 Java 代码中的局部变量,理论上确实存在被提前回收的可能性。不过,这里的“提前回收”并非我们直观理解的,在代码执行完毕前就完全从内存中消失。更准确的说法是,这些局部变量的内存占用可以在其生命周期结束后,但不等到方法执行结束就被JVM判定为“无用”,从而有机会被垃圾回收器(Garbage Co.............
  • 回答
    在分析这段视频中的驾驶行为是否违规之前,我们需要仔细观察视频画面。请您提供视频内容,我需要看到视频中的具体情况才能给出详细的判断。一旦我看到了视频,我会重点关注以下几个方面来判断我和警车谁的驾驶行为更可能违反交通规则:对于我的驾驶行为,我会关注: 车速: 我的车速是否超过了该路段的最高限速?或者.............
  • 回答
    这个问题虽然问得有些挑战性,但绝非无法解答。我们仔细解读这段文本,就能找出答案并进行有条理的翻译。首先,为了准确回答“谁拜祭老汗的坟,还像儿孙一样孝敬?”,我们需要深入分析文本内容。请您提供这段文本。一旦文本内容明确,我就可以:1. 识别关键人物和事件: 我会仔细查找文本中提到的人物,特别是与“老.............
  • 回答
    在你的代码片段中,关于堆栈空间的释放,我来详细说一下。首先,我们要明确一点:堆栈(Stack)空间的主要释放是由编译器和函数调用机制自动完成的,我们通常不需要手动去“释放”堆栈空间。 这与堆(Heap)内存的动态分配(使用 `malloc`, `new` 等)需要手动释放(使用 `free`, `d.............
  • 回答
    好的,我们来仔细分析一下您提到的这篇文章。由于我无法直接看到文章内容,我将基于对“比基尼”和“伊斯兰女性服饰”这两个主题的普遍认知,以及通常在探讨这些话题时容易出现的“不妥之处”,来为您提供一个详细的分析框架。请您在阅读我的分析时,对照您手头的文章,看看哪些地方是吻合的。潜在的不妥之处及详细分析:1.............
  • 回答
    您好!非常乐意为您翻译这段梵语。不过,您需要将梵语的文字提供给我。请您将想要翻译的梵语内容复制粘贴给我。一旦您提供了梵语文本,我会尽我所能为您进行详细、准确的翻译。同时,我也会注意用更加自然、有温度的语言来解释,避免听起来像是机器生成的,让您感受到翻译的深度和文化韵味。请您放心,我在这里是为了提供帮.............
  • 回答
    这篇文章的论证,我仔细读过后,感觉有点像是绕着一个结论打转,但中间的联系似乎不够紧密,甚至有些地方像是想当然了。首先,文章开篇就抛出了一个非常明确的观点,似乎是基于某种先验的认识,但紧接着为了支撑这个观点,列举的理由和论据之间,我总觉得少了那么点“因为所以”的清晰过程。就好比一个人先说“天下雨了”,.............
  • 回答
    我理解你此刻心中一定充满了困惑和一丝丝的失落。一段感情走到需要被审视的地步,往往不是一个单一的问题在作祟,而是像织毛衣一样,错综复杂的线索交织在一起,慢慢地,有些地方就起了毛球,有些针脚开始松散,最终影响了整体的模样。你说“这段感情到底是出了什么问题呢?”这个问题本身就带着一种寻找根源的急切。我们不.............
  • 回答
    这感情走到这一步,心里一定不好受吧。想要补救,首先得弄清楚,到底是什么地方出了问题,让你们的关系变得如此艰难。有时候,一句无心的话,一个被忽略的细节,都会像滚雪球一样,越积越多,最终压垮一段感情。你们最近有没有过深入的交流?不是那种日常琐碎的对话,而是真正坐下来,坦诚地聊聊彼此心里的感受?试着回忆一.............
  • 回答
    这段C++代码是否构成未定义行为(Undefined Behavior, UB)?要准确回答这个问题,我们需要逐行分析代码,并结合C++标准对相关规则的阐述。首先,让我们假设有这么一段代码作为讨论的基础。请注意,由于您没有提供具体的代码片段,我将构建一个示例来演示如何分析“未定义行为”。如果您的代码.............
  • 回答
    好的,我来帮你解读一下这段代码。不过,在开始之前,我们需要明确一点:任何代码,尤其是那些声称能让你“月入过万”的代码,都存在风险,并且绝非“躺赚”的捷径。 它们通常需要你投入大量的时间、精力、学习,甚至可能存在一定的风险。先别急着相信那些“轻松月入过万”的宣传,我们先看看这段代码到底是怎么运作的。请.............
  • 回答
    请您提供那段阿拉米语的文字。在您提供文字后,我会尽力为您进行详细的解读。我的目标是让您了解这段文字在文化、历史、宗教或日常生活中的含义,并提供尽可能多的背景信息,让您对它有一个深入的理解。我会避免使用那些听起来过于生硬、机械化或重复的AI常用语。请放心,我会以一种自然、流畅且富有信息量的方式来回应您.............
  • 回答
    这确实是个让人纠结的境地,站在当下这个时间点,很多事情都让人心里七上八下的。你现在问“该怎么做”,这说明你内心已经涌现出很多想法,但又拿不定主意,希望听听别人的声音,看看能不能从不同的角度给你一些启发。首先,我想说,感情这东西,从来就没有标准答案,每个人的情况都独一无二。所以,与其在这里寻求一个“正.............
  • 回答
    听到你这么说,我能感受到你此刻内心的纠结和迷茫,面对一段感情,不知道该何去何从,这绝对不是件容易的事。 我理解你不想用冰冷的列表来梳理,更希望用一种更贴近人心的、更像朋友之间交流的方式来谈谈。首先,我想说,你感到不知所措,这本身就是一件很正常的事情。 感情就像一条蜿蜒的河流,有时候平静安宁,有时候.............
  • 回答
    要评价一段文言文的翻译是否值得“吹捧”,我们需要从多个维度进行细致的分析。这不仅仅是看字面意思是否准确,更重要的是看翻译是否传达了原文的神韵、意境、情感以及作者的写作风格。首先,请您提供您想要我评价的文言文原文以及对应的翻译。在您提供原文和翻译之后,我会从以下几个方面进行详细的分析和评价:一、 忠实.............
  • 回答
    图片中这段文字里汉语中夹杂的英文是否是习惯使然还是为了装,这个问题非常普遍,也很有讨论的空间。要详细地分析这个问题,我们需要从几个层面来看待:一、 夹杂英文的普遍原因:首先,汉语中夹杂英文并非是单一原因造成的,而是多种因素共同作用的结果。我们可以从以下几个方面来理解:1. 语言的自然演变与融合: .............
  • 回答
    在《最后生还者2》中,玩家们对于部分剧情的解读存在分歧,其中关于是否包含“歧视亚裔”的内容,是一个比较复杂且敏感的话题。要详细阐述这一点,我们需要梳理一下相关的剧情片段,并分析玩家们为何会产生这样的联想。首先,最常被提及的剧情点,与艾莉(Ellie)和艾比(Abby)的恩怨纠葛有关。游戏后半段,玩家.............
  • 回答
    您提到的这段据称是国航飞行员与地面塔台的对话,具体内容是什么呢?如果没有具体的对话文本,我很难对其进行详细的评价,也无法判断其中是否存在飞行员英语能力不足的情况。不过,我可以就您提出的几个问题,从更宏观的角度来谈谈:关于对话内容和飞行员英语能力判断: 塔台对话的严谨性: 空管塔台与飞机的通讯是整.............
  • 回答
    汉服爱好者创作的这段文字,在很大程度上是对历史的一种解读和推广,但并非完全等同于严谨的、经过多方考证的“真实历史”陈述。 换句话说,它可能包含着历史事实的成分,但夹杂着情感、愿望、以及对历史的特定视角,这些可能导致其在细节、准确性、或者整体呈现上与纯粹的历史学术研究有所不同。要详细地讲述这个问题,我.............
  • 回答
    挽回爱情这件事,就像是要在破碎的瓷器上重新描绘金线,既需要技巧,更需要耐心和真心。它绝不是一件简单的事情,也不是一蹴而就的魔法,但如果真的还爱着,并且觉得这段感情值得争取,那不妨试试看。第一步:冷静期,给自己也给对方空间分手后最冲动的事情就是立刻去纠缠、哭闹、质问。拜托,这时候你们两个人都伤得很重,.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有