В конце концов, это зависит от фактического компилятора и точных настроек компиляции.
Как вы заметили, самой JVM не нужны никакие имена локальных переменных.(Строго говоря, для него тоже не нужны имена методов. Возможно даже иметь два метода с одинаковыми именами и аргументами, которые отличаются только типом возвращаемого значения , но мне нужно поискатьнекоторые подробности об этом в спецификации, чтобы сказать что-то более основательное).Но файл класса может содержать дополнительную отладочную информацию, которая выходит за рамки информации, требуемой JVM.
Стандартный компилятор Java - javac .И документация уже содержит некоторые подсказки о возможной отладочной информации:
-g
Генерирует всю информацию отладки, включая локальные переменные.По умолчанию генерируется только номер строки и информация об исходном файле.
-g: нет
Не генерирует отладочную информацию.
-g: [список ключевых слов]
Генерирует только некоторые виды отладочной информации, указанные в списке ключевых слов через запятую.Допустимые ключевые слова:
- источник : информация об отладке исходного файла.
- строки : информация об отладке номера строки.
- vars : информация об отладке локальной переменной.
Это можно попробовать на примере:
public class ExampleClass {
public static void main(String[] args) {
ExampleClass exampleClass = new ExampleClass();
exampleClass.exampleMethod();
}
public void exampleMethod() {
String string = "This is an example";
for (int counter = 0; counter < 10; counter++) {
String localResult = string + counter;
System.out.println(localResult);
}
}
}
Компиляция с помощью
javac ExampleClass.java -g:none
создаст файл класса.Печать информации об этом файле класса с помощью
javap -c -v -l ExampleClass.class
(где -c
означает дизассемблирование вывода, -v
означает, что вывод должен быть подробным, а -l
означает, что информация о номере строки должна бытьвыводится следующим образом:
public class ExampleClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // ExampleClass
#3 = Methodref #2.#22 // ExampleClass."<init>":()V
#4 = Methodref #2.#24 // ExampleClass.exampleMethod:()V
#5 = String #25 // This is an example
#6 = Class #26 // java/lang/StringBuilder
#7 = Methodref #6.#22 // java/lang/StringBuilder."<init>":()V
#8 = Methodref #6.#27 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #6.#28 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#10 = Methodref #6.#29 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream;
#12 = Methodref #32.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #34 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 exampleMethod
#20 = Utf8 StackMapTable
#21 = Class #35 // java/lang/String
#22 = NameAndType #14:#15 // "<init>":()V
#23 = Utf8 ExampleClass
#24 = NameAndType #19:#15 // exampleMethod:()V
#25 = Utf8 This is an example
#26 = Utf8 java/lang/StringBuilder
#27 = NameAndType #36:#37 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#28 = NameAndType #36:#38 // append:(I)Ljava/lang/StringBuilder;
#29 = NameAndType #39:#40 // toString:()Ljava/lang/String;
#30 = Class #41 // java/lang/System
#31 = NameAndType #42:#43 // out:Ljava/io/PrintStream;
#32 = Class #44 // java/io/PrintStream
#33 = NameAndType #45:#46 // println:(Ljava/lang/String;)V
#34 = Utf8 java/lang/Object
#35 = Utf8 java/lang/String
#36 = Utf8 append
#37 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = Utf8 (I)Ljava/lang/StringBuilder;
#39 = Utf8 toString
#40 = Utf8 ()Ljava/lang/String;
#41 = Utf8 java/lang/System
#42 = Utf8 out
#43 = Utf8 Ljava/io/PrintStream;
#44 = Utf8 java/io/PrintStream
#45 = Utf8 println
#46 = Utf8 (Ljava/lang/String;)V
{
public ExampleClass();
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
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class ExampleClass
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method exampleMethod:()V
12: return
public void exampleMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: ldc #5 // String This is an example
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush 10
8: if_icmpge 43
11: new #6 // class java/lang/StringBuilder
14: dup
15: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_3
30: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_3
34: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: iinc 2, 1
40: goto 5
43: return
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 37
}
Это довольно много информации, но ничего кроме реальной структуры самого класса.
(Выупомянул, что имена " public вещи" должны быть сохранены. Но имя " private вещи" также должно быть сохранено - по крайней мере, для размышления. С методаминапример, Class#getDeclaredFields
, вы все равно можете получить доступ к закрытым полям - например, имя должно быть где-то доступно).
Теперь, наоборот, нужно скомпилировать его с
javac ExampleClass.java -g
для сохранения всей отладочной информации.Печать результата, как описано выше, дает
public class ExampleClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#36 // java/lang/Object."<init>":()V
#2 = Class #37 // ExampleClass
#3 = Methodref #2.#36 // ExampleClass."<init>":()V
#4 = Methodref #2.#38 // ExampleClass.exampleMethod:()V
#5 = String #39 // This is an example
#6 = Class #40 // java/lang/StringBuilder
#7 = Methodref #6.#36 // java/lang/StringBuilder."<init>":()V
#8 = Methodref #6.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = Methodref #6.#42 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#10 = Methodref #6.#43 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Fieldref #44.#45 // java/lang/System.out:Ljava/io/PrintStream;
#12 = Methodref #46.#47 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #48 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 LExampleClass;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 exampleClass
#26 = Utf8 exampleMethod
#27 = Utf8 localResult
#28 = Utf8 Ljava/lang/String;
#29 = Utf8 counter
#30 = Utf8 I
#31 = Utf8 string
#32 = Utf8 StackMapTable
#33 = Class #49 // java/lang/String
#34 = Utf8 SourceFile
#35 = Utf8 ExampleClass.java
#36 = NameAndType #14:#15 // "<init>":()V
#37 = Utf8 ExampleClass
#38 = NameAndType #26:#15 // exampleMethod:()V
#39 = Utf8 This is an example
#40 = Utf8 java/lang/StringBuilder
#41 = NameAndType #50:#51 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#42 = NameAndType #50:#52 // append:(I)Ljava/lang/StringBuilder;
#43 = NameAndType #53:#54 // toString:()Ljava/lang/String;
#44 = Class #55 // java/lang/System
#45 = NameAndType #56:#57 // out:Ljava/io/PrintStream;
#46 = Class #58 // java/io/PrintStream
#47 = NameAndType #59:#60 // println:(Ljava/lang/String;)V
#48 = Utf8 java/lang/Object
#49 = Utf8 java/lang/String
#50 = Utf8 append
#51 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#52 = Utf8 (I)Ljava/lang/StringBuilder;
#53 = Utf8 toString
#54 = Utf8 ()Ljava/lang/String;
#55 = Utf8 java/lang/System
#56 = Utf8 out
#57 = Utf8 Ljava/io/PrintStream;
#58 = Utf8 java/io/PrintStream
#59 = Utf8 println
#60 = Utf8 (Ljava/lang/String;)V
{
public ExampleClass();
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 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LExampleClass;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class ExampleClass
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method exampleMethod:()V
12: return
LineNumberTable:
line 4: 0
line 5: 8
line 6: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 exampleClass LExampleClass;
public void exampleMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: ldc #5 // String This is an example
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: bipush 10
8: if_icmpge 43
11: new #6 // class java/lang/StringBuilder
14: dup
15: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
18: aload_1
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_2
23: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_3
30: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_3
34: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: iinc 2, 1
40: goto 5
43: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 11
line 12: 30
line 10: 37
line 14: 43
LocalVariableTable:
Start Length Slot Name Signature
30 7 3 localResult Ljava/lang/String;
5 38 2 counter I
0 44 0 this LExampleClass;
3 41 1 string Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 37
}
SourceFile: "ExampleClass.java"
Основные различия:
- класс содержит имя исходного файла
- постоянный пул имеет много больше записей
- методы содержат
LineNumberTable
и LocalVariableTable
.
Например, рассмотрим exampleMethod()
:
LineNumberTable:
line 9: 0
line 10: 3
line 11: 11
line 12: 30
line 10: 37
line 14: 43
LocalVariableTable:
Start Length Slot Name Signature
30 7 3 localResult Ljava/lang/String;
5 38 2 counter I
0 44 0 this LExampleClass;
3 41 1 string Ljava/lang/String;
Подробная информация о структуре этих атрибутов приведена в документации LineNumberTable
и LocalVariableTable
.
Для LineNumberTable
он говорит:
Он может использоваться отладчиками, чтобы определить, какая часть массива кода соответствует данному номеру строки в исходном исходном файле.
Для LocalVariableTable
он говорит:
. Он может использоваться отладчиками для определения значения заданной локальной переменной во время выполнения метода.
В выводе javap
имена локальных переменных уже разрешены.Однако фактическая информация, содержащаяся в самой таблице, представляет собой index в пуле констант (поэтому при сохранении отладочной информации в ней больше записей).Например, запись для переменной localResult
отображается как
30 7 3 localResult Ljava/lang/String;
, хотя на самом деле она содержит только ссылку на запись
#27 = Utf8 localResult
константы пула.
Итак, эти данные просто вычисляются декомпилятором с помощью какой-то магии или дозы, в которой скомпилированный класс сохраняет много информации и зачем?
Как показано выше, скомпилированный класс может хранить много информации.В конце концов, одна из основных целей IDE - предоставить хороший визуальный интерфейс для отладчика .И поэтому большинство компиляторов, которые так или иначе запускаются IDE, по умолчанию пытаются сохранить как можно больше отладочной информации.