All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scouter.javassist.util.HotSwapper Maven / Gradle / Ivy

There is a newer version: 2.20.0
Show newest version
/*
 * 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 scouter.javassist.util;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;

class Trigger {
    void doSwap() {}
}

/**
 * A utility class for dynamically reloading a class by
 * the Java Platform Debugger Architecture (JPDA), or HotSwap.
 * It works only with JDK 1.4 and later.
 *
 * 

Note: The new definition of the reloaded class must declare * the same set of methods and fields as the original definition. The * schema change between the original and new definitions is not allowed * by the JPDA. * *

To use this class, the JVM must be launched with the following * command line options: * *

For Java 1.4,
*

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
*

For Java 5,
*

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
* *

Note that 8000 is the port number used by HotSwapper. * Any port number can be specified. Since HotSwapper does not * launch another JVM for running a target application, this port number * is used only for inter-thread communication. * *

Furthermore, JAVA_HOME/lib/tools.jar must be included * in the class path. * *

Using HotSwapper is easy. See the following example: * *

 * CtClass clazz = ...
 * byte[] classFile = clazz.toBytecode();
 * HotSwapper hs = new HostSwapper(8000);  // 8000 is a port number.
 * hs.reload("Test", classFile);
 * 
* *

reload() * first unload the Test class and load a new version of * the Test class. * classFile is a byte array containing the new contents of * the class file for the Test class. The developers can * 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; private MethodEntryRequest request; private Map newClassFiles; private Trigger trigger; private static final String HOST_NAME = "localhost"; private static final String TRIGGER_NAME = Trigger.class.getName(); /** * Connects to the JVM. * * @param port the port number used for the connection to the JVM. */ public HotSwapper(int port) throws IOException, IllegalConnectorArgumentsException { this(Integer.toString(port)); } /** * Connects to the JVM. * * @param port the port number used for the connection to the JVM. */ public HotSwapper(String port) throws IOException, IllegalConnectorArgumentsException { jvm = null; request = null; newClassFiles = null; trigger = new Trigger(); AttachingConnector connector = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach"); Map arguments = connector.defaultArguments(); arguments.get("hostname").setValue(HOST_NAME); arguments.get("port").setValue(port); jvm = connector.attach(arguments); EventRequestManager manager = jvm.eventRequestManager(); request = methodEntryRequests(manager, TRIGGER_NAME); } private Connector findConnector(String connector) throws IOException { List connectors = Bootstrap.virtualMachineManager().allConnectors(); for (Connector con:connectors) if (con.name().equals(connector)) return con; throw new IOException("Not found: " + connector); } private static MethodEntryRequest methodEntryRequests( EventRequestManager manager, String classpattern) { MethodEntryRequest mereq = manager.createMethodEntryRequest(); mereq.addClassFilter(classpattern); mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); return mereq; } /* Stops triggering a hotswapper when reload() is called. */ @SuppressWarnings("unused") private void deleteEventRequest(EventRequestManager manager, MethodEntryRequest request) { manager.deleteEventRequest(request); } /** * Reloads a class. * * @param className the fully-qualified class name. * @param classFile the contents of the class file. */ public void reload(String className, byte[] classFile) { ReferenceType classtype = toRefType(className); Map map = new HashMap(); map.put(classtype, classFile); reload2(map, className); } /** * Reloads a class. * * @param classFiles a map between fully-qualified class names * and class files. The type of the class names * is String and the type of the * class files is byte[]. */ public void reload(Map classFiles) { Map map = new HashMap(); String className = null; for (Map.Entry e:classFiles.entrySet()) { className = e.getKey(); map.put(toRefType(className), e.getValue()); } if (className != null) reload2(map, className + " etc."); } private ReferenceType toRefType(String className) { List list = jvm.classesByName(className); if (list == null || list.isEmpty()) throw new RuntimeException("no such class: " + className); return list.get(0); } private void reload2(Map map, String msg) { synchronized (trigger) { startDaemon(); newClassFiles = map; request.enable(); trigger.doSwap(); request.disable(); Map ncf = newClassFiles; if (ncf != null) { newClassFiles = null; throw new RuntimeException("failed to reload: " + msg); } } } private void startDaemon() { new Thread() { private void errorMsg(Throwable e) { System.err.print("Exception in thread \"HotSwap\" "); e.printStackTrace(System.err); } @Override public void run() { EventSet events = null; try { events = waitEvent(); EventIterator iter = events.eventIterator(); while (iter.hasNext()) { Event event = iter.nextEvent(); if (event instanceof MethodEntryEvent) { hotswap(); break; } } } catch (Throwable e) { errorMsg(e); } try { if (events != null) events.resume(); } catch (Throwable e) { errorMsg(e); } } }.start(); } EventSet waitEvent() throws InterruptedException { EventQueue queue = jvm.eventQueue(); return queue.remove(); } void hotswap() { Map map = newClassFiles; jvm.redefineClasses(map); newClassFiles = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy