aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchibash <chiba@javassist.org>2017-04-16 23:42:05 +0900
committerchibash <chiba@javassist.org>2017-04-16 23:42:05 +0900
commitd5ea6843af8ca063f56d5356b5e29156c25f035e (patch)
tree749a5afc02a35eb269d2c029a72c515a3a4cccd3
parent25fdc6bafad29e4b1c7b0f4b340641a4d7f8c476 (diff)
downloadjavassist-d5ea6843af8ca063f56d5356b5e29156c25f035e.tar.gz
javassist-d5ea6843af8ca063f56d5356b5e29156c25f035e.zip
adds javassist.util.HotSwapAgent
-rw-r--r--javassist.jarbin724114 -> 727942 bytes
-rw-r--r--src/main/javassist/util/HotSwapAgent.java223
-rw-r--r--src/main/javassist/util/HotSwapper.java5
-rw-r--r--src/test/javassist/HotswapTest.java34
-rw-r--r--src/test/javassist/JvstTest.java1
-rw-r--r--src/test/javassist/JvstTest2.java1
6 files changed, 263 insertions, 1 deletions
diff --git a/javassist.jar b/javassist.jar
index 8d9c1982..ce389bac 100644
--- a/javassist.jar
+++ b/javassist.jar
Binary files 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.
+ *
+ * <p>Before calling the {@code redefine} methods, the hotswap agent
+ * has to be deployed.</p>
+ *
+ * <p>To create a hotswap agent, run {@link #createAgentJarFile(String)}.
+ * For example, the following command creates an agent file named {@code hotswap.jar}.
+ *
+ * <pre>
+ * $ jshell --class-path javassist.jar
+ * jshell> javassist.util.HotSwapAgent.createAgentJarFile("hotswap.jar")
+ * </pre>
+ *
+ * <p>Then, run the JVM with the VM argument {@code -javaagent:hotswap.jar}
+ * to deploy the hotswap agent.
+ * </p>
+ *
+ * <p>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}.</p>
+ *
+ * <p>The {@code HotSwapAgent} requires {@code tools.jar} as well as {@code javassist.jar}.</p>
+ *
+ * <p>The idea of this class was given by <a href="https://github.com/alugowski">Adam Lugowski</a>.
+ * Shigeru Chiba wrote this class by referring
+ * to his <a href="https://github.com/turn/RedefineClassAgent">{@code RedefineClassAgent}</a>.
+ * For details, see <a href="https://github.com/jboss-javassist/javassist/issues/119">this discussion</a>.
+ * </p>
+ *
+ * @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 <code>reload()</code> on the same <code>HotSwapper</code>
* object so that they can reload a number of classes.
*
+ * <p>{@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 {