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
Commits
5d7c4150
Commit
5d7c4150
authored
Dec 21, 2023
by
shengnan hu
Browse files
init
parents
Pipeline
#4715
failed with stage
in 30 seconds
Changes
457
Pipelines
620
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
3056 additions
and
0 deletions
+3056
-0
common/src/main/java/com/taobao/arthas/common/ArthasConstants.java
...c/main/java/com/taobao/arthas/common/ArthasConstants.java
+41
-0
common/src/main/java/com/taobao/arthas/common/ExecutingCommand.java
.../main/java/com/taobao/arthas/common/ExecutingCommand.java
+110
-0
common/src/main/java/com/taobao/arthas/common/FileUtils.java
common/src/main/java/com/taobao/arthas/common/FileUtils.java
+150
-0
common/src/main/java/com/taobao/arthas/common/IOUtils.java
common/src/main/java/com/taobao/arthas/common/IOUtils.java
+165
-0
common/src/main/java/com/taobao/arthas/common/JavaVersionUtils.java
.../main/java/com/taobao/arthas/common/JavaVersionUtils.java
+61
-0
common/src/main/java/com/taobao/arthas/common/OSUtils.java
common/src/main/java/com/taobao/arthas/common/OSUtils.java
+136
-0
common/src/main/java/com/taobao/arthas/common/Pair.java
common/src/main/java/com/taobao/arthas/common/Pair.java
+64
-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
common/target/classes/arthas-git.properties
common/target/classes/arthas-git.properties
+13
-0
common/target/classes/com/taobao/arthas/common/AnsiLog.class
common/target/classes/com/taobao/arthas/common/AnsiLog.class
+0
-0
common/target/classes/com/taobao/arthas/common/ArthasConstants.class
...et/classes/com/taobao/arthas/common/ArthasConstants.class
+0
-0
common/target/classes/com/taobao/arthas/common/ExecutingCommand.class
...t/classes/com/taobao/arthas/common/ExecutingCommand.class
+0
-0
No files found.
Too many changes to show.
To preserve performance only
457 of 457+
files are displayed.
Plain diff
Email patch
common/src/main/java/com/taobao/arthas/common/ArthasConstants.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
/**
*
* @author hengyunabc 2020-09-02
*
*/
public
class
ArthasConstants
{
/**
* local address in VM communication
*
* @see io.netty.channel.local.LocalAddress
* @see io.netty.channel.local.LocalChannel
*/
public
static
final
String
NETTY_LOCAL_ADDRESS
=
"arthas-netty-LocalAddress"
;
public
static
final
int
MAX_HTTP_CONTENT_LENGTH
=
1024
*
1024
*
10
;
public
static
final
String
ARTHAS_OUTPUT
=
"arthas-output"
;
public
static
final
String
APP_NAME
=
"app-name"
;
public
static
final
String
PROJECT_NAME
=
"project.name"
;
public
static
final
String
SPRING_APPLICATION_NAME
=
"spring.application.name"
;
public
static
final
int
TELNET_PORT
=
3658
;
public
static
final
String
DEFAULT_WEBSOCKET_PATH
=
"/ws"
;
public
static
final
int
WEBSOCKET_IDLE_SECONDS
=
60
;
/**
* HTTP cookie id
*/
public
static
final
String
ASESSION_KEY
=
"asession"
;
public
static
final
String
DEFAULT_USERNAME
=
"arthas"
;
public
static
final
String
SUBJECT_KEY
=
"subject"
;
public
static
final
String
AUTH
=
"auth"
;
public
static
final
String
USERNAME_KEY
=
"username"
;
public
static
final
String
PASSWORD_KEY
=
"password"
;
}
common/src/main/java/com/taobao/arthas/common/ExecutingCommand.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStreamReader
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* A class for executing on the command line and returning the result of
* execution.
*
* @author alessandro[at]perucchi[dot]org
*/
public
class
ExecutingCommand
{
private
ExecutingCommand
()
{
}
/**
* Executes a command on the native command line and returns the result.
*
* @param cmdToRun
* Command to run
* @return A list of Strings representing the result of the command, or empty
* string if the command failed
*/
public
static
List
<
String
>
runNative
(
String
cmdToRun
)
{
String
[]
cmd
=
cmdToRun
.
split
(
" "
);
return
runNative
(
cmd
);
}
/**
* Executes a command on the native command line and returns the result line by
* line.
*
* @param cmdToRunWithArgs
* Command to run and args, in an array
* @return A list of Strings representing the result of the command, or empty
* string if the command failed
*/
public
static
List
<
String
>
runNative
(
String
[]
cmdToRunWithArgs
)
{
Process
p
=
null
;
try
{
p
=
Runtime
.
getRuntime
().
exec
(
cmdToRunWithArgs
);
}
catch
(
SecurityException
e
)
{
AnsiLog
.
trace
(
"Couldn't run command {}:"
,
Arrays
.
toString
(
cmdToRunWithArgs
));
AnsiLog
.
trace
(
e
);
return
new
ArrayList
<
String
>(
0
);
}
catch
(
IOException
e
)
{
AnsiLog
.
trace
(
"Couldn't run command {}:"
,
Arrays
.
toString
(
cmdToRunWithArgs
));
AnsiLog
.
trace
(
e
);
return
new
ArrayList
<
String
>(
0
);
}
ArrayList
<
String
>
sa
=
new
ArrayList
<
String
>();
BufferedReader
reader
=
new
BufferedReader
(
new
InputStreamReader
(
p
.
getInputStream
()));
try
{
String
line
;
while
((
line
=
reader
.
readLine
())
!=
null
)
{
sa
.
add
(
line
);
}
p
.
waitFor
();
}
catch
(
IOException
e
)
{
AnsiLog
.
trace
(
"Problem reading output from {}:"
,
Arrays
.
toString
(
cmdToRunWithArgs
));
AnsiLog
.
trace
(
e
);
return
new
ArrayList
<
String
>(
0
);
}
catch
(
InterruptedException
ie
)
{
AnsiLog
.
trace
(
"Problem reading output from {}:"
,
Arrays
.
toString
(
cmdToRunWithArgs
));
AnsiLog
.
trace
(
ie
);
Thread
.
currentThread
().
interrupt
();
}
finally
{
IOUtils
.
close
(
reader
);
}
return
sa
;
}
/**
* Return first line of response for selected command.
*
* @param cmd2launch
* String command to be launched
* @return String or empty string if command failed
*/
public
static
String
getFirstAnswer
(
String
cmd2launch
)
{
return
getAnswerAt
(
cmd2launch
,
0
);
}
/**
* Return response on selected line index (0-based) after running selected
* command.
*
* @param cmd2launch
* String command to be launched
* @param answerIdx
* int index of line in response of the command
* @return String whole line in response or empty string if invalid index or
* running of command fails
*/
public
static
String
getAnswerAt
(
String
cmd2launch
,
int
answerIdx
)
{
List
<
String
>
sa
=
ExecutingCommand
.
runNative
(
cmd2launch
);
if
(
answerIdx
>=
0
&&
answerIdx
<
sa
.
size
())
{
return
sa
.
get
(
answerIdx
);
}
return
""
;
}
}
common/src/main/java/com/taobao/arthas/common/FileUtils.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
/**
*
* @see org.apache.commons.io.FileUtils
* @author hengyunabc 2020-05-03
*
*/
public
class
FileUtils
{
public
static
File
getTempDirectory
()
{
return
new
File
(
System
.
getProperty
(
"java.io.tmpdir"
));
}
/**
* Writes a byte array to a file creating the file if it does not exist.
* <p>
* NOTE: As from v1.3, the parent directories of the file will be created if
* they do not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @throws IOException in case of an I/O error
* @since 1.1
*/
public
static
void
writeByteArrayToFile
(
final
File
file
,
final
byte
[]
data
)
throws
IOException
{
writeByteArrayToFile
(
file
,
data
,
false
);
}
/**
* Writes a byte array to a file creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.1
*/
public
static
void
writeByteArrayToFile
(
final
File
file
,
final
byte
[]
data
,
final
boolean
append
)
throws
IOException
{
writeByteArrayToFile
(
file
,
data
,
0
,
data
.
length
,
append
);
}
/**
* Writes {@code len} bytes from the specified byte array starting at offset
* {@code off} to a file, creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param off the start offset in the data
* @param len the number of bytes to write
* @throws IOException in case of an I/O error
* @since 2.5
*/
public
static
void
writeByteArrayToFile
(
final
File
file
,
final
byte
[]
data
,
final
int
off
,
final
int
len
)
throws
IOException
{
writeByteArrayToFile
(
file
,
data
,
off
,
len
,
false
);
}
/**
* Writes {@code len} bytes from the specified byte array starting at offset
* {@code off} to a file, creating the file if it does not exist.
*
* @param file the file to write to
* @param data the content to write to the file
* @param off the start offset in the data
* @param len the number of bytes to write
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @throws IOException in case of an I/O error
* @since 2.5
*/
public
static
void
writeByteArrayToFile
(
final
File
file
,
final
byte
[]
data
,
final
int
off
,
final
int
len
,
final
boolean
append
)
throws
IOException
{
FileOutputStream
out
=
null
;
try
{
out
=
openOutputStream
(
file
,
append
);
out
.
write
(
data
,
off
,
len
);
}
finally
{
IOUtils
.
close
(
out
);
}
}
/**
* Opens a {@link FileOutputStream} for the specified file, checking and
* creating the parent directory if it does not exist.
* <p>
* At the end of the method either the stream will be successfully opened, or an
* exception will have been thrown.
* <p>
* The parent directory will be created if it does not exist. The file will be
* created if it does not exist. An exception is thrown if the file object
* exists but is a directory. An exception is thrown if the file exists but
* cannot be written to. An exception is thrown if the parent directory cannot
* be created.
*
* @param file the file to open for output, must not be {@code null}
* @param append if {@code true}, then bytes will be added to the end of the
* file rather than overwriting
* @return a new {@link FileOutputStream} for the specified file
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be written to
* @throws IOException if a parent directory needs creating but that fails
* @since 2.1
*/
public
static
FileOutputStream
openOutputStream
(
final
File
file
,
final
boolean
append
)
throws
IOException
{
if
(
file
.
exists
())
{
if
(
file
.
isDirectory
())
{
throw
new
IOException
(
"File '"
+
file
+
"' exists but is a directory"
);
}
if
(!
file
.
canWrite
())
{
throw
new
IOException
(
"File '"
+
file
+
"' cannot be written to"
);
}
}
else
{
final
File
parent
=
file
.
getParentFile
();
if
(
parent
!=
null
)
{
if
(!
parent
.
mkdirs
()
&&
!
parent
.
isDirectory
())
{
throw
new
IOException
(
"Directory '"
+
parent
+
"' could not be created"
);
}
}
}
return
new
FileOutputStream
(
file
,
append
);
}
/**
* Reads the contents of a file into a byte array.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.1
*/
public
static
byte
[]
readFileToByteArray
(
final
File
file
)
throws
IOException
{
InputStream
in
=
null
;
try
{
in
=
new
FileInputStream
(
file
);
return
IOUtils
.
getBytes
(
in
);
}
finally
{
IOUtils
.
close
(
in
);
}
}
}
common/src/main/java/com/taobao/arthas/common/IOUtils.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
import
java.io.BufferedInputStream
;
import
java.io.BufferedOutputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.Closeable
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.io.Reader
;
import
java.io.Writer
;
import
java.util.Enumeration
;
import
java.util.zip.ZipEntry
;
import
java.util.zip.ZipFile
;
/**
*
* @author hengyunabc 2018-11-06
*
*/
public
class
IOUtils
{
private
IOUtils
()
{
}
public
static
String
toString
(
InputStream
inputStream
)
throws
IOException
{
ByteArrayOutputStream
result
=
new
ByteArrayOutputStream
();
byte
[]
buffer
=
new
byte
[
1024
];
int
length
;
while
((
length
=
inputStream
.
read
(
buffer
))
!=
-
1
)
{
result
.
write
(
buffer
,
0
,
length
);
}
return
result
.
toString
(
"UTF-8"
);
}
public
static
void
copy
(
InputStream
in
,
OutputStream
out
)
throws
IOException
{
byte
[]
buffer
=
new
byte
[
1024
];
int
len
;
while
((
len
=
in
.
read
(
buffer
))
!=
-
1
)
{
out
.
write
(
buffer
,
0
,
len
);
}
}
/**
* @return a byte[] containing the information contained in the specified
* InputStream.
* @throws java.io.IOException
*/
public
static
byte
[]
getBytes
(
InputStream
input
)
throws
IOException
{
ByteArrayOutputStream
result
=
new
ByteArrayOutputStream
();
copy
(
input
,
result
);
result
.
close
();
return
result
.
toByteArray
();
}
public
static
IOException
close
(
InputStream
input
)
{
return
close
((
Closeable
)
input
);
}
public
static
IOException
close
(
OutputStream
output
)
{
return
close
((
Closeable
)
output
);
}
public
static
IOException
close
(
final
Reader
input
)
{
return
close
((
Closeable
)
input
);
}
public
static
IOException
close
(
final
Writer
output
)
{
return
close
((
Closeable
)
output
);
}
public
static
IOException
close
(
final
Closeable
closeable
)
{
try
{
if
(
closeable
!=
null
)
{
closeable
.
close
();
}
}
catch
(
final
IOException
ioe
)
{
return
ioe
;
}
return
null
;
}
// support jdk6
public
static
IOException
close
(
final
ZipFile
zip
)
{
try
{
if
(
zip
!=
null
)
{
zip
.
close
();
}
}
catch
(
final
IOException
ioe
)
{
return
ioe
;
}
return
null
;
}
public
static
boolean
isSubFile
(
File
parent
,
File
child
)
throws
IOException
{
return
child
.
getCanonicalPath
().
startsWith
(
parent
.
getCanonicalPath
()
+
File
.
separator
);
}
public
static
boolean
isSubFile
(
String
parent
,
String
child
)
throws
IOException
{
return
isSubFile
(
new
File
(
parent
),
new
File
(
child
));
}
public
static
void
unzip
(
String
zipFile
,
String
extractFolder
)
throws
IOException
{
File
file
=
new
File
(
zipFile
);
ZipFile
zip
=
null
;
try
{
int
BUFFER
=
1024
*
8
;
zip
=
new
ZipFile
(
file
);
File
newPath
=
new
File
(
extractFolder
);
newPath
.
mkdirs
();
Enumeration
<?
extends
ZipEntry
>
zipFileEntries
=
zip
.
entries
();
// Process each entry
while
(
zipFileEntries
.
hasMoreElements
())
{
// grab a zip file entry
ZipEntry
entry
=
(
ZipEntry
)
zipFileEntries
.
nextElement
();
String
currentEntry
=
entry
.
getName
();
File
destFile
=
new
File
(
newPath
,
currentEntry
);
if
(!
isSubFile
(
newPath
,
destFile
))
{
throw
new
IOException
(
"Bad zip entry: "
+
currentEntry
);
}
// destFile = new File(newPath, destFile.getName());
File
destinationParent
=
destFile
.
getParentFile
();
// create the parent directory structure if needed
destinationParent
.
mkdirs
();
if
(!
entry
.
isDirectory
())
{
BufferedInputStream
is
=
null
;
BufferedOutputStream
dest
=
null
;
try
{
is
=
new
BufferedInputStream
(
zip
.
getInputStream
(
entry
));
int
currentByte
;
// establish buffer for writing file
byte
data
[]
=
new
byte
[
BUFFER
];
// write the current file to disk
FileOutputStream
fos
=
new
FileOutputStream
(
destFile
);
dest
=
new
BufferedOutputStream
(
fos
,
BUFFER
);
// read and write until last byte is encountered
while
((
currentByte
=
is
.
read
(
data
,
0
,
BUFFER
))
!=
-
1
)
{
dest
.
write
(
data
,
0
,
currentByte
);
}
dest
.
flush
();
}
finally
{
close
(
dest
);
close
(
is
);
}
}
}
}
finally
{
close
(
zip
);
}
}
}
common/src/main/java/com/taobao/arthas/common/JavaVersionUtils.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
import
java.util.Properties
;
/**
*
* @author hengyunabc 2018-11-21
*
*/
public
class
JavaVersionUtils
{
private
static
final
String
VERSION_PROP_NAME
=
"java.specification.version"
;
private
static
final
String
JAVA_VERSION_STR
=
System
.
getProperty
(
VERSION_PROP_NAME
);
private
static
final
float
JAVA_VERSION
=
Float
.
parseFloat
(
JAVA_VERSION_STR
);
private
JavaVersionUtils
()
{
}
public
static
String
javaVersionStr
()
{
return
JAVA_VERSION_STR
;
}
public
static
String
javaVersionStr
(
Properties
props
)
{
return
(
null
!=
props
)
?
props
.
getProperty
(
VERSION_PROP_NAME
):
null
;
}
public
static
float
javaVersion
()
{
return
JAVA_VERSION
;
}
public
static
boolean
isJava6
()
{
return
JAVA_VERSION_STR
.
equals
(
"1.6"
);
}
public
static
boolean
isJava7
()
{
return
JAVA_VERSION_STR
.
equals
(
"1.7"
);
}
public
static
boolean
isJava8
()
{
return
JAVA_VERSION_STR
.
equals
(
"1.8"
);
}
public
static
boolean
isJava9
()
{
return
JAVA_VERSION_STR
.
equals
(
"9"
);
}
public
static
boolean
isLessThanJava9
()
{
return
JAVA_VERSION
<
9.0f
;
}
public
static
boolean
isGreaterThanJava7
()
{
return
JAVA_VERSION
>
1.7f
;
}
public
static
boolean
isGreaterThanJava8
()
{
return
JAVA_VERSION
>
1.8f
;
}
public
static
boolean
isGreaterThanJava11
()
{
return
JAVA_VERSION
>
11.0f
;
}
}
common/src/main/java/com/taobao/arthas/common/OSUtils.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
import
java.util.Locale
;
/**
*
* @author hengyunabc 2018-11-08
*
*/
public
class
OSUtils
{
private
static
final
String
OPERATING_SYSTEM_NAME
=
System
.
getProperty
(
"os.name"
).
toLowerCase
(
Locale
.
ENGLISH
);
private
static
final
String
OPERATING_SYSTEM_ARCH
=
System
.
getProperty
(
"os.arch"
).
toLowerCase
(
Locale
.
ENGLISH
);
private
static
final
String
UNKNOWN
=
"unknown"
;
static
PlatformEnum
platform
;
static
String
arch
;
static
{
if
(
OPERATING_SYSTEM_NAME
.
startsWith
(
"linux"
))
{
platform
=
PlatformEnum
.
LINUX
;
}
else
if
(
OPERATING_SYSTEM_NAME
.
startsWith
(
"mac"
)
||
OPERATING_SYSTEM_NAME
.
startsWith
(
"darwin"
))
{
platform
=
PlatformEnum
.
MACOSX
;
}
else
if
(
OPERATING_SYSTEM_NAME
.
startsWith
(
"windows"
))
{
platform
=
PlatformEnum
.
WINDOWS
;
}
else
{
platform
=
PlatformEnum
.
UNKNOWN
;
}
arch
=
normalizeArch
(
OPERATING_SYSTEM_ARCH
);
}
private
OSUtils
()
{
}
public
static
boolean
isWindows
()
{
return
platform
==
PlatformEnum
.
WINDOWS
;
}
public
static
boolean
isLinux
()
{
return
platform
==
PlatformEnum
.
LINUX
;
}
public
static
boolean
isMac
()
{
return
platform
==
PlatformEnum
.
MACOSX
;
}
public
static
boolean
isCygwinOrMinGW
()
{
if
(
isWindows
())
{
if
((
System
.
getenv
(
"MSYSTEM"
)
!=
null
&&
System
.
getenv
(
"MSYSTEM"
).
startsWith
(
"MINGW"
))
||
"/bin/bash"
.
equals
(
System
.
getenv
(
"SHELL"
)))
{
return
true
;
}
}
return
false
;
}
public
static
String
arch
()
{
return
arch
;
}
public
static
boolean
isArm32
()
{
return
"arm_32"
.
equals
(
arch
);
}
public
static
boolean
isArm64
()
{
return
"aarch_64"
.
equals
(
arch
);
}
private
static
String
normalizeArch
(
String
value
)
{
value
=
normalize
(
value
);
if
(
value
.
matches
(
"^(x8664|amd64|ia32e|em64t|x64)$"
))
{
return
"x86_64"
;
}
if
(
value
.
matches
(
"^(x8632|x86|i[3-6]86|ia32|x32)$"
))
{
return
"x86_32"
;
}
if
(
value
.
matches
(
"^(ia64w?|itanium64)$"
))
{
return
"itanium_64"
;
}
if
(
"ia64n"
.
equals
(
value
))
{
return
"itanium_32"
;
}
if
(
value
.
matches
(
"^(sparc|sparc32)$"
))
{
return
"sparc_32"
;
}
if
(
value
.
matches
(
"^(sparcv9|sparc64)$"
))
{
return
"sparc_64"
;
}
if
(
value
.
matches
(
"^(arm|arm32)$"
))
{
return
"arm_32"
;
}
if
(
"aarch64"
.
equals
(
value
))
{
return
"aarch_64"
;
}
if
(
value
.
matches
(
"^(mips|mips32)$"
))
{
return
"mips_32"
;
}
if
(
value
.
matches
(
"^(mipsel|mips32el)$"
))
{
return
"mipsel_32"
;
}
if
(
"mips64"
.
equals
(
value
))
{
return
"mips_64"
;
}
if
(
"mips64el"
.
equals
(
value
))
{
return
"mipsel_64"
;
}
if
(
value
.
matches
(
"^(ppc|ppc32)$"
))
{
return
"ppc_32"
;
}
if
(
value
.
matches
(
"^(ppcle|ppc32le)$"
))
{
return
"ppcle_32"
;
}
if
(
"ppc64"
.
equals
(
value
))
{
return
"ppc_64"
;
}
if
(
"ppc64le"
.
equals
(
value
))
{
return
"ppcle_64"
;
}
if
(
"s390"
.
equals
(
value
))
{
return
"s390_32"
;
}
if
(
"s390x"
.
equals
(
value
))
{
return
"s390_64"
;
}
return
UNKNOWN
;
}
private
static
String
normalize
(
String
value
)
{
if
(
value
==
null
)
{
return
""
;
}
return
value
.
toLowerCase
(
Locale
.
US
).
replaceAll
(
"[^a-z0-9]+"
,
""
);
}
}
common/src/main/java/com/taobao/arthas/common/Pair.java
0 → 100644
View file @
5d7c4150
package
com.taobao.arthas.common
;
public
class
Pair
<
X
,
Y
>
{
private
final
X
x
;
private
final
Y
y
;
public
Pair
(
X
x
,
Y
y
)
{
this
.
x
=
x
;
this
.
y
=
y
;
}
public
X
getFirst
()
{
return
x
;
}
public
Y
getSecond
()
{
return
y
;
}
public
static
<
A
,
B
>
Pair
<
A
,
B
>
make
(
A
a
,
B
b
)
{
return
new
Pair
<
A
,
B
>(
a
,
b
);
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
o
==
this
)
return
true
;
if
(!(
o
instanceof
Pair
))
return
false
;
Pair
other
=
(
Pair
)
o
;
if
(
x
==
null
)
{
if
(
other
.
x
!=
null
)
return
false
;
}
else
{
if
(!
x
.
equals
(
other
.
x
))
return
false
;
}
if
(
y
==
null
)
{
if
(
other
.
y
!=
null
)
return
false
;
}
else
{
if
(!
y
.
equals
(
other
.
y
))
return
false
;
}
return
true
;
}
@Override
public
int
hashCode
()
{
int
hashCode
=
1
;
if
(
x
!=
null
)
hashCode
=
x
.
hashCode
();
if
(
y
!=
null
)
hashCode
=
(
hashCode
*
31
)
+
y
.
hashCode
();
return
hashCode
;
}
@Override
public
String
toString
()
{
return
"P["
+
x
+
","
+
y
+
"]"
;
}
}
common/src/main/java/com/taobao/arthas/common/PidUtils.java
0 → 100644
View file @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
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 @
5d7c4150
/*
* 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 @
5d7c4150
/*
* 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
common/target/classes/arthas-git.properties
0 → 100644
View file @
5d7c4150
#Generated by Git-Commit-Id-Plugin
git.build.version
=
3.6.4
git.closest.tag.commit.count
=
git.closest.tag.name
=
git.commit.id
=
35f73a652cf4efc30cc57df74dbab7d5c1de757a
git.commit.id.abbrev
=
35f73a6
git.commit.id.describe
=
35f73a6
git.commit.id.describe-short
=
35f73a6
git.commit.message.full
=
ci-set-3
git.commit.message.short
=
ci-set-3
git.commit.user.email
=
shengnan.hu@ustchcs.com
git.commit.user.name
=
shengnan.hu
git.dirty
=
false
common/target/classes/com/taobao/arthas/common/AnsiLog.class
0 → 100644
View file @
5d7c4150
File added
common/target/classes/com/taobao/arthas/common/ArthasConstants.class
0 → 100644
View file @
5d7c4150
File added
common/target/classes/com/taobao/arthas/common/ExecutingCommand.class
0 → 100644
View file @
5d7c4150
File added
Prev
1
…
5
6
7
8
9
10
11
12
13
…
23
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