【commons-pool2源碼】_pre JMX

  • 2021 年 2 月 21 日
  • 筆記

目錄

  • 一、定義
  • 二、demo
  • 三、JMX在commons-pool2中的應用

一、定義

JMX(Java Management Extensions)

簡單來說JMX技術提供了一套輕量的標準化的資源管理方式. 什麼是資源的管理? 就是資源的增刪改查!

使用JMX技術, 可以管理和監控Java虛擬機的資源(內存, CPU, 線程等). 也可以將符合規範的 Managed Bean(MBean) 對象註冊到MBean服務器, MBean服務器作為JMX代理, 本地或者遠程控制MBean.

JDK自帶的jconsole工具就可以監控管理java應用中的MBean. jconsole工具位於%JAVA_HOME%\bin\目錄下.

如果需要更準確更詳細的了解JMX技術, 強烈建議閱讀官方文檔. 英文看起來吃力可以使用chrome瀏覽器, 右鍵翻譯成中文.

二、demo

demo使用JMX實現在不重啟應用的情況下, 動態修改對象的屬性值. 該方法可以用於動態修改java應用中的配置.

demo目錄
demo目錄

將MBean對象註冊到MBeanServer中需要滿足兩個條件之一: 實現DynamicMBean接口 或者 自定義符合標準MBean規範的接口. demo中我們採用自定義MBean接口.

  1. 定義MBean接口AppConfigMBean. getB()表示B屬性可讀, setB()表示B屬性可寫. 這裡暫時只測試一些基本類型.
package com.cztruth.jmx;
/**
 * MBean接口規範, 詳見 `com.sun.jmx.mbeanserver.Introspector.checkCompliance(Class<?>![](//img2020.cnblogs.com/blog/2300815/202102/2300815-20210221223618559-1361345336.png)
 mbeanClass)`
 * e.g.  接口是`com.cztruth.jmx.AppConfigMBean`, 則實現類則是`com.cztruth.jmx.AppConfig`
 */
public interface AppConfigMBean {
    /** A可讀 */
    int getA();

    /** B可讀 */
    String getB();

    /** B可寫 */
    void setB(String newB);

    /** C可讀 */
    int[] getC();

    /** C可寫 */
    void setC(int[] newC);
}

  1. 實現MBean接口AppConfig.
package com.cztruth.jmx;


import java.util.Arrays;

/**
 *
 * 將自己實現的MBean註冊到MBeanServer中需要滿足以下兩個條件之一:
 * jdk中描述是`implement DynamicMBean, or follows the Standard MBean conventions`
 * 1. 實現DynamicMBean接口.
 * 2. 自定義MBean接口,接口必須符合標準的MBean規範. 詳見`com.sun.jmx.mbeanserver.Introspector.checkCompliance(Class<?> mbeanClass)`
 * e.g.`AppConfig`是`AppConfigMBean`的接口實現
 */
public class AppConfig implements AppConfigMBean {

    public int a;

    public String b;

    public int[] c;

