Java Attach API
看见阿里的arthas,了解下其底层的原理,搜索到下面的文章,原来底层使用的是java的attach的api转载:https://www.cnblogs.com/LittleHann/p/4783581.html阅读目录(Content)1. instrucment与Attach API2. BTrace: VM Attach的两种方式0x1: 指定javaagent参数0x2: 动态Attach
看见阿里的arthas,了解下其底层的原理,搜索到下面的文章,原来底层使用的是java的attach的api
转载: https://www.cnblogs.com/LittleHann/p/4783581.html
阅读目录(Content)
1. instrucment与Attach API
JDK5中增加了一个包java.lang.instrucment,能够对JVM底层组件进行访问。在JDK 5中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作
在Java5中,开发基于Instrucment的应用,需要以下几个步骤
- 编写premain函数
- jar文件打包
- 运行agent
但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了instrument的应用。而Java SE 6的新特性改变了这种情况,通过Java Tool API中的attach方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到instrumentation的目的
在JDK6中,针对这点做了改进,开发者可以在main开始执行以后,再开启自己的Instrucment程序
Attach API不是Java的标准API,而是Sun公司提供的一套扩展API,用来向目标JVM"附着"(Attach)代理工具程序的。有了它,开发者可以方便的监控一个JVM,运行一个外加的代理程序,Sun JVM Attach API功能上非常简单,仅提供了如下几个功能
- 列出当前所有的JVM实例描述
- Attach到其中一个JVM上,建立通信管道
- 让目标JVM加载Agent
Relevant Link:
http://iamzhongyong.iteye.com/blog/1843558
2. BTrace: VM Attach的两种方式
BTrace的特点之一就是可以动态Attach到一个运行的JVM进程上,然后根据BTrace脚本来对目标JVM进行相应的操作
JVM的 Attach有两种方式
1. 指定javaagent参数
2. 运行时动态attach
0x1: 指定javaagent参数
这种方式的特点就是在目标JVM启动时,就确定好了要加载什么样的代理对象,例如
java -javaagent:xxxx.jar TestMain
TestMain.java
package test;
public class TestMain
{
public static void main(String[] args) throws InterruptedException
{
System.out.println("Hello");
}
}
TestAgent.java
package test;
import java.lang.instrument.Instrumentation;
import java.io.*;
public class TestMain
{
public static void agentmain(String args, Instrumentation inst) throws Exception
{
System.out.println("Args:" + args);
}
public static void premain(String args, Instrumentation inst) throws Exception
{
System.out.println("Pre Args:" + args);
Class[] classes = inst.getAllLoadedClasses();
for (Class clazz : classes)
{
System.out.println(clazz.getName());
}
}
}
TestAgent类比较简单,最终它会在目标类的Main方法执行之前,执行premain方法,其主要动作是将以及加载的类打印出来。 我们需要将这个类打包成jar文件,以便在目标JVM启动时候,以参数形式指定给它。打成jar的同时,设定MANIFEST.MF文件的内容。告知目标JVM该如何处理
Agent-Class: TestAgent
Premain-Class: TestAgent
Can-Redine-Classes: true
Can-Retransform-Classes: true
用jar命令将TestAgent打包
1. 编译TestAgent
javac TestAgent.java
2. jar打包
jar cvmf MANIFEST.MF xxx.jar TestAgent.class
启动TestMain,并设置javaagent参数
1. 编译TestMain
javac TestMain.java
2. 启动TestMain
java -javaagent:xxx.jar TestMain
0x2: 动态Attach,load指定Agent
这种方式与之前指定参数的不同在于,其可以在JVM已经运行的情况下,动态的附着上去,并可以动态加载agent
TestMain.java
public class TestMain
{
public static void main(String[] args) throws InterruptedException
{
while(true)
{
Thread.sleep(10000);
new Thread(new WaitThread()).start();
}
}
static class WaitThread implements Runnable
{
@Override
public void run()
{
System.out.println("Hello");
}
}
}
TestAgent.java
import java.lang.instrument.Instrumentation;
import java.io.*;
public class TestAgent
{
public static void agentmain(String args, Instrumentation inst) throws Exception
{
System.out.println("Args:" + args);
}
public static void premain(String args, Instrumentation inst) throws Exception
{
System.out.println("Pre Args:" + args);
Class[] classes = inst.getAllLoadedClasses();
for (Class clazz : classes)
{
System.out.println(clazz.getName());
}
}
}
动态加载agent的情况下,被调用的是agentmain方法, 其会在JVMload的时候,被调用
MANIFEST.MF
Agent-Class: TestAgent
Premain-Class: TestAgent
Can-Redine-Classes: true
Can-Retransform-Classes: true
将类打包为jar包
1. 编译TestAgent
javac TestAgent.java
2. jar打包
jar cvmf MANIFEST.MF xxx.jar TestAgent.class
动态附着到对应的JVM需要使用到JDK的Attach API
Main.java
import com.sun.tools.attach.VirtualMachine;
public class Main
{
public static void main(String[] args) throws Exception
{
VirtualMachine vm = null;
String agentjarpath = "C:/Users/zhenghan.zh/Desktop/新建文件夹/xxx.jar"; //agentjar路径
vm = VirtualMachine.attach("9730");//目标JVM的进程ID(PID)
vm.loadAgent(agentjarpath, "This is Args to the Agent.");
vm.detach();
}
}
一旦运行这个Main方法, 其就会动态的附着到我们对应的JVM进程中,并为目标JVM加载我们指定的Agent,以达到我们想做的事情, 比如BTrace就为在附着到目标JVM后,开启一个ServerSocket,以便达到与目标进程通讯的目的
Relevant Link:
http://ivanzhangwb.github.io/btrace-vm-attach-api/
3. Sun JVM Attach API
Sun JVM Attach API是Sun JVM中的一套非标准的可以连接到JVM上的API,从JDK6开始引入,除了Solaris平台的Sun JVM支持远程的Attach,在其他平台都只允许Attach到本地的JVM上
0x1: 列出当前所有的JVM实例描述
package test;
import java.util.List;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
public class Test
{
public static void main(String[] args)
{
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : list)
{
System.out.println("pid:" + vmd.id() + ":" + vmd.displayName());
}
}
}
//tools.jar needs to be added to the IDE's library path and the program's classpath. The tools.jar file is found in the JDK's lib directory.
0x2: Attach到特定进程的JVM上,并加载Agent
//Attach到JVM上
VirtualMachine virtualmachine = VirtualMachine.attach(pid);
//加载Agent
String javaHome = virtualmachine.getSystemProperties().getProperty("java.home");
String agentPath = javaHome + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar");
File file = new File(agentPath);
if(!file.exists())
{
agentPath = javaHome + File.separator + "lib" + File.separator + "management-agent.jar";
file = new File(agentPath);
if(!file.exists())
throw new IOException("Management agent not found");
}
}
agentPath = file.getCanonicalPath();
try
{
virtualmachine.loadAgent(agentPath, "com.sun.management.jmxremote");
}
catch(AgentLoadException e)
{
throw new IOException(e);
}
catch(AgentInitializationException agentinitializationexception)
{
throw new IOException(e);
}
Properties properties = virtualmachine.getAgentProperties();
address = (String)properties.get("com.sun.management.jmxremote.localConnectorAddress");
virtualmachine.detach();
0x3: Attach API底层实现(windows)
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsAttachProvider.java
public VirtualMachine attachVirtualMachine(String vmid) throws AttachNotSupportedException, IOException
{
checkAttachPermission();
// AttachNotSupportedException will be thrown if the target VM can be determined
// to be not attachable.
testAttachable(vmid);
return new WindowsVirtualMachine(this, vmid);
}
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java
WindowsVirtualMachine(AttachProvider provider, String id) throws AttachNotSupportedException, IOException
{
//继承HotSpotVirtualMachine
super(provider, id);
int pid;
try
{
pid = Integer.parseInt(id);
}
catch (NumberFormatException x)
{
throw new AttachNotSupportedException("Invalid process identifier");
}
//先连接上目标JVM
hProcess = openProcess(pid);
// The target VM might be a pre-6.0 VM so we enqueue a "null" command
// which minimally tests that the enqueue function exists in the target
// VM.
try
{
enqueue(hProcess, stub, null, null);
}
catch (IOException x)
{
throw new AttachNotSupportedException(x.getMessage());
}
}
WindowsVirtualMachine继承HotSpotVirtualMachine,先看看HotSpotVirtualMachine的loadAgent方法
\openjdk\jdk\src\share\classes\sun\tools\attach\HotSpotVirtualMachine.java
/*
* Load JPLIS agent which will load the agent JAR file and invoke
* the agentmain method.
*/
public void loadAgent(String agent, String options) throws AgentLoadException, AgentInitializationException, IOException
{
String args = agent;
if (options != null)
{
args = args + "=" + options;
}
try
{
loadAgentLibrary("instrument", args);
}
catch (AgentLoadException x)
{
throw new InternalError("instrument library is missing in target VM");
}
catch (AgentInitializationException x)
{
/*
* Translate interesting errors into the right exception and
* message (FIXME: create a better interface to the instrument
* implementation so this isn't necessary)
*/
int rc = x.returnValue();
switch (rc)
{
case JNI_ENOMEM:
throw new AgentLoadException("Insuffient memory");
case ATTACH_ERROR_BADJAR:
throw new AgentLoadException("Agent JAR not found or no Agent-Class attribute");
case ATTACH_ERROR_NOTONCP:
throw new AgentLoadException("Unable to add JAR file to system class path");
case ATTACH_ERROR_STARTFAIL:
throw new AgentInitializationException("Agent JAR loaded but agent failed to initialize");
default :
throw new AgentLoadException("Failed to load agent - unknown reason: " + rc);
}
}
}
loadAgentLibrary("instrument", args);
/*
* Load agent library
* If isAbsolute is true then the agent library is the absolute path
* to the library and thus will not be expanded in the target VM.
* if isAbsolute is false then the agent library is just a library
* name and it will be expended in the target VM.
*/
private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) throws AgentLoadException, AgentInitializationException, IOException
{
InputStream in = execute("load",
agentLibrary,
isAbsolute ? "true" : "false",
options);
try
{
int result = readInt(in);
if (result != 0)
{
throw new AgentInitializationException("Agent_OnAttach failed", result);
}
}
finally
{
in.close();
}
}
可以看到,Java在Attach到目标进行后,调用execute让目标进行加载Agent类,我们继续分析execute的实现方式,可以看到,JVM进程间通信是JVM Attach API的核心,JVM自身就预留了执行来自Attach进程的指令接口
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java
InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException
{
assert args.length <= 3; // includes null
// create a pipe using a random name
int r = (new Random()).nextInt();
String pipename = "\\\\.\\pipe\\javatool" + r;
long hPipe = createPipe(pipename);
// check if we are detached - in theory it's possible that detach is invoked
// after this check but before we enqueue the command.
if (hProcess == -1)
{
closePipe(hPipe);
throw new IOException("Detached from target VM");
}
try
{
// enqueue the command to the process
enqueue(hProcess, stub, cmd, pipename, args);
// wait for command to complete - process will connect with the
// completion status
connectPipe(hPipe);
// create an input stream for the pipe
PipedInputStream is = new PipedInputStream(hPipe);
// read completion status
int status = readInt(is);
if (status != 0)
{
// special case the load command so that the right exception is thrown
if (cmd.equals("load"))
{
throw new AgentLoadException("Failed to load agent library");
}
else
{
throw new IOException("Command failed in target VM");
}
}
// return the input stream
return is;
}
catch (IOException ioe)
{
closePipe(hPipe);
throw ioe;
}
}
JVM的execute方法中调用了大量native方法,并且从代码中可以看出,JVM Attach的进程间通信使用了管道进行通信
Relevant Link:
http://ayufox.iteye.com/blog/655761
http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html
http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/index.html
更多推荐
所有评论(0)