Java Attach API

1. instrucment與Attach API

JDK5中增長了一個包java.lang.instrucment,可以對JVM底層組件進行訪問。在JDK 5中,Instrument 要求在運行前利用命令行參數或者系統參數來設置代理類,在實際的運行之中,虛擬機在初始化之時(在絕大多數的 Java 類庫被載入以前),instrumentation的設置已經啓動,並在虛擬機中設置了回調函數,檢測特定類的加載狀況,並完成實際工做
​在Java5中,開發基於Instrucment的應用,須要如下幾個步驟html

  1. 編寫premain函數
  2. ​jar文件打包
  3. ​運行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功能上很是簡單,僅提供了以下幾個功能java

  1. 列出當前全部的JVM實例描述
  2. Attach到其中一個JVM上,創建通訊管道
  3. 讓目標JVM加載Agent

Relevant Link:git

http://iamzhongyong.iteye.com/blog/1843558

 

2. BTrace: VM Attach的兩種方式

BTrace的特色之一就是能夠動態Attach到一個運行的JVM進程上,而後根據BTrace腳原本對目標JVM進行相應的操做
JVM的 Attach有兩種方式github

1. 指定javaagent參數
2. 運行時動態attach

0x1: 指定javaagent參數

這種方式的特色就是在目標JVM啓動時,就肯定好了要加載什麼樣的代理對象,例如windows

java -javaagent:xxxx.jar TestMain

TestMain.javaapi

package test;

public class TestMain 
{ 
    public static void main(String[] args) throws InterruptedException
    {
        System.out.println("Hello");
    }

}

TestAgent.javaoracle

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該如何處理dom

Agent-Class: TestAgent
Premain-Class: TestAgent
Can-Redine-Classes: true
Can-Retransform-Classes: true

用jar命令將TestAgent打包ide

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 
相關文章
相關標籤/搜索