2014-01-14 2 views
1

Из того, что я читал раньше, после .java файл компилируется в .class файлов, каждый объект просто Object после стирания. Такие, какКак функция вызова Java вызывает объект?

Foo f = new Foo(); 

компилирует .class файл и декомпилировать, он становится:

Object f = new Foo(); 

Так как же JRE вызов функции объекта при запуске? Где хранится функция в памяти? Внутри объекта? Или с иерархией структуры классов и ищет иерархию?

+2

_but каждый объект просто Object._ Нет ... где вы это читали? –

+0

@SotiriosDelimanolis См. Отредактированный – texasbruce

+0

@SotiriosDelimanolis Путаница с типом стирания? –

ответ

-1

Если вы посмотрите на спецификацию VM, вы увидите, что код каждого метода для каждого класса хранится в файле .class класса. эта ссылка class file format

4

Согласно Java spec и wikipedia

Есть 10 основных разделов в структуре файла класса Java:

  • Magic Number: 0xCAFEBABE
  • версия Формат файла класса : младшая и основная версии файла класса
  • Постоянный пул: пул констант для класса
  • Флаги доступа: например, является ли класс абстрактным, статические и т.д.
  • Этот класс: Имя текущего класса
  • Супер Класс: Имя суперкласса
  • Интерфейсы Любые интерфейсы в классе
  • Fields: Все поля в классе
  • методы: Любые методы в классе
  • Атрибуты: Любые атрибуты класса (например, имя Исходный_файл и т.д.)

Во время выполнения, тип объекта извлекается, его класс (или скорее virtual method table) является проверен на реализацию метода, вызванного. Если у этого класса нет такой реализации, родительский класс проверяется (извлекается из записи суперкласса) и т. Д., В конце концов не выполняется, если ни один не найден.

+0

Значит, вы имеете в виду, что классы не были предварительно загружены? Скорее, он проверяет файл на диске каждый раз? – texasbruce

+0

@texasbruce Файлы '.class' содержат байтовый код для методов. Я считаю, что именно JVM кэширует их или каждый раз просматривает их. Зависит от реализации (но давайте будем реальны). –

1

При объявлении

Foo f; 

в любой момент в течение жизни f «s, это может быть ссылка на объекты, которые не типа Foo. Тип объекта может быть Fooили любой из его подклассов. Поэтому объект должен хранить информацию о фактическом («runtime») типе объекта, где-то внутри каждого объекта. (Я считаю, что эта информация связана с самим объектом, а не со ссылками на объект, такой как f.) Я точно не знаю, как выглядит формат этой информации в JVM.Но на других скомпилированных языках, с которыми я работал, информация о типе включает указатель на вектор кодовых адресов. Если тип Foo объявляет методы method1(), method2() и т. Д., То каждому присваивается номер индекса (и это число будет сохранено для методов, наследуемых или переопределенных в подклассах). Поэтому вызов метода означает переход к этому вектору и поиск адреса функции для данного индекса. Это будет работать, будет ли фактический тип времени выполнения Foo или любым подклассом Foo.

1

Пример кода:

import java.util.*; 

public class Foo { 
    public static void main() { 
     Foo foo = new Foo(); 
     Object obj = new Object(); 
     foo.f(); 

     ArrayList<Foo> fooList = new ArrayList<Foo>(); 
     ArrayList objList = new ArrayList(); 
    } 

    public void f() { 
    } 
} 

Сформированные инструкции виртуальной машины Java (javap -c Foo):

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

    public static void main(); 
    Code: 
     0: new   #2     // class Foo 
     3: dup   
     4: invokespecial #3     // Method "<init>":()V 
     7: astore_0  
     8: new   #4     // class java/lang/Object 
     11: dup   
     12: invokespecial #1     // Method java/lang/Object."<init>":()V 
     15: astore_1  
     16: aload_0  
     17: invokevirtual #5     // Method f:()V 
     20: new   #6     // class java/util/ArrayList 
     23: dup   
     24: invokespecial #7     // Method java/util/ArrayList."<init>":()V 
     27: astore_2  
     28: new   #6     // class java/util/ArrayList 
     31: dup   
     32: invokespecial #7     // Method java/util/ArrayList."<init>":()V 
     35: astore_3  
     36: return   

    public void f(); 
    Code: 
     0: return   
} 

Как вы можете видеть, Foo foo = new Foo(); переводится на:

0: new   #2     // class Foo 
3: dup   
4: invokespecial #3     // Method "<init>":()V 
7: astore_0 

в то время как Object obj = new Object(); превращается в:

8: new   #4     // class java/lang/Object 
11: dup   
12: invokespecial #1     // Method java/lang/Object."<init>":()V 
15: astore_1 

new выделяет память для объекта и сохраняет ссылку в стеке, dup создает вторую ссылку в стеке, invokespecial вызывает конструктор (который на самом деле метод, называемый <init>). Затем экземпляр хранится в локальной переменной с astore_1.

Что касается ArrayList<Foo> fooList = new ArrayList<Foo>(); и ArrayList objList = new ArrayList();, они компилировать почти то же самое:

28: new   #6     // class java/util/ArrayList 
31: dup   
32: invokespecial #7     // Method java/util/ArrayList."<init>":()V 
35: astore_3 

One использует astore_2, а другой использует astore_3. Это потому, что они хранятся в разных локальных переменных. Помимо этого, сгенерированный код один и тот же, что означает, что JVM не может сообщить Arraylist<Foo> от Arraylist, что и называется стиранием типа. Тем не менее, он может очень легко сказать Foo от Object.

+1

Мне нравится запах батт-кода утром. Недостаточно ответов не хватает времени, чтобы вникать в низкий уровень! :) – SpacePrez

0

Ваш пример local variable. Локальные переменные существуют только внутри функции и не доступны для внешнего мира. В результате компилятор не должен хранить информацию о переменной в файле класса (это неtype erasure, что относится к потере информации о параметризованных типах).

В файле классов локальные переменные называются пронумерованными слотами в пределах stack frame. Таким образом, оператор байт-кода aload_0 извлекает значение в слоте 0 (которое, например, будет) и помещает его в верхнюю часть стека операндов, тогда как astore_1 берет ссылку с верхней части стека операндов и помещает ее в слот 1 кадра.

Поскольку компилятор знает тип каждого слота в кадре, он может гарантировать, что вызываются только правильные методы. JVM предоставляет дополнительную проверку: если вы можете изменить слот, чтобы содержать недопустимую ссылку на объект, операция invokevirtual потерпит неудачу.

Хотя вам не нужно хранить информацию о типе локальных переменных для запуска вашей программы, необходимо иметь эту информацию для отладки. Таким образом, вы можете дать компилятору переключатель -g, который заставляет его помещать LocalVariableTable в ваш файл классов. Эта таблица содержит исходное имя и тип каждой локальной переменной.

Если ваш декомпилятор показывает локальные переменные как Object, это означает одну из двух вещей: (1) файл класса был написан без отладочной информации или (2) декомпилятор недостаточно умен, чтобы применить эту информацию к байт-коду ,

Смежные вопросы