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后端把数据转换为树,map递归生成json树,返回给前端(后台转换)

    首先,需要明确一下这个过程的流程和目的:将后端获得的数据转换为树形结构,再通过递归生成 JSON 树,并返回给前端。下面我们将详细讲解这个过程。 1. 将数据转换为树形结构 首先,需要将后端的数据进行转换,变成树形结构。可以使用递归来完成这个过程。 具体实现方式如下:首先,定义一个树节点的类 Node,包含节点名称、节点编号、父节点编号、节点类型等属性。然后…

    Java 2023年5月26日
    00
  • 34基于Java的学生选课系统或学生课程管理系统

    本系统是基于Java的学生选课信息管理系统,可以有效的对学生选课信息、学生个人信息、教师个人信息等等进行管理。 摘要:基于java的学生课程管理系统,基于java的学生选课系统,javaWeb的学生选课系统,学生成绩管理系统,课表管理系统,学院管理系统,大学生选课系统设计与实现,网上选课系统,课程成绩打分。 项目概述 信息系统作为现代企事业单位实现信息化的一…

    Java 2023年5月11日
    00
  • 利用Maven实现将代码打包成第三方公共jar包

    让我详细讲解一下利用Maven实现将代码打包成第三方公共jar包的完整攻略。 第一步:创建一个Maven项目 首先,我们需要创建一个Maven项目作为代码库。我们可以使用IDE工具,如Intellij IDEA、Eclipse等,在创建项目时选择Maven项目的模板(Maven Quickstart Archetype)。 第二步:编写代码 接下来,我们需要…

    Java 2023年5月20日
    00
  • 详解Spring中BeanUtils工具类的使用

    详解Spring中BeanUtils工具类的使用 什么是BeanUtils BeanUtils是Apachecommons的一个工具类库。它提供了一些方法来方便地实现JavaBean的属性复制、类型转换等操作。在Spring中,BeanUtils也被广泛应用在属性复制、对象转换等操作中。 BeanUtils的优点 BeanUtils具有以下几个优点: 简单易…

    Java 2023年5月19日
    00
  • Adobe Acrobat DC怎么使用?Adobe Acrobat DC下载安装图文教程

    如果想要使用 Adobe Acrobat DC 进行 PDF 文件的编辑和管理,可以按照以下步骤进行下载、安装和使用: 下载安装 Adobe Acrobat DC 打开 Adobe 官网(https://www.adobe.com/),选择“Acrobat”选项,并点击“开始免费试用”或“购买”按钮。 如果选择免费试用,则需要输入个人信息和支付信息,之后会获…

    Java 2023年6月15日
    00
  • Java Flink与kafka实现实时告警功能过程

    下面是详细的攻略: Java Flink与Kafka实现实时告警功能过程 概述 本文主要介绍如何使用Java Flink和Kafka构建实时告警功能,包括数据流的传送和处理、过滤及统计处理等内容。 准备工作 在实现过程中,需要准备以下工具和环境: Java Flink Apache Kafka IDE开发工具,如IntelliJ IDEA等 实现过程 1. …

    Java 2023年6月2日
    00
  • jsp内置对象及方法详细介绍

    下面我就来详细讲解一下”JSP内置对象及方法详细介绍”。 JSP内置对象 JSP内置对象是JSP容器在JSP页面执行期间自动创建的一些对象,可以用于在JSP页面中实现不同的功能。JSP内置对象一共有9个:request、response、pageContext、session、application、out、config、exception、page 。 在…

    Java 2023年6月15日
    00
  • 解决Hibernate4执行save()或update()无效问题的方法

    下面是详细讲解“解决Hibernate4执行save()或update()无效问题的方法”的完整攻略。 问题描述 在使用Hibernate4的过程中,有时会出现执行save()或update()方法无效的问题。这个问题一般是由于Hibernate在执行持久化操作时,需要在事务中进行,但是开发者没有正确配置事务所导致的。下面给出解决这个问题的方法。 解决方法 …

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