See the root: java.lang.Object

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

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

    However, there is such a class and I will give here the source code for java / lang / Object.java, omitting javadoc, and try to shed light on some points related to the jvm implementation:

    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 point out in this code?

    In total, Object has 11 public methods, 5 ordinary and 6 with a native implementation.

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

    By default, all objects are compared for link equality. By the way, I once liked a joke 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 contains nothing unusual, except that hashCode () is converted to a hexadecimal string. And if apangin hadn’t written that now it’s impossible to calculate hashCode , I would have thought that earlier Java programmers could find their object by hashCode, because he was nothing more than a reference. Those 32 bit times for many have passed, 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());
    }
    

    Besides the fact that wait belongs to the primitives providing multithreading, 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 regular methods in java.lang.Object:

    protected void finalize() throws Throwable { }
    

    This method does nothing, and there is a lot of material that you should avoid using finalize and Finalizer , meaning finalize .

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

    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 with your hands without a super class.
    I took Hello.class from my previous post .

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

    :%!xxd
    

    I marveled at the power of vim editor. Quickly found bytes for super_class. Let me remind you that they lie 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 a sequence of non-zero bytes, when zeros begin, other sections of constant_pool go. For other class files this may not be the case, but in my case it worked.
    We return back to the binary form:

    :%!xxd -r
    

    Save changes. We 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)
    


    An error, not even some, but thrown out of the native method during class loading. Let's go deal, for one we can understand how to throw such errors.

    We need the jdk sources. 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 that's not all.

    We must also run:

    ./get_source.sh
    

    And wait. Well, the source has downloaded and you can search for our error. I am doing this 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",
    

    Open classFileParser.cpp and there on line 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;
    }
    

    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 header file classFileParser.hpp 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 set and what is responsible for. It turned out that classFileParser.cpp has this line:

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

    verify is passed when called:

    instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
                                                        ClassLoaderData* loader_data,
                                                        Handle protection_domain,
                                                        KlassHandle host_klass,
                                                        GrowableArray* 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 work in 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;
    }
    

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

    Digging further, 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 this does not interest me yet. I need to find out the value of BytecodeVerificationLocal, in the case when jvm starts without the -Xverify parameter. This can be found in the code, but it seems to me that it is not appropriate now to climb further derby and it's time to get out. Documentation to help. By default, jvm starts with the -Xverify: remote option and BytecodeVerificationLocal will be false.

    So _need_verify is also false, and assert_property (property, msg, index, CHECK) is called in check_property with 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, this is where the error message is thrown. Now look at fatal (msg) to find out how to do this.
    Although, we have already answered a part of the question. You cannot make a classfile in which the super_class field is set to 0 and load it using the default ClassLoader.

    So fatal is 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 not trivial, but it follows that we are no longer returning to Java and display an error message from the bowels of jvm. On this I want to stop exploring jvm and java.lang.Object.

    conclusions

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

    Also popular now: