Как Garbage Collector актуализира препратките, изпратени към стека на операндите?

JVM може лесно да актуализира препратките на локални променливи, статични препратки, екземпляри на класове или екземпляри на обектен масив, когато премествате обект в купчина. Но как може да актуализира препратките, изпратени към стека на операндите?


person neoexpert    schedule 03.03.2020    source източник
comment
Какво си представяте, че би направило това трудно?   -  person Louis Wasserman    schedule 03.03.2020
comment
Стойност в стека на операндите може да има произволен тип. Как можем да разберем дали е референция или просто цяло число?   -  person neoexpert    schedule 03.03.2020
comment
Как, за бога, бихме могли НИЕ да знаем как ВИЕ внедрихте своя VM?   -  person Gyro Gearless    schedule 03.03.2020
comment
Защо предполагате, че JVM не проследява типа на всяка стойност в стека?   -  person Louis Wasserman    schedule 03.03.2020
comment
Знам, че CodeAttribute на метод може да има атрибут StackMapTable. Но не намерих информация дали може да се използва за събиране на боклук.   -  person neoexpert    schedule 03.03.2020
comment
@Wasserman Мисля, че бихте могли да го направите на всяка инструкция на ALOAD, може би това е начинът. Благодаря ви за съвета.   -  person neoexpert    schedule 03.03.2020
comment
Работата е там, че JVM трябва да проследява всяка LOAD-инструкция по време на изпълнение. Това забавя изпълнението...   -  person neoexpert    schedule 03.03.2020
comment
HotSpot JVM използва структури на OopMap за намиране и актуализиране на препратки към обекти в стека.   -  person apangin    schedule 03.03.2020


Отговори (1)


Няма фундаментална разлика между локална променлива и запис в стека на операндите. И двете живеят в една и съща стекова рамка. Нито едно от тях не е официално декларирано и и двете се нуждаят от JVM за извършване на изводи, за да разпознаят действителната им употреба.

Следният код

public static void example() {
    {
        int foo = 42;
    }
    {
        Object bar = "text";
    }
    {
        long x = 100L;
    }
    {
        Object foo, bar = new Object();
    }
}

(обикновено) ще се компилира до

  public static void example();
    Code:
       0: bipush        42
       2: istore_0
       3: ldc           #1                  // String text
       5: astore_0
       6: ldc2_w        #2                  // long 100l
       9: lstore_0
      10: new           #4                  // class java/lang/Object
      13: dup
      14: invokespecial #5                  // Method java/lang/Object."<init>":()V
      17: astore_1
      18: return

Обърнете внимание как локалната променлива с индекс 0 в рамката на стека се преназначава със стойности от различни типове. Като бонус, последното съхраняване на променлива с индекс 1 прави променливата с индекс 0 невалидна, тъй като иначе би съдържала висяща половина от long стойност.

Няма допълнителни подсказки за типа на локалните променливи, информацията за отстраняване на грешки не е задължителна и таблиците с карта на стека са там само когато кодът съдържа разклонения.

Единственият начин да определите дали дадена локална променлива съдържа препратка е да следвате потока на програмата и да проследите ефекта от инструкциите. Това вече предполага извеждане на стойностите в стека на операндите, тъй като без него дори не бихме знаели какво е поставила инструкцията store в променливата.

Верификаторът го прави, дори е задължителен, а събирачът на отпадъци или какъвто и да е поддържащ код на JVM също може да го направи. Реализацията може дори да има единичен анализиращ код, който да пази информацията за типа на първия анализ, който би бил проверката.

Но дори когато тази информация се реконструира всеки път, когато събирачът на отпадъци се нуждае от нея, режийните разходи няма да бъдат астрономически. Събирачът на отпадъци работи само периодично и се нуждае само от тази информация за текущо изпълняваните методи. И това е всичко само за интерпретирано изпълнение.

Когато JIT компилаторът генерира код, той така или иначе трябва да използва информацията за типа и може да подготви информация за събирача на отпадъци, но ще го направи само за определени точки, наречени безопасни точки, където генерираният код проверява дали има изключително събиране на боклука. Това означава, че между тези точки не е необходимо данните да бъдат във форма, която събирачът на боклук разбира и оптимизираният код може да приеме, че събирачът на боклук няма да премести обекти, докато ги обработва.

Това също така предполага, че в компилиран, оптимизиран код достижимостта може да е напълно различна от тази в просто интерпретирано изпълнение, т.е. неизползваните променливи може да отсъстват, но дори използваните обекти от гледна точка на изходния код могат да се считат за неизползвани, когато оптимизираният код работи с копия на техните полета, напр. в регистрите на процесора.

person Holger    schedule 03.03.2020
comment
Типовете таблица на локални променливи и стек на операнди в конкретна точка на изпълнение (програмен брояч) винаги ли са еднакви (независимо от предишни клонове на изпълнение)? Може би JVM би могъл да изгради таблица с типова карта преди време за всеки метод за всяка възможна стойност на програмния брояч. - person neoexpert; 03.03.2020
comment
Да, всъщност точно това прави по време на проверката на байт кода, която се извършва при зареждане на клас. - person Antimony; 03.03.2020
comment
Това имах предвид с „Едно внедряване може дори да има единичен анализиращ код, съхраняващ информацията за типа на първия анализ“, което обикновено е информацията, събрана по време на проверката. Все пак трябва да имате предвид, че задържането му за всяка инструкция е много данни, докато вероятността да бъдете текущата инструкция по време на фаза на спиране на света е много ниска за отделна инструкция. Би било по-разумно да ги запазите само за точки на сливане на клонове (които предоставят таблиците на картата на стека), извиквания на методи и инструкции за разпределение и да извеждате други в движение. - person Holger; 04.03.2020