Commit 7c094a26 authored by liang.tang's avatar liang.tang
Browse files

arthas-master

parents
Pipeline #220 failed with stages
in 0 seconds
package com.taobao.arthas.common;
import java.lang.management.ManagementFactory;
import java.util.Map;
/**
*
* @author hengyunabc 2019-02-16
*
*/
public class PidUtils {
private static String PID = "-1";
private static long pid = -1;
private static String MAIN_CLASS = "";
static {
// https://stackoverflow.com/a/7690178
try {
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
int index = jvmName.indexOf('@');
if (index > 0) {
PID = Long.toString(Long.parseLong(jvmName.substring(0, index)));
pid = Long.parseLong(PID);
}
} catch (Throwable e) {
// ignore
}
try {
for (final Map.Entry<String, String> entry : System.getenv().entrySet()) {
if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) // like JAVA_MAIN_CLASS_13328
MAIN_CLASS = entry.getValue();
}
} catch (Throwable e) {
// ignore
}
}
private PidUtils() {
}
public static String currentPid() {
return PID;
}
public static long currentLongPid() {
return pid;
}
public static String mainClass() {
return MAIN_CLASS;
}
}
package com.taobao.arthas.common;
/**
* Enum of supported operating systems.
*
*/
public enum PlatformEnum {
/**
* Microsoft Windows
*/
WINDOWS,
/**
* A flavor of Linux
*/
LINUX,
/**
* macOS (OS X)
*/
MACOSX,
UNKNOWN
}
\ No newline at end of file
package com.taobao.arthas.common;
public class ReflectException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Throwable cause;
public ReflectException(Throwable cause) {
super(cause.getClass().getName() + "-->" + cause.getMessage());
this.cause = cause;
}
public Throwable getCause() {
return this.cause;
}
}
\ No newline at end of file
package com.taobao.arthas.common;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* from spring
* @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ReflectUtils {
private ReflectUtils() {
}
private static final Map primitives = new HashMap(8);
private static final Map transforms = new HashMap(8);
private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader();
// SPRING PATCH BEGIN
private static final Method privateLookupInMethod;
private static final Method lookupDefineClassMethod;
private static final Method classLoaderDefineClassMethod;
private static final ProtectionDomain PROTECTION_DOMAIN;
private static final Throwable THROWABLE;
private static final List<Method> OBJECT_METHODS = new ArrayList<Method>();
static {
Method privateLookupIn;
Method lookupDefineClass;
Method classLoaderDefineClass;
ProtectionDomain protectionDomain;
Throwable throwable = null;
try {
privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.class.getMethod("privateLookupIn", Class.class,
MethodHandles.Lookup.class);
} catch (NoSuchMethodException ex) {
return null;
}
}
});
lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class);
} catch (NoSuchMethodException ex) {
return null;
}
}
});
classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
return ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE,
Integer.TYPE, ProtectionDomain.class);
}
});
protectionDomain = getProtectionDomain(ReflectUtils.class);
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
Method[] methods = Object.class.getDeclaredMethods();
for (Method method : methods) {
if ("finalize".equals(method.getName())
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
continue;
}
OBJECT_METHODS.add(method);
}
return null;
}
});
} catch (Throwable t) {
privateLookupIn = null;
lookupDefineClass = null;
classLoaderDefineClass = null;
protectionDomain = null;
throwable = t;
}
privateLookupInMethod = privateLookupIn;
lookupDefineClassMethod = lookupDefineClass;
classLoaderDefineClassMethod = classLoaderDefineClass;
PROTECTION_DOMAIN = protectionDomain;
THROWABLE = throwable;
}
// SPRING PATCH END
private static final String[] CGLIB_PACKAGES = { "java.lang", };
static {
primitives.put("byte", Byte.TYPE);
primitives.put("char", Character.TYPE);
primitives.put("double", Double.TYPE);
primitives.put("float", Float.TYPE);
primitives.put("int", Integer.TYPE);
primitives.put("long", Long.TYPE);
primitives.put("short", Short.TYPE);
primitives.put("boolean", Boolean.TYPE);
transforms.put("byte", "B");
transforms.put("char", "C");
transforms.put("double", "D");
transforms.put("float", "F");
transforms.put("int", "I");
transforms.put("long", "J");
transforms.put("short", "S");
transforms.put("boolean", "Z");
}
public static ProtectionDomain getProtectionDomain(final Class source) {
if (source == null) {
return null;
}
return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return source.getProtectionDomain();
}
});
}
public static Constructor findConstructor(String desc) {
return findConstructor(desc, defaultLoader);
}
public static Constructor findConstructor(String desc, ClassLoader loader) {
try {
int lparen = desc.indexOf('(');
String className = desc.substring(0, lparen).trim();
return getClass(className, loader).getConstructor(parseTypes(desc, loader));
} catch (ClassNotFoundException ex) {
throw new ReflectException(ex);
} catch (NoSuchMethodException ex) {
throw new ReflectException(ex);
}
}
public static Method findMethod(String desc) {
return findMethod(desc, defaultLoader);
}
public static Method findMethod(String desc, ClassLoader loader) {
try {
int lparen = desc.indexOf('(');
int dot = desc.lastIndexOf('.', lparen);
String className = desc.substring(0, dot).trim();
String methodName = desc.substring(dot + 1, lparen).trim();
return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader));
} catch (ClassNotFoundException ex) {
throw new ReflectException(ex);
} catch (NoSuchMethodException ex) {
throw new ReflectException(ex);
}
}
private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException {
int lparen = desc.indexOf('(');
int rparen = desc.indexOf(')', lparen);
List params = new ArrayList();
int start = lparen + 1;
for (;;) {
int comma = desc.indexOf(',', start);
if (comma < 0) {
break;
}
params.add(desc.substring(start, comma).trim());
start = comma + 1;
}
if (start < rparen) {
params.add(desc.substring(start, rparen).trim());
}
Class[] types = new Class[params.size()];
for (int i = 0; i < types.length; i++) {
types[i] = getClass((String) params.get(i), loader);
}
return types;
}
private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException {
return getClass(className, loader, CGLIB_PACKAGES);
}
private static Class getClass(String className, ClassLoader loader, String[] packages)
throws ClassNotFoundException {
String save = className;
int dimensions = 0;
int index = 0;
while ((index = className.indexOf("[]", index) + 1) > 0) {
dimensions++;
}
StringBuilder brackets = new StringBuilder(className.length() - dimensions);
for (int i = 0; i < dimensions; i++) {
brackets.append('[');
}
className = className.substring(0, className.length() - 2 * dimensions);
String prefix = (dimensions > 0) ? brackets + "L" : "";
String suffix = (dimensions > 0) ? ";" : "";
try {
return Class.forName(prefix + className + suffix, false, loader);
} catch (ClassNotFoundException ignore) {
}
for (int i = 0; i < packages.length; i++) {
try {
return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader);
} catch (ClassNotFoundException ignore) {
}
}
if (dimensions == 0) {
Class c = (Class) primitives.get(className);
if (c != null) {
return c;
}
} else {
String transform = (String) transforms.get(className);
if (transform != null) {
try {
return Class.forName(brackets + transform, false, loader);
} catch (ClassNotFoundException ignore) {
}
}
}
throw new ClassNotFoundException(save);
}
public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
public static Object newInstance(Class type) {
return newInstance(type, EMPTY_CLASS_ARRAY, null);
}
public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) {
return newInstance(getConstructor(type, parameterTypes), args);
}
public static Object newInstance(final Constructor cstruct, final Object[] args) {
boolean flag = cstruct.isAccessible();
try {
if (!flag) {
cstruct.setAccessible(true);
}
Object result = cstruct.newInstance(args);
return result;
} catch (InstantiationException e) {
throw new ReflectException(e);
} catch (IllegalAccessException e) {
throw new ReflectException(e);
} catch (InvocationTargetException e) {
throw new ReflectException(e.getTargetException());
} finally {
if (!flag) {
cstruct.setAccessible(flag);
}
}
}
public static Constructor getConstructor(Class type, Class[] parameterTypes) {
try {
Constructor constructor = type.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor;
} catch (NoSuchMethodException e) {
throw new ReflectException(e);
}
}
public static String[] getNames(Class[] classes) {
if (classes == null)
return null;
String[] names = new String[classes.length];
for (int i = 0; i < names.length; i++) {
names[i] = classes[i].getName();
}
return names;
}
public static Class[] getClasses(Object[] objects) {
Class[] classes = new Class[objects.length];
for (int i = 0; i < objects.length; i++) {
classes[i] = objects[i].getClass();
}
return classes;
}
public static Method findNewInstance(Class iface) {
Method m = findInterfaceMethod(iface);
if (!m.getName().equals("newInstance")) {
throw new IllegalArgumentException(iface + " missing newInstance method");
}
return m;
}
public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) {
Set methods = new HashSet();
for (int i = 0; i < properties.length; i++) {
PropertyDescriptor pd = properties[i];
if (read) {
methods.add(pd.getReadMethod());
}
if (write) {
methods.add(pd.getWriteMethod());
}
}
methods.remove(null);
return (Method[]) methods.toArray(new Method[methods.size()]);
}
public static PropertyDescriptor[] getBeanProperties(Class type) {
return getPropertiesHelper(type, true, true);
}
public static PropertyDescriptor[] getBeanGetters(Class type) {
return getPropertiesHelper(type, true, false);
}
public static PropertyDescriptor[] getBeanSetters(Class type) {
return getPropertiesHelper(type, false, true);
}
private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) {
try {
BeanInfo info = Introspector.getBeanInfo(type, Object.class);
PropertyDescriptor[] all = info.getPropertyDescriptors();
if (read && write) {
return all;
}
List properties = new ArrayList(all.length);
for (int i = 0; i < all.length; i++) {
PropertyDescriptor pd = all[i];
if ((read && pd.getReadMethod() != null) || (write && pd.getWriteMethod() != null)) {
properties.add(pd);
}
}
return (PropertyDescriptor[]) properties.toArray(new PropertyDescriptor[properties.size()]);
} catch (IntrospectionException e) {
throw new ReflectException(e);
}
}
public static Method findDeclaredMethod(final Class type, final String methodName, final Class[] parameterTypes)
throws NoSuchMethodException {
Class cl = type;
while (cl != null) {
try {
return cl.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
cl = cl.getSuperclass();
}
}
throw new NoSuchMethodException(methodName);
}
public static List addAllMethods(final Class type, final List list) {
if (type == Object.class) {
list.addAll(OBJECT_METHODS);
} else
list.addAll(java.util.Arrays.asList(type.getDeclaredMethods()));
Class superclass = type.getSuperclass();
if (superclass != null) {
addAllMethods(superclass, list);
}
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
addAllMethods(interfaces[i], list);
}
return list;
}
public static List addAllInterfaces(Class type, List list) {
Class superclass = type.getSuperclass();
if (superclass != null) {
list.addAll(Arrays.asList(type.getInterfaces()));
addAllInterfaces(superclass, list);
}
return list;
}
public static Method findInterfaceMethod(Class iface) {
if (!iface.isInterface()) {
throw new IllegalArgumentException(iface + " is not an interface");
}
Method[] methods = iface.getDeclaredMethods();
if (methods.length != 1) {
throw new IllegalArgumentException("expecting exactly 1 method in " + iface);
}
return methods[0];
}
// SPRING PATCH BEGIN
public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
return defineClass(className, b, loader, null, null);
}
public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain)
throws Exception {
return defineClass(className, b, loader, protectionDomain, null);
}
public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain,
Class<?> contextClass) throws Exception {
Class c = null;
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader && privateLookupInMethod != null
&& lookupDefineClassMethod != null) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass,
MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
} catch (InvocationTargetException ex) {
Throwable target = ex.getTargetException();
if (target.getClass() != LinkageError.class && target.getClass() != IllegalArgumentException.class) {
throw new ReflectException(target);
}
// in case of plain LinkageError (class already defined)
// or IllegalArgumentException (class in different package):
// fall through to traditional ClassLoader.defineClass below
} catch (Throwable ex) {
throw new ReflectException(ex);
}
}
// Classic option: protected ClassLoader.defineClass method
if (c == null && classLoaderDefineClassMethod != null) {
if (protectionDomain == null) {
protectionDomain = PROTECTION_DOMAIN;
}
Object[] args = new Object[] { className, b, 0, b.length, protectionDomain };
try {
if (!classLoaderDefineClassMethod.isAccessible()) {
classLoaderDefineClassMethod.setAccessible(true);
}
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
} catch (InvocationTargetException ex) {
throw new ReflectException(ex.getTargetException());
} catch (Throwable ex) {
// Fall through if setAccessible fails with InaccessibleObjectException on JDK
// 9+
// (on the module path and/or with a JVM bootstrapped with
// --illegal-access=deny)
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new ReflectException(ex);
}
}
}
// Fallback option: JDK 9+ Lookup.defineClass API even if ClassLoader does not
// match
if (c == null && contextClass != null && contextClass.getClassLoader() != loader
&& privateLookupInMethod != null && lookupDefineClassMethod != null) {
try {
MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass,
MethodHandles.lookup());
c = (Class) lookupDefineClassMethod.invoke(lookup, b);
} catch (InvocationTargetException ex) {
throw new ReflectException(ex.getTargetException());
} catch (Throwable ex) {
throw new ReflectException(ex);
}
}
// No defineClass variant available at all?
if (c == null) {
throw new ReflectException(THROWABLE);
}
// Force static initializers to run.
Class.forName(className, true, loader);
return c;
}
// SPRING PATCH END
public static int findPackageProtected(Class[] classes) {
for (int i = 0; i < classes.length; i++) {
if (!Modifier.isPublic(classes[i].getModifiers())) {
return i;
}
}
return 0;
}
}
package com.taobao.arthas.common;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.List;
import java.util.Random;
import javax.net.ServerSocketFactory;
/**
*
* @author hengyunabc 2018-11-07
*
*/
public class SocketUtils {
/**
* The default minimum value for port ranges used when finding an available
* socket port.
*/
public static final int PORT_RANGE_MIN = 1024;
/**
* The default maximum value for port ranges used when finding an available
* socket port.
*/
public static final int PORT_RANGE_MAX = 65535;
private static final Random random = new Random(System.currentTimeMillis());
private SocketUtils() {
}
public static long findTcpListenProcess(int port) {
try {
if (OSUtils.isWindows()) {
String[] command = { "netstat", "-ano", "-p", "TCP" };
List<String> lines = ExecutingCommand.runNative(command);
for (String line : lines) {
if (line.contains("LISTENING")) {
// TCP 0.0.0.0:49168 0.0.0.0:0 LISTENING 476
String[] strings = line.trim().split("\\s+");
if (strings.length == 5) {
if (strings[1].endsWith(":" + port)) {
return Long.parseLong(strings[4]);
}
}
}
}
}
if (OSUtils.isLinux() || OSUtils.isMac()) {
String pid = ExecutingCommand.getFirstAnswer("lsof -t -s TCP:LISTEN -i TCP:" + port);
if (!pid.trim().isEmpty()) {
return Long.parseLong(pid);
}
}
} catch (Throwable e) {
// ignore
}
return -1;
}
public static boolean isTcpPortAvailable(int port) {
try {
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,
InetAddress.getByName("localhost"));
serverSocket.close();
return true;
} catch (Exception ex) {
return false;
}
}
/**
* Find an available TCP port randomly selected from the range
* [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
*
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort() {
return findAvailableTcpPort(PORT_RANGE_MIN);
}
/**
* Find an available TCP port randomly selected from the range [{@code minPort},
* {@value #PORT_RANGE_MAX}].
*
* @param minPort the minimum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort(int minPort) {
return findAvailableTcpPort(minPort, PORT_RANGE_MAX);
}
/**
* Find an available TCP port randomly selected from the range [{@code minPort},
* {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort(int minPort, int maxPort) {
return findAvailablePort(minPort, maxPort);
}
/**
* Find an available port for this {@code SocketType}, randomly selected from
* the range [{@code minPort}, {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available port number for this socket type
* @throws IllegalStateException if no available port could be found
*/
private static int findAvailablePort(int minPort, int maxPort) {
int portRange = maxPort - minPort;
int candidatePort;
int searchCounter = 0;
do {
if (searchCounter > portRange) {
throw new IllegalStateException(
String.format("Could not find an available tcp port in the range [%d, %d] after %d attempts",
minPort, maxPort, searchCounter));
}
candidatePort = findRandomPort(minPort, maxPort);
searchCounter++;
} while (!isTcpPortAvailable(candidatePort));
return candidatePort;
}
/**
* Find a pseudo-random port number within the range [{@code minPort},
* {@code maxPort}].
*
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return a random port number within the specified range
*/
private static int findRandomPort(int minPort, int maxPort) {
int portRange = maxPort - minPort;
return minPort + random.nextInt(portRange + 1);
}
}
package com.taobao.arthas.common;
/**
*
* @author hengyunabc 2018-11-22
*
*/
public class UsageRender {
private UsageRender() {
}
public static String render(String usage) {
if (AnsiLog.enableColor()) {
StringBuilder sb = new StringBuilder(1024);
String lines[] = usage.split("\\r?\\n");
for (String line : lines) {
if (line.startsWith("Usage: ")) {
sb.append(AnsiLog.green("Usage: "));
sb.append(line.substring("Usage: ".length()));
} else if (!line.startsWith(" ") && line.endsWith(":")) {
sb.append(AnsiLog.green(line));
} else {
sb.append(line);
}
sb.append('\n');
}
return sb.toString();
} else {
return usage;
}
}
}
package com.taobao.arthas.common;
/**
*
* @author hengyunabc 2021-04-27
*
*/
public class VmToolUtils {
private static String libName = null;
static {
if (OSUtils.isMac()) {
libName = "libArthasJniLibrary.dylib";
}
if (OSUtils.isLinux()) {
libName = "libArthasJniLibrary-x64.so";
if (OSUtils.isArm32()) {
libName = "libArthasJniLibrary-arm.so";
} else if (OSUtils.isArm64()) {
libName = "libArthasJniLibrary-aarch64.so";
}
}
if (OSUtils.isWindows()) {
libName = "libArthasJniLibrary-x64.dll";
}
}
public static String detectLibName() {
return libName;
}
}
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package com.taobao.arthas.common.concurrent;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* An alternative weak-key {@link ConcurrentMap} which is similar to
* {@link ConcurrentHashMap}.
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public final class ConcurrentWeakKeyHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
/*
* The basic strategy is to subdivide the table among Segments,
* each of which itself is a concurrently readable hash table.
*/
/**
* The default initial capacity for this table, used when not otherwise
* specified in a constructor.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The default load factor for this table, used when not otherwise specified
* in a constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The default concurrency level for this table, used when not otherwise
* specified in a constructor.
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified by
* either of the constructors with arguments. MUST be a power of two
* &lt;= 1&lt;&lt;30 to ensure that entries are indexable using integers.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The maximum number of segments to allow; used to bound constructor
* arguments.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of unsynchronized retries in size and containsValue methods before
* resorting to locking. This is used to avoid unbounded retries if tables
* undergo continuous modification which would make it impossible to obtain
* an accurate result.
*/
static final int RETRIES_BEFORE_LOCK = 2;
/* ---------------- Fields -------------- */
/**
* Mask value for indexing into segments. The upper bits of a key's hash
* code are used to choose the segment.
*/
final int segmentMask;
/**
* Shift value for indexing within segments.
*/
final int segmentShift;
/**
* The segments, each of which is a specialized hash table
*/
final Segment<K, V>[] segments;
Set<K> keySet;
Set<Map.Entry<K, V>> entrySet;
Collection<V> values;
/* ---------------- Small Utilities -------------- */
/**
* Applies a supplemental hash function to a given hashCode, which defends
* against poor quality hash functions. This is critical because
* ConcurrentReferenceHashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ in lower
* or upper bits.
*/
private static int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += h << 15 ^ 0xffffcd7d;
h ^= h >>> 10;
h += h << 3;
h ^= h >>> 6;
h += (h << 2) + (h << 14);
return h ^ h >>> 16;
}
/**
* Returns the segment that should be used for key with given hash.
*
* @param hash the hash code for the key
* @return the segment
*/
Segment<K, V> segmentFor(int hash) {
return segments[hash >>> segmentShift & segmentMask];
}
private static int hashOf(Object key) {
return hash(key.hashCode());
}
/* ---------------- Inner Classes -------------- */
/**
* A weak-key reference which stores the key hash needed for reclamation.
*/
static final class WeakKeyReference<K> extends WeakReference<K> {
final int hash;
WeakKeyReference(K key, int hash, ReferenceQueue<Object> refQueue) {
super(key, refQueue);
this.hash = hash;
}
public int keyHash() {
return hash;
}
public Object keyRef() {
return this;
}
}
/**
* ConcurrentReferenceHashMap list entry. Note that this is never exported
* out as a user-visible Map.Entry.
*
* Because the value field is volatile, not final, it is legal wrt
* the Java Memory Model for an unsynchronized reader to see null
* instead of initial value when read via a data race. Although a
* reordering leading to this is not likely to ever actually
* occur, the Segment.readValueUnderLock method is used as a
* backup in case a null (pre-initialized) value is ever seen in
* an unsynchronized access method.
*/
static final class HashEntry<K, V> {
final Object keyRef;
final int hash;
volatile Object valueRef;
final HashEntry<K, V> next;
HashEntry(
K key, int hash, HashEntry<K, V> next, V value,
ReferenceQueue<Object> refQueue) {
this.hash = hash;
this.next = next;
keyRef = new WeakKeyReference<K>(key, hash, refQueue);
valueRef = value;
}
@SuppressWarnings("unchecked")
K key() {
return ((Reference<K>) keyRef).get();
}
V value() {
return dereferenceValue(valueRef);
}
@SuppressWarnings("unchecked")
V dereferenceValue(Object value) {
if (value instanceof WeakKeyReference) {
return ((Reference<V>) value).get();
}
return (V) value;
}
void setValue(V value) {
valueRef = value;
}
@SuppressWarnings("unchecked")
static <K, V> HashEntry<K, V>[] newArray(int i) {
return new HashEntry[i];
}
}
/**
* Segments are specialized versions of hash tables. This subclasses from
* ReentrantLock opportunistically, just to simplify some locking and avoid
* separate construction.
*/
static final class Segment<K, V> extends ReentrantLock {
/*
* Segments maintain a table of entry lists that are ALWAYS kept in a
* consistent state, so can be read without locking. Next fields of
* nodes are immutable (final). All list additions are performed at the
* front of each bin. This makes it easy to check changes, and also fast
* to traverse. When nodes would otherwise be changed, new nodes are
* created to replace them. This works well for hash tables since the
* bin lists tend to be short. (The average length is less than two for
* the default load factor threshold.)
*
* Read operations can thus proceed without locking, but rely on
* selected uses of volatiles to ensure that completed write operations
* performed by other threads are noticed. For most purposes, the
* "count" field, tracking the number of elements, serves as that
* volatile variable ensuring visibility. This is convenient because
* this field needs to be read in many read operations anyway:
*
* - All (unsynchronized) read operations must first read the
* "count" field, and should not look at table entries if
* it is 0.
*
* - All (synchronized) write operations should write to
* the "count" field after structurally changing any bin.
* The operations must not take any action that could even
* momentarily cause a concurrent read operation to see
* inconsistent data. This is made easier by the nature of
* the read operations in Map. For example, no operation
* can reveal that the table has grown but the threshold
* has not yet been updated, so there are no atomicity
* requirements for this with respect to reads.
*
* As a guide, all critical volatile reads and writes to the count field
* are marked in code comments.
*/
private static final long serialVersionUID = -8328104880676891126L;
/**
* The number of elements in this segment's region.
*/
transient volatile int count;
/**
* Number of updates that alter the size of the table. This is used
* during bulk-read methods to make sure they see a consistent snapshot:
* If modCounts change during a traversal of segments computing size or
* checking containsValue, then we might have an inconsistent view of
* state so (usually) must retry.
*/
int modCount;
/**
* The table is rehashed when its size exceeds this threshold.
* (The value of this field is always <tt>(capacity * loadFactor)</tt>.)
*/
int threshold;
/**
* The per-segment table.
*/
transient volatile HashEntry<K, V>[] table;
/**
* The load factor for the hash table. Even though this value is same
* for all segments, it is replicated to avoid needing links to outer
* object.
*/
final float loadFactor;
/**
* The collected weak-key reference queue for this segment. This should
* be (re)initialized whenever table is assigned,
*/
transient volatile ReferenceQueue<Object> refQueue;
Segment(int initialCapacity, float lf) {
loadFactor = lf;
setTable(HashEntry.<K, V>newArray(initialCapacity));
}
@SuppressWarnings("unchecked")
static <K, V> Segment<K, V>[] newArray(int i) {
return new Segment[i];
}
private static boolean keyEq(Object src, Object dest) {
return src.equals(dest);
}
/**
* Sets table to new HashEntry array. Call only while holding lock or in
* constructor.
*/
void setTable(HashEntry<K, V>[] newTable) {
threshold = (int) (newTable.length * loadFactor);
table = newTable;
refQueue = new ReferenceQueue<Object>();
}
/**
* Returns properly casted first entry of bin for given hash.
*/
HashEntry<K, V> getFirst(int hash) {
HashEntry<K, V>[] tab = table;
return tab[hash & tab.length - 1];
}
HashEntry<K, V> newHashEntry(
K key, int hash, HashEntry<K, V> next, V value) {
return new HashEntry<K, V>(
key, hash, next, value, refQueue);
}
/**
* Reads value field of an entry under lock. Called if value field ever
* appears to be null. This is possible only if a compiler happens to
* reorder a HashEntry initialization with its table assignment, which
* is legal under memory model but is not known to ever occur.
*/
V readValueUnderLock(HashEntry<K, V> e) {
lock();
try {
removeStale();
return e.value();
} finally {
unlock();
}
}
/* Specialized implementations of map methods */
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && keyEq(key, e.key())) {
Object opaque = e.valueRef;
if (opaque != null) {
return e.dereferenceValue(opaque);
}
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
boolean containsKey(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K, V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && keyEq(key, e.key())) {
return true;
}
e = e.next;
}
}
return false;
}
boolean containsValue(Object value) {
if (count != 0) { // read-volatile
for (HashEntry<K, V> e: table) {
for (; e != null; e = e.next) {
Object opaque = e.valueRef;
V v;
if (opaque == null) {
v = readValueUnderLock(e); // recheck
} else {
v = e.dereferenceValue(opaque);
}
if (value.equals(v)) {
return true;
}
}
}
}
return false;
}
boolean replace(K key, int hash, V oldValue, V newValue) {
lock();
try {
removeStale();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
boolean replaced = false;
if (e != null && oldValue.equals(e.value())) {
replaced = true;
e.setValue(newValue);
}
return replaced;
} finally {
unlock();
}
}
V replace(K key, int hash, V newValue) {
lock();
try {
removeStale();
HashEntry<K, V> e = getFirst(hash);
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
oldValue = e.value();
e.setValue(newValue);
}
return oldValue;
} finally {
unlock();
}
}
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
removeStale();
int c = count;
if (c ++ > threshold) { // ensure capacity
int reduced = rehash();
if (reduced > 0) {
count = (c -= reduced) - 1; // write-volatile
}
}
HashEntry<K, V>[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
while (e != null && (e.hash != hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue;
if (e != null) {
oldValue = e.value();
if (!onlyIfAbsent) {
e.setValue(value);
}
} else {
oldValue = null;
++ modCount;
tab[index] = newHashEntry(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
int rehash() {
HashEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity >= MAXIMUM_CAPACITY) {
return 0;
}
/*
* Reclassify nodes in each list to new Map. Because we are using
* power-of-two expansion, the elements from each bin must either
* stay at same index, or move with a power of two offset. We
* eliminate unnecessary node creation by catching cases where old
* nodes can be reused because their next fields won't change.
* Statistically, at the default threshold, only about one-sixth of
* them need cloning when a table doubles. The nodes they replace
* will be garbage collectable as soon as they are no longer
* referenced by any reader thread that may be in the midst of
* traversing table right now.
*/
HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);
threshold = (int) (newTable.length * loadFactor);
int sizeMask = newTable.length - 1;
int reduce = 0;
for (HashEntry<K, V> e: oldTable) {
// We need to guarantee that any existing reads of old Map can
// proceed. So we cannot yet null out each bin.
if (e != null) {
HashEntry<K, V> next = e.next;
int idx = e.hash & sizeMask;
// Single node on list
if (next == null) {
newTable[idx] = e;
} else {
// Reuse trailing consecutive sequence at same slot
HashEntry<K, V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K, V> last = next; last != null; last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone all remaining nodes
for (HashEntry<K, V> p = e; p != lastRun; p = p.next) {
// Skip GC'd weak references
K key = p.key();
if (key == null) {
reduce++;
continue;
}
int k = p.hash & sizeMask;
HashEntry<K, V> n = newTable[k];
newTable[k] = newHashEntry(key, p.hash, n, p.value());
}
}
}
}
table = newTable;
return reduce;
}
/**
* Remove; match on key only if value null, else match both.
*/
V remove(Object key, int hash, Object value, boolean refRemove) {
lock();
try {
if (!refRemove) {
removeStale();
}
int c = count - 1;
HashEntry<K, V>[] tab = table;
int index = hash & tab.length - 1;
HashEntry<K, V> first = tab[index];
HashEntry<K, V> e = first;
// a reference remove operation compares the Reference instance
while (e != null && key != e.keyRef &&
(refRemove || hash != e.hash || !keyEq(key, e.key()))) {
e = e.next;
}
V oldValue = null;
if (e != null) {
V v = e.value();
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay in list,
// but all preceding ones need to be cloned.
++ modCount;
HashEntry<K, V> newFirst = e.next;
for (HashEntry<K, V> p = first; p != e; p = p.next) {
K pKey = p.key();
if (pKey == null) { // Skip GC'd keys
c --;
continue;
}
newFirst = newHashEntry(
pKey, p.hash, newFirst, p.value());
}
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}
@SuppressWarnings("rawtypes")
void removeStale() {
WeakKeyReference ref;
while ((ref = (WeakKeyReference) refQueue.poll()) != null) {
remove(ref.keyRef(), ref.keyHash(), null, true);
}
}
void clear() {
if (count != 0) {
lock();
try {
Arrays.fill(table, null);
++ modCount;
// replace the reference queue to avoid unnecessary stale
// cleanups
refQueue = new ReferenceQueue<Object>();
count = 0; // write-volatile
} finally {
unlock();
}
}
}
}
/* ---------------- Public operations -------------- */
/**
* Creates a new, empty map with the specified initial capacity, load factor
* and concurrency level.
*
* @param initialCapacity the initial capacity. The implementation performs
* internal sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of
* elements per bin exceeds this threshold.
* @param concurrencyLevel the estimated number of concurrently updating
* threads. The implementation performs internal
* sizing to try to accommodate this many threads.
* @throws IllegalArgumentException if the initial capacity is negative or
* the load factor or concurrencyLevel are
* nonpositive.
*/
public ConcurrentWeakKeyHashMap(
int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) {
throw new IllegalArgumentException();
}
if (concurrencyLevel > MAX_SEGMENTS) {
concurrencyLevel = MAX_SEGMENTS;
}
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++ sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
segments = Segment.newArray(ssize);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity) {
++ c;
}
int cap = 1;
while (cap < c) {
cap <<= 1;
}
for (int i = 0; i < segments.length; ++ i) {
segments[i] = new Segment<K, V>(cap, loadFactor);
}
}
/**
* Creates a new, empty map with the specified initial capacity and load
* factor and with the default reference types (weak keys, strong values),
* and concurrencyLevel (16).
*
* @param initialCapacity The implementation performs internal sizing to
* accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of
* elements per bin exceeds this threshold.
* @throws IllegalArgumentException if the initial capacity of elements is
* negative or the load factor is
* nonpositive
*/
public ConcurrentWeakKeyHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with the specified initial capacity, and with
* default reference types (weak keys, strong values), load factor (0.75)
* and concurrencyLevel (16).
*
* @param initialCapacity the initial capacity. The implementation performs
* internal sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of elements is
* negative.
*/
public ConcurrentWeakKeyHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with a default initial capacity (16), reference
* types (weak keys, strong values), default load factor (0.75) and
* concurrencyLevel (16).
*/
public ConcurrentWeakKeyHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new map with the same mappings as the given map. The map is
* created with a capacity of 1.5 times the number of mappings in the given
* map or 16 (whichever is greater), and a default load factor (0.75) and
* concurrencyLevel (16).
*
* @param m the map
*/
public ConcurrentWeakKeyHashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR,
DEFAULT_CONCURRENCY_LEVEL);
putAll(m);
}
/**
* Returns <tt>true</tt> if this map contains no key-value mappings.
*
* @return <tt>true</tt> if this map contains no key-value mappings
*/
@Override
public boolean isEmpty() {
final Segment<K, V>[] segments = this.segments;
/*
* We keep track of per-segment modCounts to avoid ABA problems in which
* an element in one segment was added and in another removed during
* traversal, in which case the table was never actually empty at any
* point. Note the similar use of modCounts in the size() and
* containsValue() methods, which are the only other methods also
* susceptible to ABA problems.
*/
int[] mc = new int[segments.length];
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
if (segments[i].count != 0) {
return false;
} else {
mcsum += mc[i] = segments[i].modCount;
}
}
// If mcsum happens to be zero, then we know we got a snapshot before
// any modifications at all were made. This is probably common enough
// to bother tracking.
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
if (segments[i].count != 0 || mc[i] != segments[i].modCount) {
return false;
}
}
}
return true;
}
/**
* Returns the number of key-value mappings in this map. If the map contains
* more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
final Segment<K, V>[] segments = this.segments;
long sum = 0;
long check = 0;
int[] mc = new int[segments.length];
// Try a few times to get accurate count. On failure due to continuous
// async changes in table, resort to locking.
for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) {
check = 0;
sum = 0;
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
sum += segments[i].count;
mcsum += mc[i] = segments[i].modCount;
}
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
check += segments[i].count;
if (mc[i] != segments[i].modCount) {
check = -1; // force retry
break;
}
}
}
if (check == sum) {
break;
}
}
if (check != sum) { // Resort to locking all segments
sum = 0;
for (Segment<K, V> segment: segments) {
segment.lock();
}
for (Segment<K, V> segment: segments) {
sum += segment.count;
}
for (Segment<K, V> segment: segments) {
segment.unlock();
}
}
if (sum > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) sum;
}
}
/**
* Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key {@code k} to
* a value {@code v} such that {@code key.equals(k)}, then this method
* returns {@code v}; otherwise it returns {@code null}. (There can be at
* most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
@Override
public V get(Object key) {
int hash = hashOf(key);
return segmentFor(hash).get(key, hash);
}
/**
* Tests if the specified object is a key in this table.
*
* @param key possible key
* @return <tt>true</tt> if and only if the specified object is a key in
* this table, as determined by the <tt>equals</tt> method;
* <tt>false</tt> otherwise.
* @throws NullPointerException if the specified key is null
*/
@Override
public boolean containsKey(Object key) {
int hash = hashOf(key);
return segmentFor(hash).containsKey(key, hash);
}
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified
* value. Note: This method requires a full internal traversal of the hash
* table, and so is much slower than method <tt>containsKey</tt>.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the specified
* value
* @throws NullPointerException if the specified value is null
*/
@Override
public boolean containsValue(Object value) {
if (value == null) {
throw new NullPointerException();
}
// See explanation of modCount use above
final Segment<K, V>[] segments = this.segments;
int[] mc = new int[segments.length];
// Try a few times without locking
for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) {
int mcsum = 0;
for (int i = 0; i < segments.length; ++ i) {
mcsum += mc[i] = segments[i].modCount;
if (segments[i].containsValue(value)) {
return true;
}
}
boolean cleanSweep = true;
if (mcsum != 0) {
for (int i = 0; i < segments.length; ++ i) {
if (mc[i] != segments[i].modCount) {
cleanSweep = false;
break;
}
}
}
if (cleanSweep) {
return false;
}
}
// Resort to locking all segments
for (Segment<K, V> segment: segments) {
segment.lock();
}
boolean found = false;
try {
for (Segment<K, V> segment: segments) {
if (segment.containsValue(value)) {
found = true;
break;
}
}
} finally {
for (Segment<K, V> segment: segments) {
segment.unlock();
}
}
return found;
}
/**
* Legacy method testing if some key maps into the specified value in this
* table. This method is identical in functionality to
* {@link #containsValue}, and exists solely to ensure full compatibility
* with class {@link Hashtable}, which supported this method prior to
* introduction of the Java Collections framework.
*
* @param value a value to search for
* @return <tt>true</tt> if and only if some key maps to the <tt>value</tt>
* argument in this table as determined by the <tt>equals</tt>
* method; <tt>false</tt> otherwise
* @throws NullPointerException if the specified value is null
*/
public boolean contains(Object value) {
return containsValue(value);
}
/**
* Maps the specified key to the specified value in this table. Neither the
* key nor the value can be null.
*
* <p>The value can be retrieved by calling the <tt>get</tt> method with a
* key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key or value is null
*/
@Override
public V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).put(key, hash, value, false);
}
/**
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
public V putIfAbsent(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).put(key, hash, value, true);
}
/**
* Copies all of the mappings from the specified map to this one. These
* mappings replace any mappings that this map had for any of the keys
* currently in the specified map.
*
* @param m mappings to be stored in this map
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e: m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Removes the key (and its corresponding value) from this map. This method
* does nothing if the key is not in the map.
*
* @param key the key that needs to be removed
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key is null
*/
@Override
public V remove(Object key) {
int hash = hashOf(key);
return segmentFor(hash).remove(key, hash, null, false);
}
/**
* @throws NullPointerException if the specified key is null
*/
public boolean remove(Object key, Object value) {
int hash = hashOf(key);
if (value == null) {
return false;
}
return segmentFor(hash).remove(key, hash, value, false) != null;
}
/**
* @throws NullPointerException if any of the arguments are null
*/
public boolean replace(K key, V oldValue, V newValue) {
if (oldValue == null || newValue == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).replace(key, hash, oldValue, newValue);
}
/**
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
public V replace(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
int hash = hashOf(key);
return segmentFor(hash).replace(key, hash, value);
}
/**
* Removes all of the mappings from this map.
*/
@Override
public void clear() {
for (Segment<K, V> segment: segments) {
segment.clear();
}
}
/**
* Removes any stale entries whose keys have been finalized. Use of this
* method is normally not necessary since stale entries are automatically
* removed lazily, when blocking operations are required. However, there are
* some cases where this operation should be performed eagerly, such as
* cleaning up old references to a ClassLoader in a multi-classloader
* environment.
*
* Note: this method will acquire locks, one at a time, across all segments
* of this table, so if it is to be used, it should be used sparingly.
*/
public void purgeStaleEntries() {
for (Segment<K, V> segment: segments) {
segment.removeStale();
}
}
/**
* Returns a {@link Set} view of the keys contained in this map. The set is
* backed by the map, so changes to the map are reflected in the set, and
* vice-versa. The set supports element removal, which removes the
* corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Set<K> keySet() {
Set<K> ks = keySet;
return ks != null? ks : (keySet = new KeySet());
}
/**
* Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are reflected
* in the collection, and vice-versa. The collection supports element
* removal, which removes the corresponding mapping from this map, via the
* <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
* <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
* the <tt>add</tt> or <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Collection<V> values() {
Collection<V> vs = values;
return vs != null? vs : (values = new Values());
}
/**
* Returns a {@link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are reflected in the
* set, and vice-versa. The set supports element removal, which removes the
* corresponding mapping from the map, via the <tt>Iterator.remove</tt>,
* <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
* <tt>clear</tt> operations. It does not support the <tt>add</tt> or
* <tt>addAll</tt> operations.
*
* <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
* will never throw {@link ConcurrentModificationException}, and guarantees
* to traverse elements as they existed upon construction of the iterator,
* and may (but is not guaranteed to) reflect any modifications subsequent
* to construction.
*/
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es = entrySet;
return es != null? es : (entrySet = new EntrySet());
}
/**
* Returns an enumeration of the keys in this table.
*
* @return an enumeration of the keys in this table
* @see #keySet()
*/
public Enumeration<K> keys() {
return new KeyIterator();
}
/**
* Returns an enumeration of the values in this table.
*
* @return an enumeration of the values in this table
* @see #values()
*/
public Enumeration<V> elements() {
return new ValueIterator();
}
/* ---------------- Iterator Support -------------- */
abstract class HashIterator {
int nextSegmentIndex;
int nextTableIndex;
HashEntry<K, V>[] currentTable;
HashEntry<K, V> nextEntry;
HashEntry<K, V> lastReturned;
K currentKey; // Strong reference to weak key (prevents gc)
HashIterator() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
advance();
}
public void rewind() {
nextSegmentIndex = segments.length - 1;
nextTableIndex = -1;
currentTable = null;
nextEntry = null;
lastReturned = null;
currentKey = null;
advance();
}
public boolean hasMoreElements() {
return hasNext();
}
final void advance() {
if (nextEntry != null && (nextEntry = nextEntry.next) != null) {
return;
}
while (nextTableIndex >= 0) {
if ((nextEntry = currentTable[nextTableIndex --]) != null) {
return;
}
}
while (nextSegmentIndex >= 0) {
Segment<K, V> seg = segments[nextSegmentIndex --];
if (seg.count != 0) {
currentTable = seg.table;
for (int j = currentTable.length - 1; j >= 0; -- j) {
if ((nextEntry = currentTable[j]) != null) {
nextTableIndex = j - 1;
return;
}
}
}
}
}
public boolean hasNext() {
while (nextEntry != null) {
if (nextEntry.key() != null) {
return true;
}
advance();
}
return false;
}
HashEntry<K, V> nextEntry() {
do {
if (nextEntry == null) {
throw new NoSuchElementException();
}
lastReturned = nextEntry;
currentKey = lastReturned.key();
advance();
} while (currentKey == null); // Skip GC'd keys
return lastReturned;
}
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
ConcurrentWeakKeyHashMap.this.remove(currentKey);
lastReturned = null;
}
}
final class KeyIterator
extends HashIterator implements ReusableIterator<K>, Enumeration<K> {
public K next() {
return nextEntry().key();
}
public K nextElement() {
return nextEntry().key();
}
}
final class ValueIterator
extends HashIterator implements ReusableIterator<V>, Enumeration<V> {
public V next() {
return nextEntry().value();
}
public V nextElement() {
return nextEntry().value();
}
}
/*
* This class is needed for JDK5 compatibility.
*/
static class SimpleEntry<K, V> implements Entry<K, V> {
private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
key = entry.getKey();
value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
@SuppressWarnings("rawtypes")
Map.Entry e = (Map.Entry) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
@Override
public int hashCode() {
return (key == null? 0 : key.hashCode()) ^ (value == null? 0 : value.hashCode());
}
@Override
public String toString() {
return key + "=" + value;
}
private static boolean eq(Object o1, Object o2) {
return o1 == null? o2 == null : o1.equals(o2);
}
}
/**
* Custom Entry class used by EntryIterator.next(), that relays setValue
* changes to the underlying map.
*/
final class WriteThroughEntry extends SimpleEntry<K, V> {
WriteThroughEntry(K k, V v) {
super(k, v);
}
/**
* Set our entry's value and write through to the map. The value to
* return is somewhat arbitrary here. Since a WriteThroughEntry does not
* necessarily track asynchronous changes, the most recent "previous"
* value could be different from what we return (or could even have been
* removed in which case the put will re-establish). We do not and can
* not guarantee more.
*/
@Override
public V setValue(V value) {
if (value == null) {
throw new NullPointerException();
}
V v = super.setValue(value);
put(getKey(), value);
return v;
}
}
final class EntryIterator extends HashIterator implements
ReusableIterator<Entry<K, V>> {
public Map.Entry<K, V> next() {
HashEntry<K, V> e = nextEntry();
return new WriteThroughEntry(e.key(), e.value());
}
}
final class KeySet extends AbstractSet<K> {
@Override
public Iterator<K> iterator() {
return new KeyIterator();
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
@Override
public boolean remove(Object o) {
return ConcurrentWeakKeyHashMap.this.remove(o) != null;
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
final class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new ValueIterator();
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public boolean contains(Object o) {
return containsValue(o);
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new EntryIterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
V v = get(e.getKey());
return v != null && v.equals(e.getValue());
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Map.Entry<?, ?>)) {
return false;
}
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return ConcurrentWeakKeyHashMap.this.remove(e.getKey(), e.getValue());
}
@Override
public int size() {
return ConcurrentWeakKeyHashMap.this.size();
}
@Override
public boolean isEmpty() {
return ConcurrentWeakKeyHashMap.this.isEmpty();
}
@Override
public void clear() {
ConcurrentWeakKeyHashMap.this.clear();
}
}
}
\ No newline at end of file
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.taobao.arthas.common.concurrent;
import java.util.Iterator;
public interface ReusableIterator<E> extends Iterator<E> {
void rewind();
}
\ No newline at end of file
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-all</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>arthas-core</artifactId>
<name>arthas-core</name>
<properties>
<arthas.deps.package>com.alibaba.arthas.deps</arthas.deps.package>
</properties>
<build>
<finalName>arthas-core</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>arthas-core-shade</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.taobao.arthas.core.Arthas</mainClass>
<manifestEntries>
<Created-By>core engine team, middleware group, alibaba inc.</Created-By>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-Vendor-Id>com.taobao.arthas</Implementation-Vendor-Id>
<Specification-Version>${project.version}</Specification-Version>
<Specification-Title>arthas-core</Specification-Title>
</manifestEntries>
</transformer>
</transformers>
<relocations>
<!-- https://github.com/hengyunabc/arthas-repackage-deps -->
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>${arthas.deps.package}.org.slf4j</shadedPattern>
</relocation>
<relocation>
<pattern>ch.qos.logback</pattern>
<shadedPattern>${arthas.deps.package}.ch.qos.logback</shadedPattern>
</relocation>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>${arthas.deps.package}.io.netty</shadedPattern>
</relocation>
<relocation>
<pattern>com.alibaba.fastjson</pattern>
<shadedPattern>${arthas.deps.package}.com.alibaba.fastjson</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>com.alibaba.middleware:termd-core</artifact>
<excludes>
<exclude>io/termd/core/http/*.js</exclude>
<exclude>io/termd/core/http/*.css</exclude>
<exclude>io/termd/core/http/*.html</exclude>
<exclude>io/termd/core/http/*.png</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<!-- 把原来的logger command相关类还原,因为被shade plugin relocation修改了package -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<configuration>
<tasks>
<unzip src="${project.build.directory}/${project.build.finalName}-shade.${project.packaging}" dest="${project.build.directory}/tmp_out" />
<copy overwrite="true" todir="${project.build.directory}/tmp_out/com/taobao/arthas/core/command/logger">
<fileset dir="${project.build.directory}/classes/com/taobao/arthas/core/command/logger" />
</copy>
<zip basedir="${project.build.directory}/tmp_out" destfile="${project.build.directory}/${project.build.finalName}-shade.${project.packaging}" />
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-spy</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-vmtool</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>bytekit-core</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-memorycompiler</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-tunnel-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>termd-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>cli</artifactId>
</dependency>
<dependency>
<groupId>com.taobao.text</groupId>
<artifactId>text-ui</artifactId>
</dependency>
<dependency>
<groupId>com.fifesoft</groupId>
<artifactId>rsyntaxtextarea</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.codehaus.groovy</groupId>-->
<!--<artifactId>groovy-all</artifactId>-->
<!--</dependency>-->
<!-- slf4j-api/logback-classic/logback-core 不能打包到应用里,因为真正使用的是 arthas-repackage-deps -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba.arthas</groupId>
<artifactId>arthas-repackage-logger</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.4</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>math-game</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-zip</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.modules</groupId>
<artifactId>jboss-modules</artifactId>
<version>1.11.0.Final</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
</dependency>
<!-- for com.sun.tools.attach.VirtualMachine api -->
<dependency>
<groupId>com.github.olivergondza</groupId>
<artifactId>maven-jdk-tools-wrapper</artifactId>
<version>0.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
#arthas.config.overrideAll=true
arthas.telnetPort=3658
arthas.httpPort=8563
arthas.ip=127.0.0.1
# seconds
arthas.sessionTimeout=1800
#arthas.enhanceLoaders=java.lang.ClassLoader
# https://arthas.aliyun.com/doc/en/auth
# arthas.username=arthas
# arthas.password=arthas
# local connection non auth, like telnet 127.0.0.1 3658
arthas.localConnectionNonAuth=true
#arthas.appName=demoapp
#arthas.tunnelServer=ws://127.0.0.1:7777/ws
#arthas.agentId=mmmmmmyiddddd
#arthas.disabledCommands=stop,dump
#arthas.outputPath=arthas-output
\ No newline at end of file
package com.taobao.arthas.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Properties;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import com.taobao.arthas.common.AnsiLog;
import com.taobao.arthas.common.ArthasConstants;
import com.taobao.arthas.common.JavaVersionUtils;
import com.taobao.arthas.core.config.Configure;
import com.taobao.middleware.cli.CLI;
import com.taobao.middleware.cli.CLIs;
import com.taobao.middleware.cli.CommandLine;
import com.taobao.middleware.cli.Option;
import com.taobao.middleware.cli.TypedOption;
/**
* Arthas启动器
*/
public class Arthas {
int abaddddac;
private Arthas(String[] args) throws Exception {
attachAgent(parse(args));
}
private Configure parse(String[] args) {
Option pid = new TypedOption<Long>().setType(Long.class).setShortName("pid").setRequired(true);
Option core = new TypedOption<String>().setType(String.class).setShortName("core").setRequired(true);
Option agent = new TypedOption<String>().setType(String.class).setShortName("agent").setRequired(true);
Option target = new TypedOption<String>().setType(String.class).setShortName("target-ip");
Option telnetPort = new TypedOption<Integer>().setType(Integer.class)
.setShortName("telnet-port");
Option httpPort = new TypedOption<Integer>().setType(Integer.class)
.setShortName("http-port");
Option sessionTimeout = new TypedOption<Integer>().setType(Integer.class)
.setShortName("session-timeout");
Option username = new TypedOption<String>().setType(String.class).setShortName("username");
Option password = new TypedOption<String>().setType(String.class).setShortName("password");
Option tunnelServer = new TypedOption<String>().setType(String.class).setShortName("tunnel-server");
Option agentId = new TypedOption<String>().setType(String.class).setShortName("agent-id");
Option appName = new TypedOption<String>().setType(String.class).setShortName(ArthasConstants.APP_NAME);
Option statUrl = new TypedOption<String>().setType(String.class).setShortName("stat-url");
Option disabledCommands = new TypedOption<String>().setType(String.class).setShortName("disabled-commands");
CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
.addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout)
.addOption(username).addOption(password)
.addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl).addOption(disabledCommands);
CommandLine commandLine = cli.parse(Arrays.asList(args));
Configure configure = new Configure();
configure.setJavaPid((Long) commandLine.getOptionValue("pid"));
configure.setArthasAgent((String) commandLine.getOptionValue("agent"));
configure.setArthasCore((String) commandLine.getOptionValue("core"));
if (commandLine.getOptionValue("session-timeout") != null) {
configure.setSessionTimeout((Integer) commandLine.getOptionValue("session-timeout"));
}
if (commandLine.getOptionValue("target-ip") != null) {
configure.setIp((String) commandLine.getOptionValue("target-ip"));
}
if (commandLine.getOptionValue("telnet-port") != null) {
configure.setTelnetPort((Integer) commandLine.getOptionValue("telnet-port"));
}
if (commandLine.getOptionValue("http-port") != null) {
configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));
}
configure.setUsername((String) commandLine.getOptionValue("username"));
configure.setPassword((String) commandLine.getOptionValue("password"));
configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server"));
configure.setAgentId((String) commandLine.getOptionValue("agent-id"));
configure.setStatUrl((String) commandLine.getOptionValue("stat-url"));
configure.setDisabledCommands((String) commandLine.getOptionValue("disabled-commands"));
configure.setAppName((String) commandLine.getOptionValue(ArthasConstants.APP_NAME));
return configure;
}
private void attachAgent(Configure configure) throws Exception {
VirtualMachineDescriptor virtualMachineDescriptor = null;
for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) {
String pid = descriptor.id();
if (pid.equals(Long.toString(configure.getJavaPid()))) {
virtualMachineDescriptor = descriptor;
break;
}
}
VirtualMachine virtualMachine = null;
try {
if (null == virtualMachineDescriptor) { // 使用 attach(String pid) 这种方式
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
} else {
virtualMachine = VirtualMachine.attach(virtualMachineDescriptor);
}
Properties targetSystemProperties = virtualMachine.getSystemProperties();
String targetJavaVersion = JavaVersionUtils.javaVersionStr(targetSystemProperties);
String currentJavaVersion = JavaVersionUtils.javaVersionStr();
if (targetJavaVersion != null && currentJavaVersion != null) {
if (!targetJavaVersion.equals(currentJavaVersion)) {
AnsiLog.warn("Current VM java version: {} do not match target VM java version: {}, attach may fail.",
currentJavaVersion, targetJavaVersion);
AnsiLog.warn("Target VM JAVA_HOME is {}, arthas-boot JAVA_HOME is {}, try to set the same JAVA_HOME.",
targetSystemProperties.getProperty("java.home"), System.getProperty("java.home"));
}
}
String arthasAgentPath = configure.getArthasAgent();
//convert jar path to unicode string
configure.setArthasAgent(encodeArg(arthasAgentPath));
configure.setArthasCore(encodeArg(configure.getArthasCore()));
try {
virtualMachine.loadAgent(arthasAgentPath,
configure.getArthasCore() + ";" + configure.toString());
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().contains("Non-numeric value found")) {
AnsiLog.warn(e);
AnsiLog.warn("It seems to use the lower version of JDK to attach the higher version of JDK.");
AnsiLog.warn(
"This error message can be ignored, the attach may have been successful, and it will still try to connect.");
} else {
throw e;
}
}
} finally {
if (null != virtualMachine) {
virtualMachine.detach();
}
}
}
private static String encodeArg(String arg) {
try {
return URLEncoder.encode(arg, "utf-8");
} catch (UnsupportedEncodingException e) {
return arg;
}
}
public static void main(String[] args) {
try {
new Arthas(args);
} catch (Throwable t) {
AnsiLog.error("Start arthas failed, exception stack trace: ");
t.printStackTrace();
System.exit(-1);
}
}
}
package com.taobao.arthas.core;
import com.taobao.arthas.common.JavaVersionUtils;
/**
* 全局开关
* Created by vlinux on 15/6/4.
*/
public class GlobalOptions {
public static final String STRICT_MESSAGE = "By default, strict mode is true, "
+ "not allowed to set object properties. "
+ "Want to set object properties, execute `options strict false`";
int bcsssadddd;
/**
* 是否支持系统类<br/>
* 这个开关打开之后将能代理到来自JVM的部分类,由于有非常强的安全风险可能会引起系统崩溃<br/>
* 所以这个开关默认是关闭的,除非你非常了解你要做什么,否则请不要打开
*/
@Option(level = 0,
name = "unsafe",
summary = "Option to support system-level class",
description =
"This option enables to proxy functionality of JVM classes."
+ " Due to serious security risk a JVM crash is possibly be introduced."
+ " Do not activate it unless you are able to manage."
)
public static volatile boolean isUnsafe = false;
/**
* 是否支持dump被增强的类<br/>
* 这个开关打开这后,每次增强类的时候都将会将增强的类dump到文件中,以便于进行反编译分析
*/
@Option(level = 1,
name = "dump",
summary = "Option to dump the enhanced classes",
description =
"This option enables the enhanced classes to be dumped to external file " +
"for further de-compilation and analysis."
)
public static volatile boolean isDump = false;
/**
* 是否支持批量增强<br/>
* 这个开关打开后,每次均是批量增强类
*/
@Option(level = 1,
name = "batch-re-transform",
summary = "Option to support batch reTransform Class",
description = "This options enables to reTransform classes with batch mode."
)
public static volatile boolean isBatchReTransform = true;
/**
* 是否支持json格式化输出<br/>
* 这个开关打开后,使用json格式输出目标对象,配合-x参数使用
*/
@Option(level = 2,
name = "json-format",
summary = "Option to support JSON format of object output",
description = "This option enables to format object output with JSON when -x option selected."
)
public static volatile boolean isUsingJson = false;
/**
* 是否关闭子类
*/
@Option(
level = 1,
name = "disable-sub-class",
summary = "Option to control include sub class when class matching",
description = "This option disable to include sub class when matching class."
)
public static volatile boolean isDisableSubClass = false;
/**
* 是否在interface类里搜索函数
* https://github.com/alibaba/arthas/issues/1105
*/
@Option(
level = 1,
name = "support-default-method",
summary = "Option to control include default method in interface when class matching",
description = "This option disable to include default method in interface when matching class."
)
public static volatile boolean isSupportDefaultMethod = JavaVersionUtils.isGreaterThanJava7();
/**
* 是否日志中保存命令执行结果
*/
@Option(level = 1,
name = "save-result",
summary = "Option to print command's result to log file",
description = "This option enables to save each command's result to log file, " +
"which path is ${user.home}/logs/arthas-cache/result.log."
)
public static volatile boolean isSaveResult = false;
/**
* job的超时时间
*/
@Option(level = 2,
name = "job-timeout",
summary = "Option to job timeout",
description = "This option setting job timeout,The unit can be d, h, m, s for day, hour, minute, second. "
+ "1d is one day in default"
)
public static volatile String jobTimeout = "1d";
/**
* 是否打印parent类里的field
* @see com.taobao.arthas.core.view.ObjectView
*/
@Option(level = 1,
name = "print-parent-fields",
summary = "Option to print all fileds in parent class",
description = "This option enables print files in parent class, default value true."
)
public static volatile boolean printParentFields = true;
/**
* 是否打开verbose 开关
*/
@Option(level = 1,
name = "verbose",
summary = "Option to print verbose information",
description = "This option enables print verbose information, default value false."
)
public static volatile boolean verbose = false;
/**
* 是否打开strict 开关
*/
@Option(level = 1,
name = "strict",
summary = "Option to strict mode",
description = STRICT_MESSAGE
)
public static volatile boolean strict = true;
}
package com.taobao.arthas.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Arthas全局选项
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Option {
/*
* 选项级别,数字越小级别越高
*/
int level();
/*
* 选项名称
*/
String name();
/*
* 选项摘要说明
*/
String summary();
/*
* 命令描述
*/
String description();
}
\ No newline at end of file
package com.taobao.arthas.core.advisor;
public enum AccessPoint {
ACCESS_BEFORE(1, "AtEnter"), ACCESS_AFTER_RETUNING(1 << 1, "AtExit"), ACCESS_AFTER_THROWING(1 << 2, "AtExceptionExit");
private int value;
private String key;
public int getValue() {
return value;
}
public String getKey() {
return key;
}
AccessPoint(int value, String key) {
this.value = value;
this.key = key;
}
}
\ No newline at end of file
package com.taobao.arthas.core.advisor;
/**
* 通知点 Created by vlinux on 15/5/20.
*/
public class Advice {
private final ClassLoader loader;
private final Class<?> clazz;
private final ArthasMethod method;
private final Object target;
private final Object[] params;
private final Object returnObj;
private final Throwable throwExp;
private final boolean isBefore;
private final boolean isThrow;
private final boolean isReturn;
public boolean isBefore() {
return isBefore;
}
public boolean isAfterReturning() {
return isReturn;
}
public boolean isAfterThrowing() {
return isThrow;
}
public ClassLoader getLoader() {
return loader;
}
public Object getTarget() {
return target;
}
public Object[] getParams() {
return params;
}
public Object getReturnObj() {
return returnObj;
}
public Throwable getThrowExp() {
return throwExp;
}
public Class<?> getClazz() {
return clazz;
}
public ArthasMethod getMethod() {
return method;
}
/**
* for finish
*
* @param loader 类加载器
* @param clazz 类
* @param method 方法
* @param target 目标类
* @param params 调用参数
* @param returnObj 返回值
* @param throwExp 抛出异常
* @param access 进入场景
*/
private Advice(
ClassLoader loader,
Class<?> clazz,
ArthasMethod method,
Object target,
Object[] params,
Object returnObj,
Throwable throwExp,
int access) {
this.loader = loader;
this.clazz = clazz;
this.method = method;
this.target = target;
this.params = params;
this.returnObj = returnObj;
this.throwExp = throwExp;
isBefore = (access & AccessPoint.ACCESS_BEFORE.getValue()) == AccessPoint.ACCESS_BEFORE.getValue();
isThrow = (access & AccessPoint.ACCESS_AFTER_THROWING.getValue()) == AccessPoint.ACCESS_AFTER_THROWING.getValue();
isReturn = (access & AccessPoint.ACCESS_AFTER_RETUNING.getValue()) == AccessPoint.ACCESS_AFTER_RETUNING.getValue();
}
public static Advice newForBefore(ClassLoader loader,
Class<?> clazz,
ArthasMethod method,
Object target,
Object[] params) {
return new Advice(
loader,
clazz,
method,
target,
params,
null, //returnObj
null, //throwExp
AccessPoint.ACCESS_BEFORE.getValue()
);
}
public static Advice newForAfterReturning(ClassLoader loader,
Class<?> clazz,
ArthasMethod method,
Object target,
Object[] params,
Object returnObj) {
return new Advice(
loader,
clazz,
method,
target,
params,
returnObj,
null, //throwExp
AccessPoint.ACCESS_AFTER_RETUNING.getValue()
);
}
public static Advice newForAfterThrowing(ClassLoader loader,
Class<?> clazz,
ArthasMethod method,
Object target,
Object[] params,
Throwable throwExp) {
return new Advice(
loader,
clazz,
method,
target,
params,
null, //returnObj
throwExp,
AccessPoint.ACCESS_AFTER_THROWING.getValue()
);
}
}
package com.taobao.arthas.core.advisor;
/**
* 通知监听器<br/>
* Created by vlinux on 15/5/17.
*/
public interface AdviceListener {
long id();
/**
* 监听器创建<br/>
* 监听器被注册时触发
*/
void create();
/**
* 监听器销毁<br/>
* 监听器被销毁时触发
*/
void destroy();
/**
* 前置通知
*
* @param clazz 类
* @param methodName 方法名
* @param methodDesc 方法描述
* @param target 目标类实例
* 若目标为静态方法,则为null
* @param args 参数列表
* @throws Throwable 通知过程出错
*/
void before(
Class<?> clazz, String methodName, String methodDesc,
Object target, Object[] args) throws Throwable;
/**
* 返回通知
*
* @param clazz 类
* @param methodName 方法名
* @param methodDesc 方法描述
* @param target 目标类实例
* 若目标为静态方法,则为null
* @param args 参数列表
* @param returnObject 返回结果
* 若为无返回值方法(void),则为null
* @throws Throwable 通知过程出错
*/
void afterReturning(
Class<?> clazz, String methodName, String methodDesc,
Object target, Object[] args,
Object returnObject) throws Throwable;
/**
* 异常通知
*
* @param clazz 类
* @param methodName 方法名
* @param methodDesc 方法描述
* @param target 目标类实例
* 若目标为静态方法,则为null
* @param args 参数列表
* @param throwable 目标异常
* @throws Throwable 通知过程出错
*/
void afterThrowing(
Class<?> clazz, String methodName, String methodDesc,
Object target, Object[] args,
Throwable throwable) throws Throwable;
}
package com.taobao.arthas.core.advisor;
import java.util.concurrent.atomic.AtomicLong;
import com.taobao.arthas.core.command.express.ExpressException;
import com.taobao.arthas.core.command.express.ExpressFactory;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.system.Process;
import com.taobao.arthas.core.shell.system.ProcessAware;
import com.taobao.arthas.core.util.Constants;
import com.taobao.arthas.core.util.StringUtils;
/**
*
* @author hengyunabc 2020-05-20
*
*/
public abstract class AdviceListenerAdapter implements AdviceListener, ProcessAware {
private static final AtomicLong ID_GENERATOR = new AtomicLong(0);
private Process process;
private long id = ID_GENERATOR.addAndGet(1);
private boolean verbose;
@Override
public long id() {
return id;
}
@Override
public void create() {
// default no-op
}
@Override
public void destroy() {
// default no-op
}
public Process getProcess() {
return process;
}
public void setProcess(Process process) {
this.process = process;
}
@Override
final public void before(Class<?> clazz, String methodName, String methodDesc, Object target, Object[] args)
throws Throwable {
before(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args);
}
@Override
final public void afterReturning(Class<?> clazz, String methodName, String methodDesc, Object target, Object[] args,
Object returnObject) throws Throwable {
afterReturning(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args,
returnObject);
}
@Override
final public void afterThrowing(Class<?> clazz, String methodName, String methodDesc, Object target, Object[] args,
Throwable throwable) throws Throwable {
afterThrowing(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args,
throwable);
}
/**
* 前置通知
*
* @param loader 类加载器
* @param clazz 类
* @param method 方法
* @param target 目标类实例 若目标为静态方法,则为null
* @param args 参数列表
* @throws Throwable 通知过程出错
*/
public abstract void before(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target, Object[] args)
throws Throwable;
/**
* 返回通知
*
* @param loader 类加载器
* @param clazz 类
* @param method 方法
* @param target 目标类实例 若目标为静态方法,则为null
* @param args 参数列表
* @param returnObject 返回结果 若为无返回值方法(void),则为null
* @throws Throwable 通知过程出错
*/
public abstract void afterReturning(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target,
Object[] args, Object returnObject) throws Throwable;
/**
* 异常通知
*
* @param loader 类加载器
* @param clazz 类
* @param method 方法
* @param target 目标类实例 若目标为静态方法,则为null
* @param args 参数列表
* @param throwable 目标异常
* @throws Throwable 通知过程出错
*/
public abstract void afterThrowing(ClassLoader loader, Class<?> clazz, ArthasMethod method, Object target,
Object[] args, Throwable throwable) throws Throwable;
/**
* 判断条件是否满足,满足的情况下需要输出结果
*
* @param conditionExpress 条件表达式
* @param advice 当前的advice对象
* @param cost 本次执行的耗时
* @return true 如果条件表达式满足
*/
protected boolean isConditionMet(String conditionExpress, Advice advice, double cost) throws ExpressException {
return StringUtils.isEmpty(conditionExpress)
|| ExpressFactory.threadLocalExpress(advice).bind(Constants.COST_VARIABLE, cost).is(conditionExpress);
}
protected Object getExpressionResult(String express, Advice advice, double cost) throws ExpressException {
return ExpressFactory.threadLocalExpress(advice).bind(Constants.COST_VARIABLE, cost).get(express);
}
/**
* 是否超过了上限,超过之后,停止输出
*
* @param limit 命令执行上限
* @param currentTimes 当前执行次数
* @return true 如果超过或者达到了上限
*/
protected boolean isLimitExceeded(int limit, int currentTimes) {
return currentTimes >= limit;
}
/**
* 超过次数上限,则不再输出,命令终止
*
* @param process the process to be aborted
* @param limit the limit to be printed
*/
protected void abortProcess(CommandProcess process, int limit) {
process.write("Command execution times exceed limit: " + limit
+ ", so command will exit. You can set it with -n option.\n");
process.end();
}
public boolean isVerbose() {
return verbose;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
}
package com.taobao.arthas.core.advisor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.system.ExecStatus;
import com.taobao.arthas.core.shell.system.Process;
import com.taobao.arthas.core.shell.system.ProcessAware;
/**
*
* TODO line 的记录 listener方式? 还是有string为key,不过 classname|method|desc|num 这样子?
* 判断是否已插入了,可以在两行中间查询,有没有 SpyAPI 的invoke?
*
* TODO trace的怎么搞? trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到
* SPY的invoke ?直接忽略?
*
* TODO trace命令可以动态的增加 新的函数进去不?只要关联上同一个 Listener应该是可以的。
*
* TODO 在SPY里放很多的 Object数组,然后动态的设置进去? 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成
* 存放这些 Listener数组的类? 这样子的话,只要有 Binding那里,查询到一个具体分配好的类, 这样子就可以了?
* 甚至每个ClassLoader里都动态生成这样子的 存放类,那么这样子不可以避免查 ClassLoader了么?
*
* 动态为每一个增强类,生成一个新的类,新的类里,有各种的 ID 数组,保存每一个类的每一种 trace 点的信息??
*
* 多个 watch命令 对同一个类,现在的逻辑是,每个watch都有一个自己的 TransForm,但不会重复增强,因为做了判断。
* watch命令停止时,也没有去掉增强的代码。 只有reset时 才会去掉。
*
* 其实用户想查看局部变量,并不是想查看哪一行! 而是想看某个函数里子调用时的 局部变量的值! 所以实际上是想要一个新的命令,比如 watchinmethod
* , 可以 在某个子调用里,
*
* TODO 现在的trace 可以输出行号,可能不是很精确,但是可以对应上的。 这个在新的方式里怎么支持? 增加一个 linenumber binding?
* 从mehtodNode,向上查找到最近的行号?
*
* TODO 防止重复增强,最重要的应该还是动态增加 annotation,这个才是真正可以做到某一行,某一个子 invoke 都能识别出来的! 无论是
* transform多少次! 字节码怎么动态加 annotation ? annotation里签名用 url ?的key/value方式表达!
* 这样子可以有效还原信息
*
* TODO 是否考虑一个 trace /watch命令之后,得到一个具体的 Listener ID, 允许在另外的窗口里,再次
* trace/watch时指定这个ID,就会查找到,并处理。 这样子的话,真正达到了动态灵活的,一层一层增加的trace !
*
*
* @author hengyunabc 2020-04-24
*
*/
public class AdviceListenerManager {
private static final Logger logger = LoggerFactory.getLogger(AdviceListenerManager.class);
private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader();
static {
// 清理失效的 AdviceListener
ArthasBootstrap.getInstance().getScheduledExecutorService().scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
for (Entry<ClassLoader, ClassLoaderAdviceListenerManager> entry : adviceListenerMap.entrySet()) {
ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
synchronized (adviceListenerManager) {
for (Entry<String, List<AdviceListener>> eee : adviceListenerManager.map.entrySet()) {
List<AdviceListener> listeners = eee.getValue();
List<AdviceListener> newResult = new ArrayList<AdviceListener>();
for (AdviceListener listener : listeners) {
if (listener instanceof ProcessAware) {
ProcessAware processAware = (ProcessAware) listener;
Process process = processAware.getProcess();
if (process == null) {
continue;
}
ExecStatus status = process.status();
if (!status.equals(ExecStatus.TERMINATED)) {
newResult.add(listener);
}
}
}
if (newResult.size() != listeners.size()) {
adviceListenerManager.map.put(eee.getKey(), newResult);
}
}
}
}
} catch (Throwable e) {
try {
logger.error("clean AdviceListener error", e);
} catch (Throwable t) {
// ignore
}
}
}
}, 3, 3, TimeUnit.SECONDS);
}
private static final ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager> adviceListenerMap = new ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager>();
static class ClassLoaderAdviceListenerManager {
private ConcurrentHashMap<String, List<AdviceListener>> map = new ConcurrentHashMap<String, List<AdviceListener>>();
private String key(String className, String methodName, String methodDesc) {
return className + methodName + methodDesc;
}
private String keyForTrace(String className, String owner, String methodName, String methodDesc) {
return className + owner + methodName + methodDesc;
}
public void registerAdviceListener(String className, String methodName, String methodDesc,
AdviceListener listener) {
synchronized (this) {
className = className.replace('/', '.');
String key = key(className, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
if (listeners == null) {
listeners = new ArrayList<AdviceListener>();
map.put(key, listeners);
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public List<AdviceListener> queryAdviceListeners(String className, String methodName, String methodDesc) {
className = className.replace('/', '.');
String key = key(className, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
return listeners;
}
public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc,
AdviceListener listener) {
className = className.replace('/', '.');
String key = keyForTrace(className, owner, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
if (listeners == null) {
listeners = new ArrayList<AdviceListener>();
map.put(key, listeners);
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public List<AdviceListener> queryTraceAdviceListeners(String className, String owner, String methodName,
String methodDesc) {
className = className.replace('/', '.');
String key = keyForTrace(className, owner, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
return listeners;
}
}
public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName,
String methodDesc, AdviceListener listener) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager == null) {
manager = new ClassLoaderAdviceListenerManager();
adviceListenerMap.put(classLoader, manager);
}
manager.registerAdviceListener(className, methodName, methodDesc, listener);
}
public static void updateAdviceListeners() {
}
public static List<AdviceListener> queryAdviceListeners(ClassLoader classLoader, String className,
String methodName, String methodDesc) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager != null) {
return manager.queryAdviceListeners(className, methodName, methodDesc);
}
return null;
}
public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner,
String methodName, String methodDesc, AdviceListener listener) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager == null) {
manager = new ClassLoaderAdviceListenerManager();
adviceListenerMap.put(classLoader, manager);
}
manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener);
}
public static List<AdviceListener> queryTraceAdviceListeners(ClassLoader classLoader, String className,
String owner, String methodName, String methodDesc) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager != null) {
return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc);
}
return null;
}
private static ClassLoader wrap(ClassLoader classLoader) {
if (classLoader != null) {
return classLoader;
}
return FAKEBOOTSTRAPCLASSLOADER;
}
private static class FakeBootstrapClassLoader extends ClassLoader {
}
}
package com.taobao.arthas.core.advisor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
/**
* 通知编织者<br/>
* <p/>
* <h2>线程帧栈与执行帧栈</h2>
* 编织者在执行通知的时候有两个重要的栈:线程帧栈(threadFrameStack),执行帧栈(frameStack)
* <p/>
* Created by vlinux on 15/5/17.
*/
public class AdviceWeaver {
private static final Logger logger = LoggerFactory.getLogger(AdviceWeaver.class);
// 通知监听器集合
private final static Map<Long/*ADVICE_ID*/, AdviceListener> advices
= new ConcurrentHashMap<Long, AdviceListener>();
/**
* 注册监听器
*
* @param listener 通知监听器
*/
public static void reg(AdviceListener listener) {
// 触发监听器创建
listener.create();
// 注册监听器
advices.put(listener.id(), listener);
}
/**
* 注销监听器
*
* @param listener 通知监听器
*/
public static void unReg(AdviceListener listener) {
if (null != listener) {
// 注销监听器
advices.remove(listener.id());
// 触发监听器销毁
listener.destroy();
}
}
public static AdviceListener listener(long id) {
return advices.get(id);
}
/**
* 恢复监听
*
* @param listener 通知监听器
*/
public static void resume(AdviceListener listener) {
// 注册监听器
advices.put(listener.id(), listener);
}
/**
* 暂停监听
*
* @param adviceId 通知ID
*/
public static AdviceListener suspend(long adviceId) {
// 注销监听器
return advices.remove(adviceId);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment