Посмотрите В Корень: Java.lang.object

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

В интервью часто спрашивают, что это за техники, вот они как-то научились этому самостоятельно.

Пришло время поближе взглянуть на этот класс.

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

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

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
   

package java.lang; public class Object { private static native void registerNatives(); static { registerNatives(); } public final native Class<?> getClass(); public native int hashCode(); public boolean equals(Object obj) { return (this == obj); } protected native Object clone() throws CloneNotSupportedException; public String toString() { return getClass().

getName() + "@" + Integer.toHexString(hashCode()); } public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); } protected void finalize() throws Throwable { } }

Что хотелось бы отметить в этом коде? В Object имеется 11 общедоступных методов: 5 обычных и 6 с собственной реализацией.

Давайте рассмотрим штатные методы, благо их код уже доступен.

По умолчанию все объекты сравниваются на предмет равенства ссылок.

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



public boolean equals(Object obj) { return (this == obj); }

toString также не содержит ничего необычного, за исключением того, что hashCode() преобразуется в шестнадцатеричную строку.

И если апангин не писал этого сегодня как только невозможно вычислить hashCode , я бы подумал, что раньше Java-программисты могли найти свой объект по хэш-коду, т.к.

он был не более чем ссылкой.

Те 32-битные дни для многих прошли, и теперь я даже не знаю, имеет ли смысл отображать hashCode в toString().



public String toString() { return getClass().

getName() + "@" + Integer.toHexString(hashCode()); }

Помимо того, что wait относится к примитивам, обеспечивающим многопоточность, хотелось бы отметить бесполезность параметра nanos. В некоторых случаях это просто добавляет одну миллисекунду.

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



public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); }

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

protected void finalize() throws Throwable { }

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

финализировать и финализатор , смысл завершения .

Теперь посмотрим на java/lang/Object.class. Меня, например, интересует, что в нем указано как суперкласс.

Находим rt.jar в установленном jre или jdk, распаковываем:

jar -xf rt.jar

И мы видим, что в его суперклассе написано 00 00, интересно, что будет, если вручную создать файл класса без суперкласса.

Я взял Hello.class у своего предыдущее примечание .

Открыл его в vim и заменил содержимое буфера шестнадцатеричным дампом.

vim.wikia.com/wiki/Hex_dump :

:%!xxd

Я был поражен мощью редактора vim. Я быстро нашел байты для super_class. Напомню, что они врут по Характеристики 4 байта после конца константного_пула.

Конец константного_пула ищется по тегу строки 00 01 и последовательности ненулевых байтов, когда начинаются нули, находятся другие разделы константного_пула.

Для других файлов классов это может быть не так, но в моем случае это сработало.

Вернёмся к двоичной форме:

:%!xxd -r

Сохраните изменения.

Запустим наше исправленное приложение:

java -cp classes/ hello.App Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.ClassFormatError: Invalid superclass index 0 in class file hello/App at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

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

Пойдём разбираться, может поймём как выкидывать такие ошибки.

Нам нужны исходники jdk. Для исследования я выбрал OpenJDK. Мы их скачаем отсюда: hg.openjdk.java.net/jdk8/jdk8 И храним в Меркурии: клон hg hg.openjdk.java.net/jdk8/jdk8 Но это не все.

Вам также необходимо запустить:

.

/get_source.sh

И ждать.

Отлично, исходники скачались и можно искать нашу ошибку.

Я делаю это с помощью grep:

grep -nr 'Invalid superclass index' * hotspot/src/share/vm/classfile/classFileParser.cpp:3095: "Invalid superclass index %u in class file %s", hotspot/src/share/vm/classfile/classFileParser.cpp:3100: "Invalid superclass index %u in class file %s",

Откройте classFileParser.cpp и там строка 3095:

instanceKlassHandle ClassFileParser::parse_super_class(int super_class_index, TRAPS) { instanceKlassHandle super_klass; if (super_class_index == 0) { check_property(_class_name == vmSymbols::java_lang_Object(), "Invalid superclass index %u in class file %s", super_class_index, CHECK_NULL); } else { check_property(valid_klass_reference_at(super_class_index), "Invalid superclass index %u in class file %s", super_class_index, CHECK_NULL); // The class name should be legal because it is checked when parsing constant pool. // However, make sure it is not an array type. bool is_array = false; if (_cp->tag_at(super_class_index).

is_klass()) { super_klass = instanceKlassHandle(THREAD, _cp->resolved_klass_at(super_class_index)); if (_need_verify) is_array = super_klass->oop_is_array(); } else if (_need_verify) { is_array = (_cp->unresolved_klass_at(super_class_index)->byte_at(0) == JVM_SIGNATURE_ARRAY); } if (_need_verify) { guarantee_property(!is_array, "Bad superclass name in class file %s", CHECK_NULL); } } return super_klass; }

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

if (super_class_index == 0) { check_property(_class_name == vmSymbols::java_lang_Object(), "Invalid superclass index %u in class file %s", super_class_index, CHECK_NULL); }

check_property находится в заголовочном файле classFileParser.hpp и выглядит следующим образом:

inline void check_property(bool property, const char* msg, int index, TRAPS) { if (_need_verify) { guarantee_property(property, msg, index, CHECK); } else { assert_property(property, msg, index, CHECK); } }

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

_need_verify = Verifier::should_verify_for(class_loader(), verify);

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

instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name, ClassLoaderData* loader_data, Handle protection_domain, KlassHandle host_klass, GrowableArray<Handle>* cp_patches, TempNewSymbol& parsed_name, bool verify, TRAPS)

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

instanceKlassHandle result = parser.parseClassFile(h_name, loader_data, protection_domain, parsed_name, false, CHECK_(h));

Как must_verify_for работает в hotspot/src/share/vm/classfile/verifier.cpp:

bool Verifier::should_verify_for(oop class_loader, bool should_verify_class) { return (class_loader == NULL || !should_verify_class) ? BytecodeVerificationLocal : BytecodeVerificationRemote; }

Поскольку мы передаем false в must_verify_class, посмотрите BytecodeVerificationLocal в hotspot/src/share/vm/runtime/arguments.cpp:

// -Xverify } else if (match_option(option, "-Xverify", &tail)) { if (strcmp(tail, ":all") == 0 || strcmp(tail, "") == 0) { FLAG_SET_CMDLINE(bool, BytecodeVerificationLocal, true); FLAG_SET_CMDLINE(bool, BytecodeVerificationRemote, true); } else if (strcmp(tail, ":remote") == 0) { FLAG_SET_CMDLINE(bool, BytecodeVerificationLocal, false); FLAG_SET_CMDLINE(bool, BytecodeVerificationRemote, true); } else if (strcmp(tail, ":none") == 0) { FLAG_SET_CMDLINE(bool, BytecodeVerificationLocal, false); FLAG_SET_CMDLINE(bool, BytecodeVerificationRemote, false); } else if (is_bad_option(option, args->ignoreUnrecognized, "verification")) { return JNI_EINVAL; } // -Xdebug }

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

#define FLAG_SET_CMDLINE(type, name, value) (CommandLineFlagsEx::type##AtPut(FLAG_MEMBER_WITH_TYPE(name,type), (type)(value), Flag::COMMAND_LINE)) class CommandLineFlagsEx : CommandLineFlags { public: static void boolAtPut(CommandLineFlagWithType flag, bool value, Flag::Flags origin); static void intxAtPut(CommandLineFlagWithType flag, intx value, Flag::Flags origin); static void uintxAtPut(CommandLineFlagWithType flag, uintx value, Flag::Flags origin); static void uint64_tAtPut(CommandLineFlagWithType flag, uint64_t value, Flag::Flags origin); static void doubleAtPut(CommandLineFlagWithType flag, double value, Flag::Flags origin); static void ccstrAtPut(CommandLineFlagWithType flag, ccstr value, Flag::Flags origin); static bool is_default(CommandLineFlag flag); static bool is_ergo(CommandLineFlag flag); static bool is_cmdline(CommandLineFlag flag); };

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

Документация в помощь.

По умолчанию jvm запускается с параметром -Xverify: удаленный и BytecodeVerificationLocal будет ложным.

Это означает, что _need_verify также имеет значение false и Assert_property(property, msg, index, CHECK) вызывается в check_property с параметрами false, «Неверный индекс суперкласса %u в файле класса %s», 0, CHECK_NULL.

inline void assert_property(bool b, const char* msg, int index, TRAPS) { #ifdef ASSERT if (!b) { ResourceMark rm(THREAD); fatal(err_msg(msg, index, _class_name->as_C_string())); } #endif }

Собственно, именно здесь выдается сообщение об ошибке.

Теперь давайте посмотрим на fat(msg), чтобы увидеть, как это делается.

Хотя на часть вопроса мы уже ответили.

Вы не можете создать файл классов, в котором поле super_class будет иметь значение 0, и загрузить его с помощью ClassLoader по умолчанию.

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

#define fatal(msg) \ do { \ report_fatal(__FILE__, __LINE__, msg); \ BREAKPOINT; \ } while (0)

точка доступа/src/share/vm/utilities/debug.cpp:

void report_fatal(const char* file, int line, const char* message) { report_vm_error(file, line, "fatal error", message); } void report_vm_error(const char* file, int line, const char* error_msg, const char* detail_msg) { if (Debugging || error_is_suppressed(file, line)) return; Thread* const thread = ThreadLocalStorage::get_thread_slow(); VMError err(thread, file, line, error_msg, detail_msg); err.report_and_die(); }

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

выводы
java.lang.Object — это специальный класс, имеющий уникальный файл класса, в котором ни один класс не указан в качестве суперкласса.

Создать тот же класс с помощью языка Java невозможно, но сделать это, манипулируя байтами файла класса, также сложно, если не невозможно.

Надеюсь, мне удалось передать часть восхищения, которое я испытал при изучении исходного кода jvm. Я призываю всех попробовать то же самое.

Теги: #java #jvm #java

Вместе с данным постом часто просматривают:
Lumtu.com © 2024