Зри в корень: java.lang.Object

В Java в вершине иерархии классов лежит класс java.lang.Object. Лежит и лежит, долгое время я им совсем не интересовался.

На собеседованиях часто спрашивают, какие в нем есть методы, поэтому они как-то сами собой выучились. Пришло время посмотреть на этот класс более внимательно. Первый вопрос, который у меня возник, есть ли вообще в исходниках Java класс java.lang.Object. Он же ведь необычный, он вполне может быть жестко зашит в реализацию, как самый верхний.

Однако, такой класс есть и я приведу тут исходники java/lang/Object.java, опустив javadoc, и попытаюсь пролить свет на некоторые моменты связанные с реализацией jvm:

Что бы я хотел отметить в этом коде?

Всего в Object 11 публичных методов, 5 обычных и 6 с нативной реализацией.

Рассмотрим обычные методы, так как их код уже доступен.

По дефолту все объекты сравниваются на равенство ссылок. Мне, кстати, в своем время понравилась шутка про то, что для того, чтобы запутать C++ программистов указатели в Java названы ссылками.

toString тоже не содержит ничего необычного, кроме разве того, что hashCode() преобразуется в шестнадцатеричную строку. И если бы apangin не написал, что нынче как только нельзя посчитать hashCode, я бы подумал, что раньше Java программисты могли найти свой объект по hashCode, т.к. он был не чем иным как ссылкой. Те 32 битные времена для многих прошли, и теперь даже не знаю, есть ли смысл в toString() выводить hashCode.

Кроме того, что wait относится к примитивам обеспечивающим многопоточность, хочется отметить бесполезность параметра nanos.

В некоторых случаях он просто добавляет одну милисекунду. Интересно, это закладка на будущее или уже есть системы в которых у wait(long timeout, int nanos) другая реализация.

Завершает парад обычных методов в java.lang.Object:

Этот метод ничего не делает, и есть куча материалов о том, что следует избегать его использования finalize и Finalizerсмысл finalize.

Теперь посмотрим на на java/lang/Object.class Например, мне интересно что в нем указано в качестве супер класса. Находим в установленном jre или jdk rt.jar, распаковываем:

И видим, что в super class у него прописаны 00 00, интересно что будет, если руками создать class файл без супер класса.
Я взял Hello.class из моей предыдущей заметки.

Открыл его в vim и заменил содержание буфера на hex дамп vim.wikia.com/wiki/Hex_dump:

Поразился мощи vim редактора. Быстренько нашел байты для super_class. Напомню, они лежат согласно спецификации через 4 байта после окончания constant_pool. Конец constant_pool ищется по тегу строки 00 01 и последовательности не нулевых байтов, когда начинаются нули идут другие разделы constant_pool. Для других class файлов это может быть не так, но в моем случае сработало.
Возвращаемся обратно к бинарному виду:

Сохраняем изменения. Запускаем наше поправленное приложение:

Ошибка, да еще не какая-нибудь, а выброшенная из нативного метода во время загрузки классов. Пойдем разбираться, за одно может поймем как выбрасывать такие ошибки.

Нам нужны исходники jdk. Я выбрал OpenJDK для исследования. Будем качать их отсюда:

hg.openjdk.java.net/jdk8/jdk8

И хранить в Меркурии:

hg clone hg.openjdk.java.net/jdk8/jdk8

Но на этом не всё.

Надо еще запустить:

И подождать. Отлично, исходники скачались и можно искать нашу ошибку. Я делаю это grep-ом:

Открываем classFileParser.cpp и там на 3095 строчке:

Нас интересует вот эта часть:

check_property лежит в заголовочном файле classFileParser.hpp и выглядит так:

Я стал искать где выставляется _need_verify и за что отвечает. Оказалось в classFileParser.cpp есть вот такая строчка:

verify передается при вызове:

Этот метод вызывается во многих местах, но нас интересует в hotspot/src/share/vm/classfile/classLoader.cpp:

Как же устроен should_verify_for в hotspot/src/share/vm/classfile/verifier.cpp:

Так как в should_verify_class мы передаем false, смотрим BytecodeVerificationLocal в hotspot/src/share/vm/runtime/arguments.cpp:

Зарываясь дальше можно найти черную магию с макросами в hotspot/src/share/vm/runtime/globals_extension.hpp:

Но меня это пока не интересует. Мне надо выяснить значение BytecodeVerificationLocal, в случае когда jvm стартует без параметра -Xverify. Это можно найти в коде, но мне кажется, сейчас не уместным лезть дальнейшие дерби и пора выбираться. Документация в помощь. По дефолту jvm запускается с параметром -Xverify:remote и BytecodeVerificationLocal будет false.

Значит _need_verify тоже false и в check_property вызывается assert_property(property, msg, index, CHECK) с параметрами false, «Invalid superclass index %u in class file %s», 0, CHECK_NULL.

Собственно, здесь и выбрасывается сообщение об ошибке. Теперь посмотрим на fatal(msg), чтобы узнать как это делается.
Хотя, на часть вопроса мы уже ответили. Нельзя сделать classfile в котором для поля super_class будет значение 0 и загружать его с помощью дефолтного ClassLoader.

Итак, fatal определенный в hotspot/src/share/vm/utilities/debug.hpp:

hotspot/src/share/vm/utilities/debug.cpp:

Реализация report_and_die() в hotspot/src/share/vm/utilities/vmError.cpp нетривиальна, но из нее следует, что в Java мы уже не возвращаемся и выводим сообщение об ошибке из недр jvm. На этом я хочу переостановить исследование jvm и java.lang.Object.

Выводы

java.lang.Object особый класс, имеющий уникальный class file, в котором в качестве суперкласса не указан ни один класс. Создать такой же класс средствами языка Java нельзя, но также затруднительно, если вообще возможно, сделать это и манипуляциями с байтами class файла. Надеюсь у меня получилось передать часть восхищения, которое я испытывал исследуя исходники jvm. Призываю всех попробовать сделать то же самое.

Источник