📜 ⬆️ ⬇️

Look at the root: java.lang.Object

In Java, at the top of the class hierarchy is the java.lang.Object class. Lies and lies, for a long time I was not interested in them at all.

At the interviews they often ask what methods there are in it, so they somehow learned by themselves. It's time to look at this class more closely. The first question I have is whether there is a java.lang.Object class in Java sources. After all, it is unusual, it may well be rigidly sewn into the implementation, as the very top one.

However, there is such a class, and I will give here the source code of java / lang / Object.java, omitting javadoc, and try to shed light on some points related to the implementation of 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 { } } 

What would I like to mention in this code?
')
Object has a total of 11 public methods, 5 normal and 6 native implementations.

Consider the usual methods, since their code is already available.

By default, all objects are compared for equality of links. By the way, I liked the joke in my own time about the fact that in order to confuse C ++ programmers, pointers in Java are called links.

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

toString also does not contain anything unusual, except that the hashCode () is converted to a hexadecimal string. And if apangin didn’t write that now as soon as it’s impossible to calculate hashCode , I’d think that earlier Java programmers could find their object using hashCode, since he was nothing more than a reference. Those 32 bit times have passed for many, and now I don’t even know if it makes sense in toString () to output hashCode.

 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 

In addition to the fact that wait refers to primitives providing multi-threading, I would like to note the uselessness of the nanos parameter.

In some cases, it simply adds one millisecond. Interestingly, this is a bookmark for the future or there are already systems in which wait (long timeout, int nanos) has a different implementation.

 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); } 

Completes the parade of conventional methods in java.lang.Object:

 protected void finalize() throws Throwable { } 

This method does nothing, and there are plenty of materials that should avoid its use of finalize and Finalizer , meaning finalize .

Now let's look at java / lang / Object.class. For example, I’m wondering what is indicated in it as a super class. We find in the installed jre or jdk rt.jar, unpack:

 jar -xf rt.jar 

And we see that 00 00 is written in his super class, I wonder what will happen if you create a class file without a super class by hand.
I took Hello.class from my previous note .

I opened it in vim and replaced the contents of the buffer with a hex dump vim.wikia.com/wiki/Hex_dump :

 :%!xxd 

Amazed at the power of vim editor. I quickly found the bytes for super_class. Let me remind you that they are according to the specification 4 bytes after the end of constant_pool. The end of constant_pool is searched by the tag of the string 00 01 and the sequence of non-zero bytes, when the zeros begin, the other sections of the constant_pool begin. For other class files this may not be the case, but in my case it worked.
Go back to the binary view:

 :%!xxd -r 

Save the changes. Launch our fixed application:

 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) 


The error, and not yet any, but thrown out of the native method during class loading. Let's go understand, for one can understand how to throw out such errors.

We need the jdk source. I chose OpenJDK for research. We will download them from here:

hg.openjdk.java.net/jdk8/jdk8

And store in Mercury:

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

But this is not all.

We still have to run:

 ./get_source.sh 

And wait. Great, the sources are downloaded and you can search for our error. I make it grep-ohm:

 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", 

We open classFileParser.cpp and there on the 3095 line:

 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; } 

We are interested in this part:

 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 lies in the classFileParser.hpp header file and looks like this:

 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); } } 

I began to look for where _need_verify is exposed and for what is responsible. It turned out in classFileParser.cpp there is such a line:

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

verify is transmitted when calling:

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

This method is called in many places, but we are interested in hotspot / src / share / vm / classfile / classLoader.cpp:

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

How should_verify_for in hotspot / src / share / vm / classfile / verifier.cpp work:

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

Since we pass false to should_verify_class, we look at BytecodeVerificationLocal at 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 } 

Further on, you can find black magic with macros in 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); }; 

But it does not interest me yet. I need to figure out the value of BytecodeVerificationLocal, in the case where jvm starts without the -Xverify option. This can be found in the code, but it seems to me that now it is not appropriate to go further derby and it's time to get out. Documentation to help. By default, jvm starts with the -Xverify: remote parameter and BytecodeVerificationLocal will be false.

This means that _need_verify is also false and in check_property is called assert_property (property, msg, index, CHECK) with the parameters false, “Invalid superclass index% u in class file% 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 } 

Actually, an error message is thrown here. Now look at fatal (msg) to find out how to do it.
Although, we have already answered a part of the question. You cannot make a classfile in which the value for super_class is 0 and load it with the default ClassLoader.

So, the fatal defined in hotspot / src / share / vm / utilities / debug.hpp:

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

hotspot / 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(); } 

The implementation of report_and_die () in hotspot / src / share / vm / utilities / vmError.cpp is nontrivial, but it follows that in Java we are no longer returning and display an error message from the depths of jvm. At this point, I want to re-investigate jvm and java.lang.Object.

findings

java.lang.Object is a special class that has a unique class file in which no class is specified as a superclass. It is impossible to create the same class using the Java language, but it is also difficult, if not impossible, to do this by manipulating the class file bytes. I hope I managed to convey some of the admiration that I felt exploring the source code jvm. I urge everyone to try to do the same.

Source: https://habr.com/ru/post/265373/


All Articles