Java Attach API的作用是什么?

Java Attach API是JDK 6中新增的一项功能,它提供了一种机制,允许运行在JVM中的Java进程与Agent程序进行动态依附。Attach API可以让应用程序在运行时动态连接到正在运行的JVM,并访问它的状态、执行代码和甚至修改它的状态或执行代码。它提供了一种标准的方式,使得开发者能够审查和修改某个正在运行的Java进程,而不必暴力地中断应用程序。

使用Attach API的过程,需要有两个部分共同配合:

  1. JVM的部分:JVM需要开放Attach机制,使得外部的代码可以通过Attach接口连接到这个VM中。在打开这个机制时,需要在启动JVM时设置一个参数:-Dcom.sun.management.jmxremote

  2. Agent的部分:Agent则是一个Java代码,它被Attach到了某个JVM中,它可以通过使用Java Instrumentation技术,完全不停机地对JVM中的class字节码进行修改和替换,从而实现为应用程序注入一些新的行为。例如,可以监控和成批操作线程,甚至可以实现代码注入(通过修改已存在的代码)、代码替换或者是执行热插拔。

下面我们来看一下如何使用Attach API进行远程连接,并获取远程JVM中的运行状态。我们将分别从服务端和客户端两个角度进行讲解:

  • 服务端环境

  • 在JVM启动时添加如下参数,开启JMX远程连接:

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=8899 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
"
  1. 启动一个简单的Java应用程序,暴露出运行时MBean以供远程连接查看:
public class RemoteServer {

