Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Administrator
arthas-master
Commits
7c094a26
Commit
7c094a26
authored
Dec 18, 2023
by
liang.tang
Browse files
arthas-master
parents
Pipeline
#220
failed with stages
in 0 seconds
Changes
361
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
3659 additions
and
0 deletions
+3659
-0
common/src/main/java/com/taobao/arthas/common/PidUtils.java
common/src/main/java/com/taobao/arthas/common/PidUtils.java
+56
-0
common/src/main/java/com/taobao/arthas/common/PlatformEnum.java
.../src/main/java/com/taobao/arthas/common/PlatformEnum.java
+23
-0
common/src/main/java/com/taobao/arthas/common/ReflectException.java
.../main/java/com/taobao/arthas/common/ReflectException.java
+17
-0
common/src/main/java/com/taobao/arthas/common/ReflectUtils.java
.../src/main/java/com/taobao/arthas/common/ReflectUtils.java
+511
-0
common/src/main/java/com/taobao/arthas/common/SocketUtils.java
...n/src/main/java/com/taobao/arthas/common/SocketUtils.java
+151
-0
common/src/main/java/com/taobao/arthas/common/UsageRender.java
...n/src/main/java/com/taobao/arthas/common/UsageRender.java
+33
-0
common/src/main/java/com/taobao/arthas/common/VmToolUtils.java
...n/src/main/java/com/taobao/arthas/common/VmToolUtils.java
+30
-0
common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java
...ao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java
+1472
-0
common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java
...com/taobao/arthas/common/concurrent/ReusableIterator.java
+23
-0
core/pom.xml
core/pom.xml
+270
-0
core/src/main/java/arthas.properties
core/src/main/java/arthas.properties
+25
-0
core/src/main/java/com/taobao/arthas/core/Arthas.java
core/src/main/java/com/taobao/arthas/core/Arthas.java
+160
-0
core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
+140
-0
core/src/main/java/com/taobao/arthas/core/Option.java
core/src/main/java/com/taobao/arthas/core/Option.java
+36
-0
core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java
...main/java/com/taobao/arthas/core/advisor/AccessPoint.java
+23
-0
core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
.../src/main/java/com/taobao/arthas/core/advisor/Advice.java
+146
-0
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java
...n/java/com/taobao/arthas/core/advisor/AdviceListener.java
+73
-0
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java
...com/taobao/arthas/core/advisor/AdviceListenerAdapter.java
+157
-0
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java
...com/taobao/arthas/core/advisor/AdviceListenerManager.java
+235
-0
core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java
...ain/java/com/taobao/arthas/core/advisor/AdviceWeaver.java
+78
-0
No files found.
Too many changes to show.
To preserve performance only
361 of 361+
files are displayed.
Plain diff
Email patch
common/src/main/java/com/taobao/arthas/common/PidUtils.java
0 → 100644
View file @
7c094a26
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
;
}
}
common/src/main/java/com/taobao/arthas/common/PlatformEnum.java
0 → 100644
View file @
7c094a26
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
common/src/main/java/com/taobao/arthas/common/ReflectException.java
0 → 100644
View file @
7c094a26
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
common/src/main/java/com/taobao/arthas/common/ReflectUtils.java
0 → 100644
View file @
7c094a26
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
;
}
}
common/src/main/java/com/taobao/arthas/common/SocketUtils.java
0 → 100644
View file @
7c094a26
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
);
}
}
common/src/main/java/com/taobao/arthas/common/UsageRender.java
0 → 100644
View file @
7c094a26
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
;
}
}
}
common/src/main/java/com/taobao/arthas/common/VmToolUtils.java
0 → 100644
View file @
7c094a26
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
;
}
}
common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java
0 → 100644
View file @
7c094a26
/*
* 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
* <= 1<<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
common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java
0 → 100644
View file @
7c094a26
/*
* 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
core/pom.xml
0 → 100644
View file @
7c094a26
<?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>
core/src/main/java/arthas.properties
0 → 100644
View file @
7c094a26
#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
core/src/main/java/com/taobao/arthas/core/Arthas.java
0 → 100644
View file @
7c094a26
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
);
}
}
}
core/src/main/java/com/taobao/arthas/core/GlobalOptions.java
0 → 100644
View file @
7c094a26
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
;
}
core/src/main/java/com/taobao/arthas/core/Option.java
0 → 100644
View file @
7c094a26
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
core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java
0 → 100644
View file @
7c094a26
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
core/src/main/java/com/taobao/arthas/core/advisor/Advice.java
0 → 100644
View file @
7c094a26
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
()
);
}
}
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java
0 → 100644
View file @
7c094a26
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
;
}
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java
0 → 100644
View file @
7c094a26
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
;
}
}
core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java
0 → 100644
View file @
7c094a26
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
{
}
}
core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java
0 → 100644
View file @
7c094a26
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
);
}
}
Prev
1
…
3
4
5
6
7
8
9
10
11
…
19
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment