【commons-pool2原始碼】_pre JMX

cz寫真發表於2021-02-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<?>![](https://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

感謝閱讀!

相關文章