当前位置: 首页 > article >正文

JVM常用概念之局部变量可达性

问题

存储在局部变量中的引用在越界后会被收集吗?

基础知识

C/C++编程语言中存储在局部变量中的引用在越界后会被收集吗?

在C/C++编程语言中,明确声明为 auto 或 register 或未明确声明为 static 或 extern 的本地对象具有自动存储期限。这些对象的存储持续到创建它们的块退出为止。

自动存储期(Automatic Storage Duration)‌

这种存储期的对象在栈内存中分配,通常用于函数内部的局部变量。当函数执行结束,变量的作用域结束时,其存储空间会自动释放。

静态存储期(Static Storage Duration)‌

这种存储期的对象具有全局或静态的作用域,通常用于全局变量或使用static关键字声明的变量。这些变量在整个程序运行期间都存在,直到程序结束时才会被释放。例如,全局变量在程序启动前进行初始化,并在程序结束时才被释放‌。

线程存储期(Thread Storage Duration)‌

这种存储期的对象与线程的生命周期相关联,它在线程开始时初始化,并在线程结束时被释放。使用thread_local关键字声明的变量属于这种存储期。例如,线程局部存储的变量仅在创建它的线程中有效,其他线程无法直接访问‌。

如果命名自动对象具有初始化或具有析构函数,则不得在其块结束之前将其销毁,也不应将其作为优化消除,即使它看起来未被使用 — C++98 Standard

void method() {
  ...something...

  {
     MutexLocker ml(mutex);
     ...something under the lock...
  } // ~MutexLocker unlocks

  ...something else...
}

Java编程语言中存储在局部变量中的引用在越界后会被收集吗?

Java 没有析构函数,但有方法可以检测对象是否被视为不可访问,并采取相应措施,例如通过soft/weak/PhantomReference或finalizers。然而,Java 中的语法代码块并不是这样运作的。

可以设计程序的优化转换,将可访问的对象数量减少到少于那些天真地认为可访问的对象的数量。例如,Java 编译器或代码生成器可以选择将不再使用的变量或参数设置为 null,以使此类对象的存储空间可以更快地被回收。— Java Language Specification 8

实验

源码

package jvm;

public class LocalFinalize {

    private static volatile boolean flag;

    static {
        new Thread(() -> {
            try {
                while (true) {
                    System.gc();
                    Thread.sleep(1000);
                }
            } catch (Throwable t) {}
        }).start();
    }

    public static void arm() {
        new Thread(() -> {
            try {
                 Thread.sleep(5000);
                 flag = false;
            } catch (Throwable t) {}
        }).start();
    }

    public static void main(String... args) throws InterruptedException {
        System.out.println("Pass 1");
        arm();
        flag = true;
        pass();

        System.out.println("Wait for pass 1 finalization");
        Thread.sleep(10000);

        System.out.println("Pass 2");
        flag = true;
        pass();
    }

    public static void pass() {
        MyHook h1 = new MyHook();
        MyHook h2 = new MyHook();

        while (flag) {
            // spin
        }

        h1.log();
    }

    public static class MyHook {
       public MyHook() {
           System.out.println("Created " + this);
       }

       public void log() {
           System.out.println("Alive " + this);
       }

       @Override
       protected void finalize() throws Throwable {
           System.out.println("Finalized " + this);
       }
    }

}

运行结果

Pass 1
Created jvm.LocalFinalize$MyHook@2d98a335
Created jvm.LocalFinalize$MyHook@16b98e56
Finalized jvm.LocalFinalize$MyHook@16b98e56
Alive jvm.LocalFinalize$MyHook@2d98a335
Wait for pass 1 finalization
Finalized jvm.LocalFinalize$MyHook@2d98a335
Pass 2
Created jvm.LocalFinalize$MyHook@7ef20235
Created jvm.LocalFinalize$MyHook@27d6c5e0
Finalized jvm.LocalFinalize$MyHook@27d6c5e0

发生这种情况是因为优化编译器知道h2的最后一次使用是在分配之后。因此,在将当前活动变量(稍后在循环执行期间)传达给垃圾收集器时,它不再认为h2是活动的。因此,垃圾收集器将该MyHook实例视为已死并运行其终结。由于h1的使用是在循环之后,因此它被视为可访问的,并且终结是静默的。
这主要是因为JVM允许 GC 可以回收本地分配的巨大缓冲区而无需退出该方法。

void processAndWait() {
  byte[] buf = new byte[1024 * 1024];
  writeToBuf(buf);
  processBuf(buf); // last use!
  waitForTheDeathOfUniverse(); // oops
}

汇编

Classfile /Users/nanxiaotao/IdeaProjects/TaoKang-JVM/target/classes/jvm/LocalFinalize.class
  Last modified Mar 5, 2025; size 2083 bytes
  MD5 checksum 664332dbab7056700222d8c9713f0772
  Compiled from "LocalFinalize.java"
public class jvm.LocalFinalize
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    #1 = Methodref          #28.#59       // java/lang/Object."<init>":()V
    #2 = Class              #60           // java/lang/Thread
    #3 = InvokeDynamic      #0:#65        // #0:run:()Ljava/lang/Runnable;
    #4 = Methodref          #2.#66        // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    #5 = Methodref          #2.#67        // java/lang/Thread.start:()V
    #6 = Fieldref           #68.#69       // java/lang/System.out:Ljava/io/PrintStream;
    #7 = String             #70           // Pass 1
    #8 = Methodref          #71.#72       // java/io/PrintStream.println:(Ljava/lang/String;)V
    #9 = Methodref          #27.#73       // jvm/LocalFinalize.arm:()V
   #10 = Fieldref           #27.#74       // jvm/LocalFinalize.flag:Z
   #11 = Methodref          #27.#75       // jvm/LocalFinalize.pass:()V
   #12 = String             #76           // Wait for pass 1 finalization
   #13 = Long               10000l
   #15 = Methodref          #2.#77        // java/lang/Thread.sleep:(J)V
   #16 = String             #78           // Pass 2
   #17 = Class              #79           // jvm/LocalFinalize$MyHook
   #18 = Methodref          #17.#59       // jvm/LocalFinalize$MyHook."<init>":()V
   #19 = Methodref          #17.#80       // jvm/LocalFinalize$MyHook.log:()V
   #20 = Long               5000l
   #22 = Class              #81           // java/lang/Throwable
   #23 = Methodref          #68.#82       // java/lang/System.gc:()V
   #24 = Long               1000l
   #26 = InvokeDynamic      #1:#65        // #1:run:()Ljava/lang/Runnable;
   #27 = Class              #84           // jvm/LocalFinalize
   #28 = Class              #85           // java/lang/Object
   #29 = Utf8               MyHook
   #30 = Utf8               InnerClasses
   #31 = Utf8               flag
   #32 = Utf8               Z
   #33 = Utf8               <init>
   #34 = Utf8               ()V
   #35 = Utf8               Code
   #36 = Utf8               LineNumberTable
   #37 = Utf8               LocalVariableTable
   #38 = Utf8               this
   #39 = Utf8               Ljvm/LocalFinalize;
   #40 = Utf8               arm
   #41 = Utf8               main
   #42 = Utf8               ([Ljava/lang/String;)V
   #43 = Utf8               args
   #44 = Utf8               [Ljava/lang/String;
   #45 = Utf8               Exceptions
   #46 = Class              #86           // java/lang/InterruptedException
   #47 = Utf8               pass
   #48 = Utf8               h1
   #49 = Utf8               Ljvm/LocalFinalize$MyHook;
   #50 = Utf8               h2
   #51 = Utf8               StackMapTable
   #52 = Class              #79           // jvm/LocalFinalize$MyHook
   #53 = Utf8               lambda$arm$1
   #54 = Class              #81           // java/lang/Throwable
   #55 = Utf8               lambda$static$0
   #56 = Utf8               <clinit>
   #57 = Utf8               SourceFile
   #58 = Utf8               LocalFinalize.java
   #59 = NameAndType        #33:#34       // "<init>":()V
   #60 = Utf8               java/lang/Thread
   #61 = Utf8               BootstrapMethods
   #62 = MethodHandle       #6:#87        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #63 = MethodType         #34           //  ()V
   #64 = MethodHandle       #6:#88        // invokestatic jvm/LocalFinalize.lambda$arm$1:()V
   #65 = NameAndType        #89:#90       // run:()Ljava/lang/Runnable;
   #66 = NameAndType        #33:#91       // "<init>":(Ljava/lang/Runnable;)V
   #67 = NameAndType        #92:#34       // start:()V
   #68 = Class              #93           // java/lang/System
   #69 = NameAndType        #94:#95       // out:Ljava/io/PrintStream;
   #70 = Utf8               Pass 1
   #71 = Class              #96           // java/io/PrintStream
   #72 = NameAndType        #97:#98       // println:(Ljava/lang/String;)V
   #73 = NameAndType        #40:#34       // arm:()V
   #74 = NameAndType        #31:#32       // flag:Z
   #75 = NameAndType        #47:#34       // pass:()V
   #76 = Utf8               Wait for pass 1 finalization
   #77 = NameAndType        #99:#100      // sleep:(J)V
   #78 = Utf8               Pass 2
   #79 = Utf8               jvm/LocalFinalize$MyHook
   #80 = NameAndType        #101:#34      // log:()V
   #81 = Utf8               java/lang/Throwable
   #82 = NameAndType        #102:#34      // gc:()V
   #83 = MethodHandle       #6:#103       // invokestatic jvm/LocalFinalize.lambda$static$0:()V
   #84 = Utf8               jvm/LocalFinalize
   #85 = Utf8               java/lang/Object
   #86 = Utf8               java/lang/InterruptedException
   #87 = Methodref          #104.#105     // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #88 = Methodref          #27.#106      // jvm/LocalFinalize.lambda$arm$1:()V
   #89 = Utf8               run
   #90 = Utf8               ()Ljava/lang/Runnable;
   #91 = Utf8               (Ljava/lang/Runnable;)V
   #92 = Utf8               start
   #93 = Utf8               java/lang/System
   #94 = Utf8               out
   #95 = Utf8               Ljava/io/PrintStream;
   #96 = Utf8               java/io/PrintStream
   #97 = Utf8               println
   #98 = Utf8               (Ljava/lang/String;)V
   #99 = Utf8               sleep
  #100 = Utf8               (J)V
  #101 = Utf8               log
  #102 = Utf8               gc
  #103 = Methodref          #27.#107      // jvm/LocalFinalize.lambda$static$0:()V
  #104 = Class              #108          // java/lang/invoke/LambdaMetafactory
  #105 = NameAndType        #109:#112     // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #106 = NameAndType        #53:#34       // lambda$arm$1:()V
  #107 = NameAndType        #55:#34       // lambda$static$0:()V
  #108 = Utf8               java/lang/invoke/LambdaMetafactory
  #109 = Utf8               metafactory
  #110 = Class              #114          // java/lang/invoke/MethodHandles$Lookup
  #111 = Utf8               Lookup
  #112 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #113 = Class              #115          // java/lang/invoke/MethodHandles
  #114 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #115 = Utf8               java/lang/invoke/MethodHandles
{
  private static volatile boolean flag;
    descriptor: Z
    flags: ACC_PRIVATE, ACC_STATIC, ACC_VOLATILE

  public jvm.LocalFinalize();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/LocalFinalize;

  public static void arm();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        12: invokevirtual #5                  // Method java/lang/Thread.start:()V
        15: return
      LineNumberTable:
        line 19: 0
        line 24: 12
        line 25: 15

  public static void main(java.lang.String...) throws java.lang.InterruptedException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String Pass 1
         5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: invokestatic  #9                  // Method arm:()V
        11: iconst_1
        12: putstatic     #10                 // Field flag:Z
        15: invokestatic  #11                 // Method pass:()V
        18: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        21: ldc           #12                 // String Wait for pass 1 finalization
        23: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: ldc2_w        #13                 // long 10000l
        29: invokestatic  #15                 // Method java/lang/Thread.sleep:(J)V
        32: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        35: ldc           #16                 // String Pass 2
        37: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        40: iconst_1
        41: putstatic     #10                 // Field flag:Z
        44: invokestatic  #11                 // Method pass:()V
        47: return
      LineNumberTable:
        line 28: 0
        line 29: 8
        line 30: 11
        line 31: 15
        line 33: 18
        line 34: 26
        line 36: 32
        line 37: 40
        line 38: 44
        line 39: 47
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      48     0  args   [Ljava/lang/String;
    Exceptions:
      throws java.lang.InterruptedException

  public static void pass();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: new           #17                 // class jvm/LocalFinalize$MyHook
         3: dup
         4: invokespecial #18                 // Method jvm/LocalFinalize$MyHook."<init>":()V
         7: astore_0
         8: new           #17                 // class jvm/LocalFinalize$MyHook
        11: dup
        12: invokespecial #18                 // Method jvm/LocalFinalize$MyHook."<init>":()V
        15: astore_1
        16: getstatic     #10                 // Field flag:Z
        19: ifeq          25
        22: goto          16
        25: aload_0
        26: invokevirtual #19                 // Method jvm/LocalFinalize$MyHook.log:()V
        29: return
      LineNumberTable:
        line 42: 0
        line 43: 8
        line 45: 16
        line 49: 25
        line 50: 29
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      22     0    h1   Ljvm/LocalFinalize$MyHook;
           16      14     1    h2   Ljvm/LocalFinalize$MyHook;
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 16
          locals = [ class jvm/LocalFinalize$MyHook, class jvm/LocalFinalize$MyHook ]
        frame_type = 8 /* same */

  private static void lambda$arm$1();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=0
         0: ldc2_w        #20                 // long 5000l
         3: invokestatic  #15                 // Method java/lang/Thread.sleep:(J)V
         6: iconst_0
         7: putstatic     #10                 // Field flag:Z
        10: goto          14
        13: astore_0
        14: return
      Exception table:
         from    to  target type
             0    10    13   Class java/lang/Throwable
      LineNumberTable:
        line 21: 0
        line 22: 6
        line 23: 10
        line 24: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
      StackMapTable: number_of_entries = 2
        frame_type = 77 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 0 /* same */

  private static void lambda$static$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=0
         0: invokestatic  #23                 // Method java/lang/System.gc:()V
         3: ldc2_w        #24                 // long 1000l
         6: invokestatic  #15                 // Method java/lang/Thread.sleep:(J)V
         9: goto          0
        12: astore_0
        13: return
      Exception table:
         from    to  target type
             0    12    12   Class java/lang/Throwable
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 14: 12
        line 15: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
      StackMapTable: number_of_entries = 2
        frame_type = 0 /* same */
        frame_type = 75 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #26,  0             // InvokeDynamic #1:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        12: invokevirtual #5                  // Method java/lang/Thread.start:()V
        15: return
      LineNumberTable:
        line 8: 0
        line 15: 12
        line 16: 15
}
SourceFile: "LocalFinalize.java"
InnerClasses:
     public static #29= #17 of #27; //MyHook=class jvm/LocalFinalize$MyHook of class jvm/LocalFinalize
     public static final #111= #110 of #113; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #62 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #63 ()V
      #64 invokestatic jvm/LocalFinalize.lambda$arm$1:()V
      #63 ()V
  1: #62 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #63 ()V
      #83 invokestatic jvm/LocalFinalize.lambda$static$0:()V
      #63 ()V

局部变量表(LVT)

public static void pass();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0
         0: new           #17                 // class LocalFinalize$MyHook
         3: dup
         4: invokespecial #18                 // Method LocalFinalize$MyHook."<init>":()V
         7: astore_0
         8: new           #17                 // class LocalFinalize$MyHook
        11: dup
        12: invokespecial #18                 // Method LocalFinalize$MyHook."<init>":()V
        15: astore_1
        16: getstatic     #10                 // Field flag:Z
        19: ifeq          25
        22: goto          16
        25: aload_0
        26: invokevirtual #19                 // Method LocalFinalize$MyHook.log:()V
        29: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      22     0    h1   LLocalFinalize$MyHook; //  8 + 22 = 30
           16      14     1    h2   LLocalFinalize$MyHook; // 16 + 14 = 30
data16 data16 xchg %ax,%ax      # loop alignment
                                # output would also say:
                                # ImmutableOopMap{r10=Oop rbp=Oop}
LOOP:
  test   %eax,0x15ae2bca(%rip)  # safepoint poll, switch to GC can happen here
  movzbl 0x70(%r10),%r8d        # get this.flag
  test   %r8d,%r8d              # check flag and loop back
  jne    LOOP

...

ImmutableOopMap{r10=Oop rbp=Oop} 基本上说%r10和%rbp保存“普通对象指针”。 %r10保存this — 看看我们如何从中读取flag ,而%rbp保存稍后将使用的对h1引用。这里缺少对h2的引用。如果在循环期间发生 GC,则线程将在执行安全点轮询时阻塞,此时运行时将在此映射的帮助下确切地知道要关注哪些寄存器。

建议

可以通过使用该局部变量将存储在局部变量中的对象的可达性扩展到给定的程序点。但是,如果没有明显的不足,这很难做到。例如,“仅仅”调用方法并传递该局部变量是不够的,因为该方法可能会被内联,并且相同的优化会启动。从 Java 9 开始,有 java.lang.ref.Reference::reachabilityFence 方法提供所需的语义。

如果您“只是”想要拥有类似 C++ 的“在块退出时释放”的析构 — — 在离开块时执行某些操作 — — 那么建议您使用try-finally来实现。


http://www.kler.cn/a/573305.html

相关文章:

  • 大模型为何无法达到AGI?
  • 利用Python爬虫按图搜索1688商品(拍立淘)
  • 【linux 安装mongodb】在redhat9上安装mongodb8出现下载元数据错误
  • 【大模型安全】大模型的技术风险
  • NModbus 连接到Modbus服务器(Modbus TCP)
  • NFC 碰一碰发视频系统技术开发实战:从硬件触发到智能生成的全流程实现
  • B站pwn教程笔记-4
  • Java Web-Filter
  • 面试基础---MySQL 事务隔离级别与 MVCC 深度解析
  • IP地址怎么加密https访问?
  • JAVA毕设项目-基于SSM框架的百色学院创新实践学分认定系统源码+设计文档
  • 毕业项目推荐:基于yolov8/yolov5/yolo11的田间杂草检测识别系统(python+卷积神经网络)
  • Stable Diffusion LoRA 技术详解
  • debian/control 文件中的${misc:Depends}
  • 如何在React中正确处理异步操作?
  • windows 利用nvm 管理node.js 2025最新版
  • 靶场之路-VulnHub-DC-6 nmap提权、kali爆破、shell反连
  • DAViMNet:基于状态空间模型的域自适应目标检测
  • 二、Java-封装playwright UI自动化(根据官网执行步骤,首先封装BrowserFactory枚举类及BrowserManager)
  • Python开发高效PDF批量转Word