From d5ea6843af8ca063f56d5356b5e29156c25f035e Mon Sep 17 00:00:00 2001 From: chibash Date: Sun, 16 Apr 2017 23:42:05 +0900 Subject: adds javassist.util.HotSwapAgent --- javassist.jar | Bin 724114 -> 727942 bytes src/main/javassist/util/HotSwapAgent.java | 223 ++++++++++++++++++++++++++++++ src/main/javassist/util/HotSwapper.java | 5 + src/test/javassist/HotswapTest.java | 34 +++++ src/test/javassist/JvstTest.java | 1 + src/test/javassist/JvstTest2.java | 1 - 6 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/main/javassist/util/HotSwapAgent.java create mode 100644 src/test/javassist/HotswapTest.java diff --git a/javassist.jar b/javassist.jar index 8d9c1982..ce389bac 100644 Binary files a/javassist.jar and b/javassist.jar differ diff --git a/src/main/javassist/util/HotSwapAgent.java b/src/main/javassist/util/HotSwapAgent.java new file mode 100644 index 00000000..5bfbe53c --- /dev/null +++ b/src/main/javassist/util/HotSwapAgent.java @@ -0,0 +1,223 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later, + * or the Apache License Version 2.0. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.lang.management.ManagementFactory; +import com.sun.tools.attach.VirtualMachine; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * A utility class for dynamically adding a new method + * or modifying an existing method body. + * This class provides {@link #redefine(Class, CtClass)} + * and {@link #redefine(Class[], CtClass[])}, which replace the + * existing class definition with a new one. + * These methods perform the replacement by + * {@code java.lang.instrument.Instrumentation}. For details + * of acceptable modification, + * see the {@code Instrumentation} interface. + * + *

Before calling the {@code redefine} methods, the hotswap agent + * has to be deployed.

+ * + *

To create a hotswap agent, run {@link #createAgentJarFile(String)}. + * For example, the following command creates an agent file named {@code hotswap.jar}. + * + *

+ * $ jshell --class-path javassist.jar
+ * jshell> javassist.util.HotSwapAgent.createAgentJarFile("hotswap.jar") 
+ * 
+ * + *

Then, run the JVM with the VM argument {@code -javaagent:hotswap.jar} + * to deploy the hotswap agent. + *

+ * + *

If the {@code -javaagent} option is not given to the JVM, {@code HotSwapAgent} + * attempts to automatically create and start the hotswap agent on demand. + * This automated deployment may fail. If it fails, manually create the hotswap agent + * and deploy it by {@code -javaagent}.

+ * + *

The {@code HotSwapAgent} requires {@code tools.jar} as well as {@code javassist.jar}.

+ * + *

The idea of this class was given by Adam Lugowski. + * Shigeru Chiba wrote this class by referring + * to his {@code RedefineClassAgent}. + * For details, see this discussion. + *

+ * + * @see #redefine(Class, CtClass) + * @see #redefine(Class[], CtClass[]) + * @since 3.22 + */ +public class HotSwapAgent { + private static Instrumentation instrumentation = null; + + /** + * Obtains the {@code Instrumentation} object. + * + * @return null when it is not available. + */ + public Instrumentation instrumentation() { return instrumentation; } + + /** + * The entry point invoked when this agent is started by {@code -javaagent}. + */ + public static void premain(String agentArgs, Instrumentation inst) throws Throwable { + agentmain(agentArgs, inst); + } + + /** + * The entry point invoked when this agent is started after the JVM starts. + */ + public static void agentmain(String agentArgs, Instrumentation inst) throws Throwable { + if (!inst.isRedefineClassesSupported()) + throw new RuntimeException("this JVM does not support redefinition of classes"); + + instrumentation = inst; + } + + /** + * Redefines a class. + */ + public static void redefine(Class oldClass, CtClass newClass) + throws NotFoundException, IOException, CannotCompileException + { + Class[] old = { oldClass }; + CtClass[] newClasses = { newClass }; + redefine(old, newClasses); + } + + /** + * Redefines classes. + */ + public static void redefine(Class[] oldClasses, CtClass[] newClasses) + throws NotFoundException, IOException, CannotCompileException + { + startAgent(); + ClassDefinition[] defs = new ClassDefinition[oldClasses.length]; + for (int i = 0; i < oldClasses.length; i++) + defs[i] = new ClassDefinition(oldClasses[i], newClasses[i].toBytecode()); + + try { + instrumentation.redefineClasses(defs); + } + catch (ClassNotFoundException e) { + throw new NotFoundException(e.getMessage(), e); + } + catch (UnmodifiableClassException e) { + throw new CannotCompileException(e.getMessage(), e); + } + } + + /** + * Ensures that the agent is ready. + * It attempts to dynamically start the agent if necessary. + */ + private static void startAgent() throws NotFoundException { + if (instrumentation != null) + return; + + try { + File agentJar = createJarFile(); + + String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); + String pid = nameOfRunningVM.substring(0, nameOfRunningVM.indexOf('@')); + + VirtualMachine vm = VirtualMachine.attach(pid); + vm.loadAgent(agentJar.getAbsolutePath(), ""); + vm.detach(); + } + catch (Exception e) { + throw new NotFoundException("hotswap agent", e); + } + + for (int sec = 0; sec < 10 /* sec */; sec++) { + if (instrumentation != null) + return; + + try { + Thread.sleep(1000); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + throw new NotFoundException("hotswap agent (timeout)"); + } + + /** + * Creates an agent file for using {@code HotSwapAgent}. + */ + public static File createAgentJarFile(String fileName) + throws IOException, CannotCompileException, NotFoundException + { + return createJarFile(new File(fileName)); + } + + private static File createJarFile() + throws IOException, CannotCompileException, NotFoundException + { + File jar = File.createTempFile("agent", ".jar"); + jar.deleteOnExit(); + return createJarFile(jar); + } + + private static File createJarFile(File jar) + throws IOException, CannotCompileException, NotFoundException + { + Manifest manifest = new Manifest(); + Attributes attrs = manifest.getMainAttributes(); + attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + attrs.put(new Attributes.Name("Premain-Class"), HotSwapAgent.class.getName()); + attrs.put(new Attributes.Name("Agent-Class"), HotSwapAgent.class.getName()); + attrs.put(new Attributes.Name("Can-Retransform-Classes"), "true"); + attrs.put(new Attributes.Name("Can-Redefine-Classes"), "true"); + + JarOutputStream jos = null; + try { + jos = new JarOutputStream(new FileOutputStream(jar), manifest); + String cname = HotSwapAgent.class.getName(); + JarEntry e = new JarEntry(cname.replace('.', '/') + ".class"); + jos.putNextEntry(e); + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get(cname); + jos.write(clazz.toBytecode()); + jos.closeEntry(); + } + finally { + if (jos != null) + jos.close(); + } + + return jar; + } +} diff --git a/src/main/javassist/util/HotSwapper.java b/src/main/javassist/util/HotSwapper.java index 9028f2aa..f47c3285 100644 --- a/src/main/javassist/util/HotSwapper.java +++ b/src/main/javassist/util/HotSwapper.java @@ -70,7 +70,12 @@ class Trigger { * repatedly call reload() on the same HotSwapper * object so that they can reload a number of classes. * + *

{@code HotSwap} depends on the debug agent to perform hot-swapping + * but it is reported that the debug agent is buggy under massively multithreaded + * environments. If you encounter a problem, try {@link HotSwapAgent}. + * * @since 3.1 + * @see HotSwapAgent */ public class HotSwapper { private VirtualMachine jvm; diff --git a/src/test/javassist/HotswapTest.java b/src/test/javassist/HotswapTest.java new file mode 100644 index 00000000..8b4db598 --- /dev/null +++ b/src/test/javassist/HotswapTest.java @@ -0,0 +1,34 @@ +package javassist; + +import javassist.util.HotSwapAgent; +import junit.framework.TestCase; + +public class HotswapTest extends TestCase { + public static void main(String[] args) throws Exception { + // run java -javaagent:hotswap.jar javassist.HotswapTest + new HotswapTest(HotswapTest.class.getName()).testHotswap(); + } + + public HotswapTest(String name) { + super(name); + } + + public static class Foo { + public int foo() { return 1; } + } + + public void testHotswap() throws Exception { + Foo f = new Foo(); + assertEquals(1, f.foo()); + + ClassPool cp = ClassPool.getDefault(); + CtClass clazz = cp.get(Foo.class.getName()); + CtMethod m = clazz.getDeclaredMethod("foo"); + clazz.removeMethod(m); + clazz.addMethod(CtNewMethod.make("public int foo() { return 2; }", clazz)); + HotSwapAgent.redefine(Foo.class, clazz); + Foo g = new Foo(); + assertEquals(2, g.foo()); + System.out.println("Foo#foo() = " + g.foo()); + } +} diff --git a/src/test/javassist/JvstTest.java b/src/test/javassist/JvstTest.java index 550d4278..d660a335 100644 --- a/src/test/javassist/JvstTest.java +++ b/src/test/javassist/JvstTest.java @@ -1124,6 +1124,7 @@ public class JvstTest extends JvstTestRoot { suite.addTestSuite(testproxy.ProxyFactoryPerformanceTest.class); // remove? suite.addTestSuite(javassist.proxyfactory.ProxyFactoryTest.class); suite.addTestSuite(javassist.proxyfactory.Tester.class); + suite.addTestSuite(javassist.HotswapTest.class); suite.addTestSuite(test.javassist.proxy.ProxySerializationTest.class); suite.addTestSuite(test.javassist.convert.ArrayAccessReplaceTest.class); suite.addTestSuite(test.javassist.proxy.JASSIST113RegressionTest.class); diff --git a/src/test/javassist/JvstTest2.java b/src/test/javassist/JvstTest2.java index 443bbb30..ec314e12 100644 --- a/src/test/javassist/JvstTest2.java +++ b/src/test/javassist/JvstTest2.java @@ -4,7 +4,6 @@ import java.io.*; import java.net.URL; import java.lang.reflect.Method; -import javassist.bytecode.ClassFile; import javassist.expr.*; public class JvstTest2 extends JvstTestRoot { -- cgit v1.2.3