java 如何存储文字?

How does java store literals?

System.out.println(5678);

打印语句中直接使用该文字。但是 java 是直接将它存储在内存中还是创建一个自动变量然后将其存储在那里?如果第二种情况成立,如果有人不小心使用相同的变量名访问该变量会怎样?

我编译了以下测试文件:

class test {
    public static void main(String[] args) {
        System.out.println(5678);
        System.out.println("test string");
    }
}

javap -c反编译结果如下:

    public static void main(java.lang.String[]);
      Code:
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: sipush        5678
         6: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
         9: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: ldc           #19                 // String test string
        14: invokevirtual #21                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        17: return

如果添加 -verbose 标志,它还会打印常量池,您可以在其中查找 #19 并看到它是字符串 test string.

如果您对更多此类详细信息感兴趣:JVM specification 有更多详细信息。

说明

无论 Java 在幕后做什么,对用户和程序员来说都是完全隐藏的。

只要还在写Java代码,就不可能乱来。当然,如果您尝试连接 JVM 进程并注入 C 代码,或通过其本机界面与之交互,情况可能会有所不同。

这也完全取决于 JVM 如何在内存中实际处理它。 Java.

中的内存管理对程序员完全隐藏

字节码

内联变量

话虽这么说,但让我们看一下此代码段的结果字节码(请参阅 javabytes.io 或使用 javap -c Test.class):

// Source code
public class Test {
    public static void main(String [] args) {
        int value = 4000;
        System.out.println(value);

        System.out.println(5678);
    }
}

// Byte code
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1    // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: sipush        4000
       3: istore_1
       4: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iload_1
       8: invokevirtual #3    // Method java/io/PrintStream.println:(I)V
      11: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
      14: sipush        5678
      17: invokevirtual #3    // Method java/io/PrintStream.println:(I)V
      20: return
}

如您所见,两种变体实际上是相同的。 sipush 命令直接加载值,根本没有任何变量的迹象。

sipushshort 直接压入堆栈,然后由调用 print 方法的 invokevirtual 从那里获取它(有关详细信息,请参阅 Java bytecode instruction list ).

为什么会这样?嗯,编译器很聪明。它认为变量 value 没有任何作用,实际上完全摆脱了它。它将代码更改为 System.out.println(4000) 并完全删除了 value

存在变量

但是我们想看到一些变量,所以让我们让它更复杂一点,这样 Java 就不会再通过引入只能在 运行 处计算的依赖项来内联变量-时间:

// Source code
public class Test {
    public static void main(String [] args) {
        int value = (int) System.currentTimeMillis();
        System.out.println(value);

        System.out.println(5678);
    }
}

// Byte code
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1    // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2    // Method java/lang/System.currentTimeMillis:()J
       3: l2i
       4: istore_1
       5: getstatic     #3    // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iload_1
       9: invokevirtual #4    // Method java/io/PrintStream.println:(I)V
      12: getstatic     #3    // Field java/lang/System.out:Ljava/io/PrintStream;
      15: sipush        5678
      18: invokevirtual #4    // Method java/io/PrintStream.println:(I)V
      21: return
}   

我们终于看到了一些可变的动作!该变量由该方法计算,转换为 int,然后通过 istore_1 存储。然后,它被 iload_1 动态加载到堆栈上并传递给 print 方法。

所以通过变量,我们需要 istoreiload 它到方法。使用文字,我们可以直接将其加载到使用 sipush.

的方法中

简答:不!你不能在编译时访问它们。

Java 将文字存储在 permgen space 内存中,编译时不能通过变量名访问它。它也是特定于 JVM 如何实现的。

例如,如果我们在下面的代码中讨论 String 文字,Java 可能会将 "sameSame" 存储在某个内存位置(字符串池)中,然后将其用于两种方法两次创建相同的字符串。

private static String test1(){
    return "sameSame";
}

private static String test2(){
    return "sameSame";
}