docs: private static MethodHandle defineClassJava8(MethodHandles.Lookup lookup) throws ReflectiveOperationException
    {
      MethodHandle defineClass = lookup.findVirtual(
          UnsafeUtils.theUnsafeClass(),
          "defineClass",
          MethodType.methodType(
              Class.class,
              String.class,
              byte[].class,
              int.class,
              int.class,
              ClassLoader.class,
              ProtectionDomain.class
          )
      ).bindTo(UnsafeUtils.theUnsafe());
  
      MethodHandle getProtectionDomain = lookup.unreflect(Class.class.getMethod("getProtectionDomain"));
      MethodHandle getClassLoader = lookup.unreflect(Class.class.getMethod("getClassLoader"));
  
      // apply getProtectionDomain and getClassLoader to the targetClass, modifying the methodHandle as follows:
      // defineClass = (String className, byte[] byteCode, int offset, int length, Class class1, Class class2) ->
      //   defineClass(className, byteCode, offset, length, class1.getClassLoader(), class2.getProtectionDomain())
      defineClass = MethodHandles.filterArguments(defineClass, 5, getProtectionDomain);
      defineClass = MethodHandles.filterArguments(defineClass, 4, getClassLoader);
  
      // duplicate the last argument to apply the methods above to the same class, modifying the methodHandle as follows:
      // defineClass = (String className, byte[] byteCode, int offset, int length, Class targetClass) ->
      //   defineClass(className, byteCode, offset, length, targetClass, targetClass)
      defineClass = MethodHandles.permuteArguments(
          defineClass,
          MethodType.methodType(Class.class, String.class, byte[].class, int.class, int.class, Class.class),
          0, 1, 2, 3, 4, 4
      );
  
      // set offset argument to 0, modifying the methodHandle as follows:
      // defineClass = (String className, byte[] byteCode, int length, Class targetClass) ->
      //   defineClass(className, byteCode, 0, length, targetClass)
      defineClass = MethodHandles.insertArguments(defineClass, 2, (int) 0);
  
      // JDK8 does not implement MethodHandles.arrayLength so we have to roll our own
      MethodHandle arrayLength = lookup.findStatic(
          lookup.lookupClass(),
          "getArrayLength",
          MethodType.methodType(int.class, byte[].class)
      );
  
      // apply arrayLength to the length argument, modifying the methodHandle as follows:
      // defineClass = (String className, byte[] byteCode1, byte[] byteCode2, Class targetClass) ->
      //   defineClass(className, byteCode1, byteCode2.length, targetClass)
      defineClass = MethodHandles.filterArguments(defineClass, 2, arrayLength);
  
      // duplicate the byteCode argument and reorder to match JDK9 signature, modifying the methodHandle as follows:
      // defineClass = (Class targetClass, byte[] byteCode, String className) ->
      //   defineClass(className, byteCode, byteCode, targetClass)
      defineClass = MethodHandles.permuteArguments(
          defineClass,
          MethodType.methodType(Class.class, Class.class, byte[].class, String.class),
          2, 1, 1, 0
      );
  
      return defineClass;
    }