    public static void main(String[] args) {
        WatchService watchService = null;
        try {
            // create a WatchService instance
            watchService = FileSystems.getDefault().newWatchService();

            // create a Path object for current directory
            Path path = Paths.get(".");

            // register a watch service for the PATH object obtained above
            path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
              StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

            // start an infinite loop
            while (true) {
                System.out.println("Current time:" + new Date() + ". Waiting for changes...");

                // wait for a file system event to occur
                WatchKey watchKey = watchService.take();

                // fetch all the events from the watch key
                List<WatchEvent<?>> events = watchKey.pollEvents();

                // iterate over each of the events and process them
                for (WatchEvent event : events) {
                    if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                        System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context() + ".");
                    }
                    if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                        System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context() + ".");
                    }
                    if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                        System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context() + ".");
                    }
                }

                // reset the watch key and loop again
                boolean valid = watchKey.reset();
                if (!valid) {
                    break;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (watchService != null) {
                try {
                    watchService.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 客户端环境

  • 编写一个简单的Java应用程序,在使用LocalAttachProvider连接到远程JMX端口并获取其中的JMXServerConnection:

public class RemoteJmx {
    private static final VirtualMachineDescriptor findProcess(String name) {
        List<VirtualMachineDescriptor> vms = VirtualMachine.list();
        for (VirtualMachineDescriptor desc : vms) {
            if (desc.displayName().endsWith(name)) {
                return desc;
            }
        }
        return null;
    }
    public static void main(String[] args) throws Exception {
        String processName = "RemoteServer";
        VirtualMachineDescriptor desc = findProcess(processName);
        VirtualMachine vm = VirtualMachine.attach(desc);

        String connectorAddr = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
        if (connectorAddr == null) {
            String agent = vm.getSystemProperties().getProperty("java.home") +
            File.separator + "lib" + File.separator + "management-agent.jar";
            vm.loadAgent(agent);
        connectorAddr = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
    }

    JMXServiceURL url = new JMXServiceURL(connectorAddr);
    try (JMXConnector connector = JMXConnectorFactory.connect(url)) {
        MBeanServerConnection mbeanServer = connector.getMBeanServerConnection();
        Set<ObjectName> mbeans = mbeanServer.queryNames(null, null);
        for (ObjectName objectName : mbeans) {
            System.out.println(objectName);
        }
    }
}

通过此方式,我们可以在客户端上连接到服务端的JMX端口,并获取其中的运行状态对象以便我们进行诊断调试。由于这种方式可以在应用程序运作时提供持续的诊断调试服务,因此在开发和生产环境中都有许多用处。

  • 示例2:使用Attach API注入字节码并执行代码

在现代的分布式系统中,我们常常要使用协调器来维持不同的服务之间的一致性。你可能听说过Zookeeper或Etcd这样的产品。然而,有时候我们想要简化体系结构,做一些自己的东西。要实现分布式锁,我们可以尝试使用Java的attach API注入字节码,并且指定bytecode作为一个java运行时的agent。检视以下示例:

  • 服务端环境

在服务端环境中执行下面的Java代码,以监听/distributed_lock路径的节点变化:

public class LockServer {
    public static ZooKeeper zk = null;
    public static final String ROOT_LOCK_PATH = "/distributed_lock";
    public static final String SERVER_ID = "1";
    public static final String ADDRESS = "localhost:7878";
    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.err.println("USAGE: LockServer <zkQuorum> <thisServerAddr>");
            System.exit(1);
        }
        String zkQuorum = args[0];
        String thisServerAddr = args[1];
        zk = new ZooKeeper(zkQuorum, 6000, null);
        try {
            zk.create(ROOT_LOCK_PATH, SERVER_ID.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } catch (KeeperException | InterruptedException e) {
            System.err.println(e.getMessage());
        }
        new LockEngine(zk, thisServerAddr).run();
    }
}

在本例中,我们首先连接到本地的ZooKeeper节点并检查是否已在节点上设置一个路径锁。如果没有,则我们创建一个分布式锁路径(在本例中是/distributed_lock)。稍后,我们将使用LockEngine来运行我们的分布式锁服务。

请注意,LockEngine是异步事件驱动的,可以快速响应任务尝试获取和释放锁的请求。这里我们使用事件驱动技术可以保持代码的整洁性和可维护性。

  • 客户端环境

接下来我们要使用客户端Attach的API来注入LockEngine。LockEngine以自己作为参数,并被注入到正在运行的Java虚拟机进程的字节码中。以下是将LockEngine注入到LockServer JVM进程中的Java代码:

public class LockClient {
    private static void attachAgent(String jarFilePath, String targetJvmPid) throws Exception {
        VirtualMachine vm = VirtualMachine.attach(targetJvmPid);
        vm.loadAgent(jarFilePath);
        vm.detach();
    }
    public static void main(String[] args) throws Exception {
        String jarFilePath = "/path/to/lock-engine.jar";
        String processName = "LockServer";
        String targetJvmPid = findProcess(processName);
        attachAgent(jarFilePath, targetJvmPid);
    }
}

LockClient程序可以从目标JVM中远程注入LockEngine.jar,然后我们就可以从Zookeeper中获取分布式锁来等待锁的释放。例如,以下是在LockEngine中实现try-to-lock和unlock操作的示例Java代码:

public class LockEngine {
    private ZooKeeper zkClient;
    private final String serverId;
    private String lockBaseNode = LockServer.ROOT_LOCK_PATH;
    private String myLockPath;
    private CountDownLatch lockAvailableSignal = new CountDownLatch(1);
    private String serverAddress = LockServer.ADDRESS;
    public LockEngine(ZooKeeper zk, String serverAddr) {
        zkClient = zk;
        serverId = LockServer.SERVER_ID;
        serverAddress = serverAddr;
    }
    public boolean tryToLock() throws KeeperException, InterruptedException {
        if (myLockPath != null) {
            return true;
        }

        myLockPath = zkClient.create(lockBaseNode + "/" + serverId + "-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        List<String> sortedNode = zkClient.getChildren(lockBaseNode, false);
        SortedSet<String> set = new TreeSet<String>();
        for (String node : sortedNode) {
            set.add(lockBaseNode + "/" + node);
        }
        String first = set.first();
        SortedSet<String> lessThanMe = ((TreeSet<String>) set).headSet(myLockPath);
        if (myLockPath.equals(first)) {
            return true;
        }
        if (!lessThanMe.isEmpty()) {
            String last = lessThanMe.last();
            zkClient.exists(last, new LockWatcher(lockAvailableSignal, zkClient, myLockPath));
            lockAvailableSignal.await();
        }
        return true;
    }
    public void unlock() throws Exception {
        zkClient.delete(this.myLockPath, -1);
        myLockPath = null;
        lockAvailableSignal = new CountDownLatch(1);
    }
}

这个锁定模块是一个图片完整的Java类,你可以选择提取和使用其中的代码,或在你的项目中直接使用Jar。如要使用该类,你需要在程序中注入它,如上所述。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java Attach API的作用是什么? - Python技术站

(0)
上一篇 2023年5月11日
下一篇 2023年5月11日

相关文章

  • 别在Java代码里乱打日志了,这才是正确的打日志姿势

    这里是关于正确打印日志的攻略: 1. 什么是好的日志? 一个好的日志应该包含以下几个方面: 表现良好 日志输出应有较好的可读性; 日志输出要具有一定层次性; 日志输出要有一定格式化,比如在同一业务中输出的日志应当保持一致性。 提供足够的信息 日志应当详细描述应用发生了什么,以及发生原因; 一些需要排查的问题,还可以在日志中给出一些额外的信息或态度。 不只是输…

    Java 2023年5月25日
    00
  • 关于JAVA 数组的使用介绍

    关于Java数组的使用介绍 Java中的数组是一种非常常见的数据结构,可以容纳同一种数据类型(可以是基本类型或对象类型)的固定数量的元素。本文将介绍Java数组的基本用法,包括声明、初始化、访问以及一些常见的操作和示例。 数组的声明和初始化 Java声明一个数组需要指定数组名称、数组元素的类型和数组的大小,数组元素的类型可以是Java中的任意数据类型(例如,…

    Java 2023年5月26日
    00
  • Mybatis Interceptor线程安全引发的bug问题

    首先我们来了解一下什么是 Mybatis Interceptor。 Mybatis Interceptor 是 Mybatis 框架提供的一个扩展机制,允许我们在 Mybatis 核心逻辑运行前或运行后进行拦截,来实现对 SQL 语句、参数、结果集等进行定制化处理。 而“线程安全引发的 bug”问题是在使用 Mybatis Interceptor 进行并发处…

    Java 2023年5月27日
    00
  • 使用json字符串插入节点或者覆盖节点

    使用json字符串插入节点或者覆盖节点的过程可以分为以下几个步骤: 将json字符串解析为json对象 根据需要插入或覆盖的节点,生成新的json节点 将新的json节点插入或覆盖到目标json对象中 将最终结果转换为json字符串 下面通过两个示例说明具体的操作过程。 示例1:插入节点 假设原始的json字符串为: { "name": …

    Java 2023年5月26日
    00
  • freemarker jsp java内存方式实现分页示例

    首先需明确,Freemarker是一种模板引擎,可用于生成HTML网页、电子邮件、配置文件等等。本文将阐述如何使用Freemarker结合Java和JSP技术进行分页实现。 进入正题,具体实现步骤如下: 首先需要导入Freemarker的jar包到项目中,并在程序中初始化Freemarker配置,代码示例如下: javaConfiguration cfg =…

    Java 2023年6月15日
    00
  • 深入了解JAVA数据类型与运算符

    深入了解JAVA数据类型与运算符 JAVA数据类型 JAVA中的数据类型分为两类,基本数据类型和引用数据类型。 基本数据类型 JAVA的基本数据类型包括以下8种: byte:1字节,范围-128~127 short:2字节,范围-32768~32767 int:4字节,范围-2147483648~2147483647 long:8字节,范围-92233720…

    Java 2023年5月26日
    00
  • 基于javax.validation结合spring的最佳实践

    基于javax.validation结合Spring的最佳实践,主要是利用Spring框架提供的Validator和DataBinder接口以及javax.validation提供的注解和API对请求参数和数据模型进行合法性校验,来保证应用程序的数据输入和输出的正确性。 下面是基于Spring Boot的完整攻略: 1. 引入依赖 在pom.xml文件中引入…

    Java 2023年5月19日
    00
  • Struts2.5版本struts.xml与web.xml配置的更改方法

    Struts2.5是一个非常流行的Java web框架,其中struts.xml与web.xml是两个重要的配置文件。如果你需要修改它们,以下是详细的操作步骤: 编辑struts.xml Struts2.5默认使用的是struts.xml配置文件,你可以根据自己的需要修改它。下面是修改struts.xml的步骤: 打开struts.xml文件 “` “`…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部