Search code examples
javabytecodebytecode-manipulation

Java: What classes can not be transformed by an agent or by MBean?


I wonder which classes I can not intercept and manipulate by using byte code transformation and java agents.

Q1: I know not all classes can be redefined (altered, manipulated) on load as well as later on. These classes include methods that are non-native but replaced by hard-wired native implementations (like some Spring and System methods). I wonder what classes are off limits for byte code manipulation for one or both of on-load and/or redefine after load?

Q2: Did this set of types and methods not able to be altered has changed for recent JDK / JRE versions?

Q3: If I manipulate the JVM by altering the default class loader (no fancy changes thou), would I be able to increase the number of types possible to be redefined and what and why?

[Additional]

I did quite some research and even ran an agent myself and see what comes along. Basically there are quite some classes missing. Loading all of the JRE classes in the class path one sees some missing classes loaded before the agent mechanism kicks in. It is of cause normally since even the agent needs classes to run and those classes need classes... . But I wonder what is the set of classes one can never alter, why is that so as well as can hacking the JVM bring you any further.

I try to wade into the JVM / Java manipulation basically in-order to understand everything as well as add some nice monitoring tools to my tool-belt. Also I am implementing a class reloading solution for my bigger project.


Solution

  • Well, I did a simple test using

    public static void premain(String agentArgs, Instrumentation inst) {
      System.out.println(System.getProperty("java.version"));
      System.out.println(inst.isRedefineClassesSupported());
      int num=0;
      for(Class<?> clazz:inst.getAllLoadedClasses())
        if(!clazz.isArray() && !inst.isModifiableClass(clazz)) {
          System.out.println("not modifiable "+clazz);
          num++;
        }
      System.out.println((num==0? "all classes are": num+" classes are not")+" transformable");
    }
    

    and as of 1.7.0_51 and 1.8.0_60 it printed:

    true
    all classes are transformable
    

    In other words, your first assumption is wrong. There is no restriction about which classes can be redefined later-on. Of course, when you redefine essential classes, you have to take a lot of care to avoid screwing everything up.

    You are right when you assume that upon java agent start, there are already loaded classes for which load-time transformation is not possible. Note that you can use the same method used in the code above, getAllLoadedClasses(), to learn which classes are already loaded. In my setup, it returns more than 400 non-array classes.

    The only safe assumption you can make about them, is, that the exact set is deliberately unspecified as otherwise, no changes to the implementation of the JVM bootstrap process were possible. That would be a tough restriction—imposed by a non-standard feature…


    Manipulating the system class loader does not change anything, as these classes are not loaded by the system class loader, but by the bootstrap loader. This is the class loader which is represented as null because it can not represented by a Java object instance.

    You can verify it via:

    public static void premain(String agentArgs, Instrumentation inst) {
      System.out.println(System.getProperty("java.version"));
      System.out.println(inst.isRedefineClassesSupported());
      int num=0;
      for(Class<?> clazz:inst.getAllLoadedClasses())
        if(clazz.getClassLoader()!=null) {
          System.out.println("already loaded "+clazz);
          num++;
        }
      System.out.println(num+" non-bootstrap class(es) loaded");
    }
    

    In my setup, it printed:

    1.8.0_60
    true
    already loaded class ExperimentalAgent
    1 non-bootstrap class(es) loaded
    

    showing that the only class loaded through the system class loader is the agent itself, all other already existing classes are loaded and defined by the bootstrap loader.

    When classes loaded by the system class loader contain references to core classes, they are resolved by asking the system class loader, however, it has no other option than delegating to its parent loader to get them resolved in a compatible way. Otherwise, the redefined classes were considered to be different classes than those resolved within the bootstrap loader (i.e. when the core classes reference each other). Note that for classes whose qualified name begins with java., attempts to define them on a Java-side class loader are rejected anyway:

    ClassLoader.defineClass:

    Throws:

    SecurityException - If an attempt is made to add this class to a package that contains classes that were signed by a different set of certificates than this class (which is unsigned), or if name begins with "java.".