달력

3

« 2024/3 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
.. .. ..

 

[출처] Attach API|작성자 날씬한곰

 

Java EE/초급 2007/09/03 14:08

저자 JZ Ventures사의 사장 겸 대표 컨설턴트 John Zukowski

이 아티클의 영문 원본은 http://java.sun.com/mailers/techtips/corejava/2007/tt0807.html#2
에서 볼수 있습니다.

Java 플랫폼을 다룰 때 일반적으로 표준 java.*javax.* 라이브러리를 사용하여 프로그래밍한다. 그러나 Sun JDK(Java Development Kit)에서 이 라이브러리만 제공하는 것은 아니다. JDK 설치 디렉토리 아래 lib 디렉토리에 있는 tools.jar 파일에서 몇 가지 추가 API를 제공한다. javadoc 도구와 Attach API라는 API의 확장이 지원됨을 알 수 있다.

이름이 암시하는 것처럼, Attach API는 대상 VM(virtual machine)에 연결할 수 있게 해준다. 다른 VM에 연결하면 현재 상황을 모니터링하고 문제가 발생하기 전에 이를 발견할 수도 있다. Attach API 클래스는 com.sun.tools.attachcom.sun.tools.attach.spi 패키지에 있지만, 일반적으로 com.sun.tools.attach.spi 클래스를 직접 사용하는 일은 없다.

사용하지 않을 .spi 패키지에 있는 유일한 클래스를 비롯하여 전체 API는 총 7개의 클래스로 구성된다. 그 중에 3개는 exception 클래스이고 하나는 permission이다. 따라서 VirtualMachine 및 이와 관련된 VirtualMachineDescriptor 클래스를 제외하곤 학습할 내용이 그리 많지 않다.

VirtualMachine 클래스는 특정 JVM 인스턴스를 나타낸다. VirtualMachine 클래스에 프로세스 ID를 제공하여 JVM에 연결한 다음 사용자 정의 동작을 수행하도록 관리 에이전트를 로드한다.

VirtualMachine vm = VirtualMachine.attach (processid);
String agent = ...
vm.loadAgent(agent);

VirtualMachine을 얻는 또 다른 방법은 시스템에 알려진 VM의 목록을 요청하고 관심 있는 하나를 선택하는 것인데, 흔히 이름을 기준으로 한다.

String name = ...
List vms = VirtualMachine.list();
for (VirtualMachineDescriptor vmd: vms) {
    if (vmd.displayName().equals(name)) {
        VirtualMachine vm = VirtualMachine.attach(vmd.id());
        String agent = ...
        vm.loadAgent(agent);
        // ...
    }
}

이 에이전트로 무엇을 할 수 있는지 알아보기에 앞서 고려해야 할 사항 두 가지가 있다. 첫 번째는, loadAgent() 메소드가 설정을 에이전트에 전달하는 두 번째 인수 옵션을 갖는다는 것이다. 여기서는 여러 개의 옵션을 전달할 가능성이 있는 단 하나의 인수만 존재하므로, 여러 개의 인수는 쉼표로 구분된 목록의 형태로 전달된다.

vm.loadAgent (agent, "a=1,b=2,c=3");그런 다음 에이전트는 다음과 비슷한 코드로 그 인수를 분할하는데, 인수가 에이전트의 args 변수에 전달되었다고 가정한다.

String options[] = args.split(",");
for (String option: options)
    System.out.println(option);
}


두 번째로 언급할 점은 현재 VM을 대상 VM으로부터 분리하는 방법이다. 이는 detach() 메소드를 통해 수행된다. loadAgent()를 사용하여 에이전트를 로드한 다음 detach()해야 한다.

JMX 에이전트가 JDK와 함께 제공된 management-agent.jar 파일에 있다. tools.jar와 동일한 디렉토리에 있는 JMX 관리 에이전트는 원격 JMX 에이전트의 MBean Server를 시작하고 그 서버와의 MBeanServerConnection을 얻게 해준다. 그리고 이를 통해 원격 VM에 있는 스레드 등을 나열할 수 있다.

다음 프로그램이 바로 그런 작업을 수행한다. 먼저 식별된 VM과 연결한다. 그런 다음 실행 중인 원격 JMX 서버를 찾고, 아직 시작된 것이 없으면 하나를 시작한다. ,code>management-agent.jar 파일은 원격 VM의 java.home을 찾아 지정한다. 반드시 로컬일 필요는 없다. 일단 연결되면 MBeanServerConnection을 얻으며, 그로부터 ManagementFactory에서 스레드나 ThreadMXBean 등을 쿼리한다. 마지막으로, 스레드와 그 상태의 목록이 표시된다.

import java.lang.management.*;
import java.io.*;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
import com.sun.tools.attach.*;

public class Threads {

  public static void main(String args[]) throws Exception {
    if (args.length != 1) {
      System.err.println("Please provide process id");
      System.exit(-1);
    }
    VirtualMachine vm = VirtualMachine.attach(args[0]);
    String connectorAddr = vm.getAgentProperties().getProperty(
      "com.sun.management.jmxremote.localConnectorAddress");
    if (connectorAddr == null) {
      String agent = vm.getSystemProperties().getProperty(
        "java.home")+File.separator+&qut;lib&qut;+File.separator+
        "management-agent.jar";
      vm.loadAgent(agent);
      connectorAddr = vm.getAgentProperties().getProperty(
        "com.sun.management.jmxremote.localConnectorAddress");
    }
    JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);
    JMXConnector connector = JMXConnectorFactory.connect(serviceURL);
    MBeanServerConnection mbsc = connector.getMBeanServerConnection();
    ObjectName objName = new ObjectName(
      ManagementFactory.THREAD_MXBEAN_NAME);
    Set<ObjectName> mbeans = mbsc.queryNames(objName, null);
    for (ObjectName name: mbeans) {
      ThreadMXBean threadBean;
      threadBean = ManagementFactory.newPlatformMXBeanProxy(
        mbsc, name.toString(), ThreadMXBean.class);
      long threadIds[] = threadBean.getAllThreadIds();
      for (long threadId: threadIds) {
        ThreadInfo threadInfo = threadBean.getThreadInfo(threadId);
        System.out.println (threadInfo.getThreadName() + &qut; / &qut; +
            threadInfo.getThreadState());
      }
    }
  }
}


