使用javaagent通过JVMTI接口调用java.lang.instrument.Instrumentation#redefineClasses方法,可以有限地修改已加载的类,关于RedefineClasses接口函数,可参见:JVM(TM) Tool Interface 1.2.1
RedefineClasses() API究竟做了哪些工作,可以贴一段注释:
// The RedefineClasses() API is used to change the definition of one or
// more classes. While the API supports redefining more than one class
// in a single call, in general, the API is discussed in the context of
// changing the definition of a single current class to a single new
// class. For clarity, the current class is will always be called
// "the_class" and the new class will always be called "scratch_class".
//
// The name "the_class" is used because there is only one structure
// that represents a specific class; redefinition does not replace the
// structure, but instead replaces parts of the structure. The name
// "scratch_class" is used because the structure that represents the
// new definition of a specific class is simply used to carry around
// the parts of the new definition until they are used to replace the
// appropriate parts in the_class. Once redefinition of a class is
// complete, scratch_class is thrown away.
//
//
// Implementation Overview:
//
// The RedefineClasses() API is mostly a wrapper around the VM op that
// does the real work. The work is split in varying degrees between
// doit_prologue(), doit() and doit_epilogue().
//
// 1) doit_prologue() is called by the JavaThread on the way to a
// safepoint. It does parameter verification and loads scratch_class
// which involves:
// - parsing the incoming class definition using the_class' class
// loader and security context
// - linking scratch_class
// - merging constant pools and rewriting bytecodes as needed
// for the merged constant pool
// - verifying the bytecodes in scratch_class
// - setting up the constant pool cache and rewriting bytecodes
// as needed to use the cache
// - finally, scratch_class is compared to the_class to verify
// that it is a valid replacement class
// - if everything is good, then scratch_class is saved in an
// instance field in the VM operation for the doit() call
//
// Note: A JavaThread must do the above work.
//
// 2) doit() is called by the VMThread during a safepoint. It installs
// the new class definition(s) which involves:
// - retrieving the scratch_class from the instance field in the
// VM operation
// - house keeping (flushing breakpoints and caches, deoptimizing
// dependent compiled code)
// - replacing parts in the_class with parts from scratch_class
// - adding weak reference(s) to track the obsolete but interesting
// parts of the_class
// - adjusting constant pool caches and vtables in other classes
// that refer to methods in the_class. These adjustments use the
// ClassLoaderDataGraph::classes_do() facility which only allows
// a helper method to be specified. The interesting parameters
// that we would like to pass to the helper method are saved in
// static global fields in the VM operation.
// - telling the SystemDictionary to notice our changes
//
// Note: the above work must be done by the VMThread to be safe.
//
// 3) doit_epilogue() is called by the JavaThread after the VM op
// is finished and the safepoint is done. It simply cleans up
// memory allocated in doit_prologue() and used in doit().
//
题主关于服务器端热部署的需求,现在有很多第三方JVM Agent提供了这种功能,譬如著名的Spring Framework旗下的 Spring-Loaded子项目。看项目说明,可以在运行时修改的class文件的数据范围有了较大的扩展。Spring Loaded is a JVM agent for reloading class file changes whilst a JVM is running. It transforms classes at loadtime to make them amenable to later reloading. Unlike 'hot code replace' which only allows simple changes once a JVM is running (e.g. changes to method bodies), Spring Loaded allows you to add/modify/delete methods/fields/constructors. The annotations on types/methods/fields/constructors can also be modified and it is possible to add/remove/change values in enum types.
Spring Loaded is usable on any bytecode that may run on a JVM, and is actually the reloading system used in Grails 2.