    public AppConfig(int a, String b, int[] c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public int getA() {
        return this.a;
    }

    @Override
    public String getB() {
        return this.b;
    }

    @Override
    public void setB(String newB) {
        this.b = newB;
    }

    @Override
    public int[] getC() {
        return this.c;
    }

    @Override
    public void setC(int[] newC) {
        this.c = newC;
    }

    @Override
    public String toString() {
        return "AppConfig{" +
                "a=" + a +
                ", b='" + b + '\'' +
                ", c=" + Arrays.toString(c) +
                '}';
    }
}
  1. Main.java中將一個AppConfig對象註冊到MBeanServer. 並用while讓程序一直運行, 這樣做使應用可以被遠程連接和管理. 這裡要注意ObjectName的name需要符合ObjectName的命名規範.
package com.cztruth.jmx;

import javax.management.*;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {

    /**
     * 如果需要明確規定遠程鏈接的端口, 需要在啟動時加入以下jvm參數.
     * e.g. java -jar -Dcom.sun.management.jmxremote.port=3333 -D... -D... jmx.jar.
     * 如果使用的IDE工具啟動項目需要去 Edit Configurations -> Configuration標籤 -> VM options -> 添加 -D...
     * -Dcom.sun.management.jmxremote.port=3333
     * -Dcom.sun.management.jmxremote.ssl=false
     * -Dcom.sun.management.jmxremote.authenticate=false
     */
    public static void main(String[] args) {

        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = null;
        AppConfig appConfig = new AppConfig(1, "sout", new int[]{45,33});
        try {
            // ObjectName的名字不能亂取, 需要遵守ObjectName的規範。 詳見: `javax.management.ObjectName`注釋
            objectName = new ObjectName("com.cztruth.jmx.AppConfig:type=test,name=name1");
            mBeanServer.registerMBean(appConfig, objectName);
            /** 卡住線程, 並且每10s輸出一次 */
            while (true) {
                Thread.sleep(10000L);
                System.out.print(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\t");
                System.out.println(appConfig);
            }
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        } catch (NotCompliantMBeanException e) {
            e.printStackTrace();
        } catch (InstanceAlreadyExistsException e) {
            e.printStackTrace();
        } catch (MBeanRegistrationException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 將AppConfig對象從MBeanServer中註銷
            if (objectName != null) {
                try {
                    mBeanServer.unregisterMBean(objectName);
                } catch (InstanceNotFoundException e) {
                    e.printStackTrace();
                } catch (MBeanRegistrationException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  1. 使用jconsole監控管理MBean.

運行demo(Main.java 下的 main())

找到並運行jconsole.exe. jconsole工具位於%JAVA_HOME%\bin\目錄下. 博主的jconsole位於C:\Program Files\Java\jdk1.8.0_271\bin\jconsole.exe.

選擇本地進程中的com.cztruth.jmx.Main進程.

jconsole選擇連接的進程

MBean的標籤下我們可以看到對應ObjectName(com.cztruth.jmx.AppConfig:type=test,name=name1). A, B, C三個屬性值都是可讀的.

jconsole管理MBean

通過jconsole, 修改該B的值為改好了, 並且控制台也正確輸出了MBean對象的當前值已經被改動.

修改B

但是C屬性卻無法通過jconsole修改. 難道JMX無法管理數組? 理論上來說AppConfig對象只是持有的C數組的引用, String類型的B能被替換, C應該也是可以的. 而且 <<深入理解Java虛擬機>> 中提到過, 大部分bin目錄下的工具都是jdk/lib/tools.jar的一層薄封裝而已. 所以這裡猜測jconsole只不過是沒有將修改MBean數組屬性的方式可視化操作. 之後查閱官方文檔, 其中提到The JMX technology defines standard connectors (known as JMX connectors) that enable you to access JMX agents from remote management applications., 所以我們可以通過JMX connectors去遠程控制MBean.

  1. UpdateAppConfig.java中就實現了自定義遠程監控管理MBean.

運行demo(Main.java 下的 main()), 並且在啟動時加入VM options, 設置JMX遠程佔用的端口號.

如果運行的是jar包啟動, 則在控制台輸入java -jar -Dcom.sun.management.jmxremote.port=3333 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false jmx.jar啟動.
如果是在IDEA中則在啟動設置的VM options參數中加上三個-D....

添加VM options第一步

添加VM options第二步

new一個JMXServiceURL對象, 因為demo運行時 jmxremote port 設置的是3333, 所以url = service:jmx:rmi:///jndi/rmi://localhost:3333/jmxrmi. 然後通過Attribute對象設置一個新的C.

package com.cztruth.jmx;

import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Set;

public class UpdateAppConfig {

    public static void main(String[] args) {
        try {
            JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:3333/jmxrmi");
            JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL);
            MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
            // 查找所有的MBeans, 遍歷就能獲取所有的objectName
            Set<ObjectInstance> set = mBeanServerConnection.queryMBeans(null,null);
            for (ObjectInstance objectInstance : set) {
                System.out.println(objectInstance.getObjectName().toString());
            }
            // 這裡還是強制獲取用作demo的 objectName
            ObjectName objectName = new ObjectName("com.cztruth.jmx.AppConfig:type=test,name=name1");
            // 修改 屬性 C
            Attribute cAttribute = new Attribute("C", new int[]{2,3,4,6});
            mBeanServerConnection.setAttribute(objectName, cAttribute);
            System.out.println();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ReflectionException e) {
            e.printStackTrace();
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        } catch (AttributeNotFoundException e) {
            e.printStackTrace();
        } catch (InvalidAttributeValueException e) {
            e.printStackTrace();
        } catch (MBeanException e) {
            e.printStackTrace();
        }

    }
}

成功修改了C.

遠程鏈接JMX並修改數組成功

三、JMX在commons-pool2中的應用

在commons-pools中使用到了MXBean. 科普下 MXBean與MBean的區別

看完org.apache.commons.pool2.impl.GenericObjectPoolMXBean接口, 以及它的實現org.apache.commons.pool2.impl.GenericObjectPool, 推斷出
它的作用就是監控pool的配置和pool中的對象. 接口中的Set<DefaultPooledObjectInfo> listAllObjects()方法就是為了監控pool中的對象.

我們使用一個測試用例, 將代碼運行起來, 並往pool中添加三個對象, 借走其中一個. 然後使用jconsole工具監控.

package org.apache.commons.pool2.impl;

import org.junit.Test;

/**
 * 當前測試需要在commons-pools的測試用力中的`rg.apache.commons.pool2.impl.TestGenericObjectPool`類完成.
 * 當然也可以自己實現一個簡單的PooledObjectFactory類.
 */
public class MyTest {

    private TestGenericObjectPool.SimpleFactory simpleFactory = null;
    protected GenericObjectPool<String> genericObjectPool = null;

    @Test(timeout = 600000)
    public void testJmxRegistration() {
        try {
            simpleFactory = new TestGenericObjectPool.SimpleFactory();
            genericObjectPool = new GenericObjectPool<>(simpleFactory);
            // 添加三個對象
            genericObjectPool.addObject(); // "0"
            genericObjectPool.addObject(); // "1"
            genericObjectPool.addObject(); // "2"
            // 10ms後借走一個對象
            Thread.sleep(10L);
            genericObjectPool.borrowObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 不要讓線程結束
        while (true) {
            try {
                Thread.sleep(10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

jconsole中簡單的GenericObjectPool屬性展示

通過listAllObjects操作, 我們可以監控pool中的對象.

pool中的對象1

可以看到池子中有三個對象, 並且一個對象被借走一次.

pool中的對象2

感謝閱讀!