이 프로그램을 컴파일하려면 해당 CLASSPATH에 tools.jar가 있어야 한다. JAVA_HOME이 Attach API가 포함된 Java SE 6 설치 디렉토리로 설정되었다고 가정하면, 다음 줄이 프로그램을 컴파일한다.
> javac -cp %JAVA_HOME%/lib/tools.jar Threads.java

여기서 프로그램을 실행할 수 있지만, 연결할 대상은 없다. 따라서 프레임을 표시하는 간단한 Swing 프로그램을 소개한다. 특별한 것은 없으며, 그저 알아볼 만한 스레드 이름 몇 개를 나열할 뿐이다.

import java.awt.*
import javax.swing.*

public class MyFrame {
  public static void main(String args[]) {
    Runnable runner = new Runnable() {
      public void run() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setVisible(true);
      }
    };
    EventQueue.invokeLater(runner);
  }
}

한 창에서 MyFrame 프로그램을 실행하고, 다른 창에서 Threads 프로그램을 실행할 준비를 한다. 시스템이 원격 VM과 연결할 수 있도록 동일한 런타임 위치를 공유해야 한다.

일반적인 시작 명령으로 MyFrame을 시작한다.
> java MyFrame

그런 다음 실행 중인 애플리케이션의 프로세스 ID를 찾아야 한다. 바로 여기서 jps 명령이 진가를 발휘한다. 사용 중인 Java 런타임 디렉토리에 대해 시작된 모든 VM의 프로세스 ID를 나열하는 것이다. 실제로는 아래와 다른 프로세스 ID가 출력될 것이다.

> jps
5156 Jps
4276 MyFrame

jps 명령 자체가 Java 프로그램이므로 그 역시 목록에 나타난다. 여기서 4276이 Threads 프로그램으로 전달되어야 한다. 실제 ID는 이와 다를 것이다. 그런 다음 Threads를 실행하면 실행 중인 스레드의 목록을 덤프한다.

> java -cp %JAVA_HOME%/lib/tools.jar;. Threads 4276

JMX server connection timeout 18 / TIMED_WAITING
RMI Scheduler(0) / TIMED_WAITING
RMI TCP Connection(1)-192.168.0.101 / RUNNABLE
RMI TCP Accept-0 / RUNNABLE
DestroyJavaVM / RUNNABLE
AWT-EventQueue-0 / WAITING
AWT-Windows / RUNNABLE
AWT-Shutdown / WAITING
Java2D Disposer / WAITING
Attach Listener / RUNNABLE
Signal Dispatcher / RUNNABLE
Finalizer / WAITING
Reference Handler / WAITING


JMX 관리 에이전트를 사용하면 스레드를 나열하는 것 이상의 훨씬 많은 일을 할 수 있다. 예를 들어, ThreadMXBeanfindDeadlockedThreads() 메소드를 호출하여 교착 상태의 스레드를 찾을 수 있다.

직접 에이전트를 만드는 것은 더 간단한 편이다. 애플리케이션에서 main() 메소드를 필요로 하는 것과 비슷하게 에이전트는 agentMain() 메소드를 갖는다. 이는 어떤 인터페이스에도 속하지 않는다. 시스템은 그저 정확한 인수가 매개변수로 설정된 것을 찾아야 함을 알 뿐이다.

import java.lang.instrument.*

public class SecretAgent {
   public static void agentmain(String agentArgs,
       Instrumentation instrumentation) {
     // ...
   }
}

에이전트를 사용하려면 컴파일된 클래스 파일을 JAR 파일로 패키지화하고 매니페스트에서 Agent-Class를 지정해야 한다.

Agent-Class: SecretAgent

그러면 프로그램의 main() 메소드가 원래 Threads 프로그램보다 약간 더 짧아지는데, 원격 JMS 커넥터와 연결할 필요가 없기 때문이다. 여기서는 새로 패키지화된 에이전트를 사용하도록 JAR 파일 참조를 변경하면 된다. 그런 다음 SecretAgent 프로그램을 실행하면 시작하는 즉시, 즉 애플리케이션의 main() 메소드가 호출되기도 전에 agentmain() 메소드가 실행된다. Applet처럼, 어떤 인터페이스에도 속하지 않은 작업을 수행하도록 멋진 이름이 지정된 또 다른 메소드들이 존재한다.

Threads 프로그램을 사용하여 또 다른 VM을 모니터링하고, 일부 스레드를 교착 상태로 보낸 다음 이 차단된 VM과 계속 통신할 수 있는 방법을 표시해 본다.

Attach API를 위한 API 문서를 참조한다. 또한 JVM TI(Java Virtual Machine Tool Interface)에 대한 자료도 읽어볼 수 있다. 거기서는 Attach API를 사용해야 한다.

[출처] Attach API|작성자 날씬한곰

.
:
Posted by .07274.