JMX:Java Management Extensions技术是Java SE平台的标准功能,提供了一种简单的、标准的监控和管理资源的方式。主要用于监控和管理Java应用程序运行状态、设备和资源信息、Java虚拟机运行情况等信息。JMX可以是动态的,所以可以在资源创建、安装、实现时进行动态监控和管理,JDK自带的jconsole就是使用JMX技术实现的监控工具。
使用JMX技术时,需要定义一个被称为MBean或MXBean的Java对象来表示要管理指定的资源,然后可以把资源信息注册到MBean Server对外提供服务。MBean Server充当了对外提供服务和内存管理MBean资源的代理功能。
JMX不仅可以用于本地管理,JMX Remote API还为JMX添加了远程功能,使之可以通过网络远程监视和管理应用程序。
JNDI:JavaNaming And Directory Interface,Java命名和目录结构
命名服务,如:域名映射到ip地址的服务
目录服务,如:电话簿,如果我们要找某个人的电话号码,我们需要从电话簿里找到这个人的名称,然后再看其电话号码
JMX技术架构主要有资源管理(MBean/MXBean)模块、资源代理模块(MBean Server)、远程管理模块(Remote API)组成。
资源管理在架构中标识为资源探测层(Probe Level),在JMX中,使用MBean或 MXBean来表示一个资源(下面简称MBean),访问和管理资源也都是通过MBean,所以MBean往往包含着资源的属性和操作方法。
JMX已经对JVM进行了多维度的资源检测,所以可以轻松启动JMX代理来访问内置的JVM资源检测,从而通过JMX技术远程监控和管理JVM。
以下是JMX对JVM的资源检测类,都可以直接使用。
package com.example.test.bean;
import java.lang.management.*;
import java.util.List;
import java.util.stream.Collectors;
public class JavaManagementExtensions {
public static void main(String[] args) {
//操作系统信息
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
String osName = operatingSystemMXBean.getName();
String osVersion = operatingSystemMXBean.getVersion();
int processors = operatingSystemMXBean.getAvailableProcessors();
System.out.println(String.format("操作系统:%s,版本:%s,处理器:%d个", osName, osVersion, processors));
//编译系统信息
CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean();
String compilationMXBeanName = compilationMXBean.getName();
System.out.println("编译系统:" + compilationMXBeanName);
//内存信息
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
long max = heapMemoryUsage.getMax();
long used = heapMemoryUsage.getUsed();
System.out.println(String.format("使用内存:%dMB/%dMB", used / 1024 / 1024, max / 1024 / 1024));
//垃圾收集器信息
List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
String gcNames = gcMXBeans.stream()
.map(MemoryManagerMXBean::getName)
.collect(Collectors.joining(","));
System.out.println("垃圾收集器:" + gcNames);
}
}
操作系统:Windows 10,版本:10.0,处理器:8个
编译系统:HotSpot 64-Bit Tiered Compilers
使用内存:6MB/3604MB
垃圾收集器:PS Scavenge,PS MarkSweep
资源代理MBean Server是MBean资源的代理,通过MBean Server可以让MBean资源用于远程管理,MBean资源和MBean Server通常是在同一个JVM中,但不是必须的。
想要MBean Server管理MBean资源,首先要把资源注册到MBean Server,任何符合JMX的MBean资源都可以进行注册,最后MBean Server会暴露一个远程通信接口对外提供服务。
合规的MBean -> 注册到MBServer -> MBServer会暴露接口提供服务
可以通过网络协议访问JMX API,如HTTP协议,SNMP(网络管理协议)、RMI远程方法调用协议等,JMX技术默认实现了RMI远程调用协议。
因为资源管理MBean的充分解耦,所以我们可以轻松的把资源管理功能扩展到其他协议,如:通过HTTP在网页端进行管理。
演示自定义资源MBean,模拟一个内存资源MBean,最后对它进行进程管理
MBean的编写必须遵守JMX的设计规范,MBean类似于一个特殊的Java Bean,它需要一个接口和一个实现类。MBean资源接口总是以MBean或者MXBean结尾,实现类则是要以接口去掉MBean或MXBean之后的名字来命名。
命名规范:
例如:
MBean资源接口:MyMemoryMBean/MyMemoryMXBean
实现类:MyMemory
自定义编写MyMemoryMXBean:
package com.example.test.bean;
//定义MBean资源接口[如果资源接口中含有自定义实体类的引用,则需要以MXBean结尾,MyMemoryMXBean]
public interface MyMemoryMBean {
long getTotal();
void setTotal(long total);
long getUsed();
void setUsed(long used);
String doMemoryInfo();
}
package com.example.test.bean;
public class MyMemory implements MyMemoryMBean{
private long total;
private long used;
@Override
public long getTotal() {
return total;
}
@Override
public void setTotal(long total) {
this.total = total;
}
@Override
public long getUsed() {
return used;
}
@Override
public void setUsed(long used) {
this.used = used;
}
@Override
public String doMemoryInfo() {
return String.format("使用内存:%dMB/%dMB", used, total);
}
}
注意:
①这个例子在MyMemory.java中只有两个long基本类型属性,所以接口是以MBean结尾。如果资源实现类中的属性是自定义实体类的引用,那么接口就需要以MXBean结尾。
②MyMemory与MyMemoryMBea需要在同一个包下,否则的话,需要额外实现其他接口
这样就完成了线程数量资源MBean的创建,其中total和used是资源属性,doMemoryInfo是资源操作方法。
通过上面的JMX架构图,我们可以知道MBean资源需要注册到MBean Server进行代理才可以暴露给外部进行调用。所以我们想要通过远程管理我们自定义的MyMemory资源,需要先进行资源代理。
package com.example.test.demo;
import com.example.test.bean.MyMemory;
import javax.management.*;
import java.lang.management.ManagementFactory;
public class MyMemoryManagement {
public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {
//获取MBean Server
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
//创建【实现类】服务
MyMemory myMemory = new MyMemory();
myMemory.setTotal(100L);
myMemory.setUsed(20L);
//注册服务到MBean Server
//com.example.test.bean:type=myMemory [包名:type=服务名]
ObjectName objectName = new ObjectName("com.example.test.bean:type=myMemory");
platformMBeanServer.registerMBean(myMemory, objectName);
while(true){
Thread.sleep(3000);
System.out.println(myMemory.doMemoryInfo());
}
}
}
结果:
使用内存:20MB/100MB
使用内存:20MB/100MB
jconsole是Java自带的基于JMX技术的监控管理工具,如果已经配置了JDK环境变量,可以直接在cmd窗口输入jconsole命令启动。
启动jconsole后会列出当前机器上的Java进程,我们选择自己想要监控的Java进程监控即可,连接后会提示不安全的协议,因为Java程序默认启动是不会配置HTTPS协议。
连接成功之后就可以看到多维度的JVM监控信息,这些信息都是通过读取JVM资源MBean信息得到的。
传统的应用程序使用JMX,只需要两步:
1.编写MBean,提供管理接口和监控数据
2.注册MBean
在Spring应用程序中,使用JMX只需要一步:
编写MBean,提供管理接口和监控数据【第二步的注册由Spring自动完成,加上@EnableMBeanExport,告诉Spring自动注册MBean】
package com.example.test.demo;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
@Component
@ManagedResource(objectName = "com.example.test.demo:name=blacklist", description = "Blacklist of IP address")
public class BlacklistMBean {
private Set<String> ips = new HashSet<>();
@ManagedAttribute(description = "Get IP address in blacklist")
public String[] getBlacklist(){
int size = ips.size();
String[] ans = new String[size];
int i = 0;
for(String str : ips){
ans[i++] = str;
}
return ans;
}
@ManagedOperation
@ManagedOperationParameter(name="ip", description = "Target IP address that will be added to blacklist")
public void addBlacklist(String ip){
ips.add(ip);
}
@ManagedOperation
@ManagedOperationParameter(name="ip", description = "Target IP address that will be removed from blacklist")
public void removeBlacklist(String ip){
ips.remove(ip);
}
public boolean shouldBlock(String ip){
return ips.contains(ip);
}
}
package com.example.test.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
@Order(1)
@Component
public class BlacklistInterceptor implements HandlerInterceptor {
final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
BlacklistMBean blacklistMBean;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ip = request.getRemoteAddr();
logger.info("check ip address {}...", ip);
if(blacklistMBean.shouldBlock(ip)){
logger.warn("will block ip {} for it is in blacklist...", ip);
response.sendError(403);
return false;
}
return true;
}
}
必须加上@Configuration注解,Spring才能统一管理当前拦截器的实例
addPathPatterns("/api/**")配置拦截路径,
其中/**表示当前目录以及所有子目录(递归),/*表示当前目录,不包括子目录
package com.example.test.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ComponentScan
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//注入自定义拦截器
@Autowired
private BlacklistInterceptor blacklistInterceptor;
//添加自定义拦截器【拦截所有请求】
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(blacklistInterceptor).addPathPatterns("/**");
}
}
SpringBoot启动类配置(加上@EnableMBeanExport):
@SpringBootApplication
@EnableMBeanExport // 自动注册MBean
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
controller编写:
package com.example.test.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JMXController {
@RequestMapping("/test")
public String testController(){
return "jmx test....";
}
}
启动cmd窗口,输入jconsole,选择本地进程【黑名单进程TestApplication】,并连接(默认是不安全连接,无视即可)
此时查看ip黑名单列表,发现没有任何数据
在浏览器上访问地址:
http://127.0.0.1:8080/test
出现结果:
jmx test....
服务器日志打印信息:
2022-09-02 11:45:03.522 INFO 15084 --- [nio-8080-exec-1] c.e.test.demo.BlacklistInterceptor : check ip address 127.0.0.1...
调用getBlacklist方法,发现127.0.0.1已经添加进去了:
这个时候再访问地址:
http://127.0.0.1:8080/test
发现结果:
服务器日志信息:
2022-09-02 13:45:30.817 INFO 15084 --- [nio-8080-exec-4] c.e.test.demo.BlacklistInterceptor : check ip address 127.0.0.1...
2022-09-02 13:45:30.817 WARN 15084 --- [nio-8080-exec-4] c.e.test.demo.BlacklistInterceptor : will block ip 127.0.0.1 for it is in blacklist...
可以发现,我们可以通过jconsole动态修改MBean的属性,动态调用其方法
使用jconsole直接通过Local Process 连接JVM有个限制,就是jconsole和正在运行的JVM必须在同一台机器。如果要开远程连接,首先需要打开JMX端口。同时,在启动项目时,需要添加以下JVM启动参数:
第一个参数表示在1999端口监听JMX连接
第二个和第三个表示无需验证,不使用SSL连接,在开发测试阶段比较方便,生产环境必须指定验证
方式并启用SSL(安全性)
许多JavaEE服务器,如JBoss的管理后台都是通过JMX提供管理接口,并由Web方式访问,对用户更
加友好
原文链接:
①https://www.liaoxuefeng.com/wiki/1252599548343744/1282385687609378
②https://www.wdbyte.com/java/jmx.html#_3-jmx-%E7%9A%84%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84