В Java на вершине иерархии классов находится класс java.lang.Object. Врёт и врёт, долгое время меня это совершенно не интересовало.
В интервью часто спрашивают, что это за техники, вот они как-то научились этому самостоятельно.
Пришло время поближе взглянуть на этот класс.
Первый вопрос, который у меня возник, заключался в том, существует ли вообще класс java.lang.Object в исходном коде Java. Ведь он необычный, вполне может быть зашит в реализацию, как и верхний.
Однако такой класс существует, и я приведу здесь исходный код java/lang/Object.java, опуская javadoc, и попытаюсь пролить свет на некоторые моменты, связанные с реализацией jvm:
Что хотелось бы отметить в этом коде? В Object имеется 11 общедоступных методов: 5 обычных и 6 с собственной реализацией.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 { } }
Давайте рассмотрим штатные методы, благо их код уже доступен.
По умолчанию все объекты сравниваются на предмет равенства ссылок.
Кстати, мне однажды понравилась шутка о том, что, чтобы запутать программистов на 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
-
Райффайзенбанк Ищет Спикеров На
19 Oct, 24 -
Идея Социальной Сети «Тудудан»
19 Oct, 24 -
Htc С Android К Новому Году
19 Oct, 24 -
Кто Ваш Эксперт?
19 Oct, 24