Как java хранит литералы? - PullRequest
3 голосов
/ 17 октября 2019
System.out.println(5678);

Этот литерал напрямую используется в операторе печати. Но java хранит это непосредственно в памяти, или это создает автоматическую переменную и затем сохраняет это там? Если второй случай будет верным, что произойдет, если кто-то случайно получит доступ к этой переменной, используя то же имя переменной?

Ответы [ 3 ]

7 голосов
/ 17 октября 2019

Объяснение

Все, что делает 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 загружает значения напрямую, без каких-либо признаков каких-либо переменных.

sipush помещает short непосредственно в стек, откуда оно затем берется invokevirtual, которыйвызывает метод print (подробнее см. список инструкций Java bytecode ).

Почему это так? Ну, компилятор умный. Он понял, что переменная 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 и передается методу печати. ​​

Таким образом, через переменную нам нужно istore и iload его методу. С помощью литерала мы можем напрямую загрузить его в метод, используя sipush.

2 голосов
/ 17 октября 2019

Я скомпилировал следующий тестовый файл:

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 имеет гораздо большеподробности.

1 голос
/ 17 октября 2019

Краткий ответ: Нет! вы не можете получить к ним доступ во время компиляции.

Java хранит литералы в памяти permgen space и не доступна по именам переменных во время компиляции. Для JVM также характерно, как они ее реализуют.

Например, если мы говорим о String литералах в приведенном ниже коде, Java может сохранить "sameSame" в некотором месте памяти (String Pool) и затем использовать этодля обоих методов вместо создания одной и той же строки дважды.

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

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