Java进程监控
最近项目需要对Java进程,堆栈信息,内存,cpu等资源的消耗进行监控,借鉴了git已有的轮子JPOM java项目管理系统和在线demo网站及对其源码的分析,提炼出了以下几种监控方式。
1.引言
有两种途径可以监控Java进程及对应JVM信息:
一.使用JDK自带rt.jar中 java.lang.management包下的类来管理。java.lang.management包提供了全面的监控和管理工具,包括JVM的监管API、监管API日志、jconsole和其他监控工具、Java管理扩展平台(JMX)等等。
类名 | 描述 |
---|---|
ClassLoadingMXBean | 用于 Java 虚拟机的类加载系统的管理接口。 |
CompilationMXBean | 用于 Java 虚拟机的编译系统的管理接口。 |
GarbageCollectorMXBean | 用于 Java 虚拟机的垃圾回收的管理接口。 |
MemoryManagerMXBean | 内存管理器的管理接口。 |
MemoryMXBean | Java 虚拟机的内存系统的管理接口。 |
MemoryPoolMXBean | 内存池的管理接口。 |
OperatingSystemMXBean | 用于操作系统的管理接口,Java 虚拟机在此操作系统上运行。 |
RuntimeMXBean | Java 虚拟机的运行时系统的管理接口。 |
ThreadMXBean | Java 虚拟机线程系统的管理接口。 |
二 . 使用系统和JVM提供的命令来获取。
如导出堆栈信息: jmap -heap 2576 > JVMHeap.log
进程线程信息:jstack 2576 >> JVMjstack.log
内存使用情况:
window: tasklist /V /FI "pid eq 5027"
linux : top -b -n 1 -p 5027
2. 程序启停, 为进程自定义项目名称
Linux: 启动命令
String command = String.format("nohup java %s %s -Dapplication=%s -Dbasedir=%s %s %s >> %s 2>&1 &",
projectInfoModel.getJvm(),
ProjectInfoModel.getClassPathLib(projectInfoModel),
projectInfoModel.getId(),
projectInfoModel.getAbsoluteLib(),
projectInfoModel.getMainClass(),
projectInfoModel.getArgs(),
projectInfoModel.getAbsoluteLog());
Linux停止
kill %s
Window启动
String command = String.format("javaw %s %s -Dapplication=%s -Dbasedir=%s %s %s >> %s &",
jvm, classPath, tag,
projectInfoModel.getAbsoluteLib(), mainClass, args, projectInfoModel.getAbsoluteLog());
Window停止
taskkill /F /PID %s
tips: -D=value
在虚拟机的系统属性中设置属性名/值对,运行在此虚拟机之上的应用程序可用System.getProperty(“propertyName”)得到value的值。如果value中有空格,则需要用双引号将该值括起来,如-Dname=”space string”。该参数通常用于设置系统级全局变量值,如配置文件路径,应为该属性在程序中任何地方都可访问。
启动命令中添加 -Dapplication=%s 参数来将Project Name加入进程的JVM信息中,后续访问可根据Project Name确定唯一的进程Pid。
Properties properties = virtualMachine.getAgentProperties();
String args = properties.getProperty("sun.jvm.args", "");
if (StrUtil.containsIgnoreCase(args, appTag)) {
return virtualMachine;
}
args = properties.getProperty("sun.java.command", "");
if (StrUtil.containsIgnoreCase(args, appTag)) {
return virtualMachine;
}
3. 操作系统判断
使用System.getProperties() 来获取操作系统配置参数: 如System.getProperty("os.name")获取当前操作系统。
Key | Description of Associated Value | 中文描述 |
---|---|---|
java.version | Java Runtime Environment version | Java 运行时环境版本 |
java.vendor | Java Runtime Environment vendor | Java 运行时环境供应商 |
java.vendor.url | Java vendor URL | Java 供应商的 URL |
java.home | Java installation directory | Java 安装目录 |
java.vm.specification.version | Java Virtual Machine specification version | Java 虚拟机规范版本 |
java.vm.specification.vendor | Java Virtual Machine specification vendor | Java 虚拟机规范供应商 |
java.vm.specification.name | Java Virtual Machine specification name | Java 虚拟机规范名称 |
java.vm.version | Java Virtual Machine implementation version | Java 虚拟机实现版本 |
java.vm.vendor | Java Virtual Machine implementation vendor | Java 虚拟机实现供应商 |
java.vm.name | Java Virtual Machine implementation name | Java 虚拟机实现名称 |
java.specification.version | Java Runtime Environment specification version | Java 运行时环境规范版本 |
java.specification.vendor | Java Runtime Environment specification vendor | Java 运行时环境规范供应商 |
java.specification.name | Java Runtime Environment specification name | Java 运行时环境规范名称 |
java.class.version | Java class format version number | Java 类格式版本号 |
java.class.path | Java class path | Java 类路径 |
java.library.path | List of paths to search when loading libraries | 加载库时搜索的路径列表 |
java.io.tmpdir | Default temp file path | 默认的临时文件路径 |
java.compiler | Name of JIT compiler to use | 要使用的 JIT 编译器的名称 |
java.ext.dirs | Path of extension directory or directories | 一个或多个扩展目录的路径 |
os.name | Operating system name | 操作系统的名称 |
os.arch | Operating system architecture | 操作系统的架构 |
os.version | Operating system version | 操作系统的版本 |
file.separator | File separator ("/" on UNIX) | 文件分隔符(在 UNIX 系统中是“/”) |
path.separator | Path separator (":" on UNIX) | 路径分隔符(在 UNIX 系统中是“:”) |
line.separator | Line separator ("\n" on UNIX) | 行分隔符(在 UNIX 系统中是“/n”) |
user.name | User's account name | 用户的账户名称 |
user.home | User's home directory | 用户的主目录 |
user.dir | User's current working directory | 用户的当前工作目录 |
或者使用OperatingSystemMXBean系统类来获取
OperatingSystemMXBean op = ManagementFactory.getOperatingSystemMXBean();
System.out.println("Architecture: " + op.getArch());
System.out.println("Processors: " + op.getAvailableProcessors());
System.out.println("System name: " + op.getName());
System.out.println("System version: " + op.getVersion());
System.out.println("Last minute load: " + op.getSystemLoadAverage());
4. 获取进程信息
Window : tasklist /V | findstr java
Linux: top -b -n 1 | grep java
通过 VirtualMachine.list() 取得JVM进程描述信息对象
List<VirtualMachineDescriptor> descriptorList = VirtualMachine.list();
5. 内存,CPU信息
Window:
使用命令 tasklist /V /FI "pid eq 5027" 获得进程的内存使用信息,结合OperatingSystemMXBean获取操作系统的内存等的使用情况来分析;
Linux:
top -b -n 1 -p 5027
6. 堆内存信息
使用 MemoryMXBean获取堆内存和非堆内存(方法区)
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
//堆内存
MemoryUsage memory = memoryMXBean.getHeapMemoryUsage();
//非堆内存
MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
7. 端口信息
window: netstat -nao -p tcp | findstr /V "CLOSE_WAIT" | findstr 2576
linux : netstat -antup | grep 2576 |grep -v "CLOSE_WAIT" | head -20
8. 线程信息
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
System.out.println("ThreadCount: " + thread.getThreadCount());
System.out.println("AllThreadIds: " + thread.getAllThreadIds());
System.out.println("CurrentThreadUserTime: " + thread.getCurrentThreadUserTime());
9. MXBean使用样例
9.1 根据pid获取jvm对象
VirtualMachine virtualMachine = VirtualMachine.attach(Integer.toString(pid));
9.2 根据jvm对象获取jmx服务
public static JMXServiceURL getJMXServiceURL(VirtualMachine virtualMachine) throws IOException, AgentLoadException, AgentInitializationException {
String address = virtualMachine.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
if (address != null) {
return new JMXServiceURL(address);
}
int pid = Convert.toInt(virtualMachine.id());
address = ConnectorAddressLink.importFrom(pid);
if (address != null) {
return new JMXServiceURL(address);
}
String agent = getManagementAgent();
virtualMachine.loadAgent(agent);
address = virtualMachine.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
if (address != null) {
return new JMXServiceURL(address);
}
return null;
}
9.3. 使用MXBean代理获取具体管理工具
/**
* 访问指定程序的 MXBean
*/
public static <T extends PlatformManagedObject> T visitMBean(int pid, Class<T> clazz) throws Exception {
//第一种直接调用同一 Java 虚拟机内的 MXBean 中的方法。
// RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
// String vendor1 = mxbean.getVmVendor();
// System.out.println("vendor1:" + vendor1);
//第二种使用 MXBean 代理
VirtualMachine virtualMachine = VirtualMachine.attach(Integer.toString(pid));
JMXServiceURL jmxServiceURL = JVMUtil.getJMXServiceURL(virtualMachine);
if (jmxServiceURL == null) {
return null;
}
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null);
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
// return ManagementFactory.newPlatformMXBeanProxy(mBeanServerConnection, ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
// ManagementFactory.newPlatformMXBeanProxy(mBeanServerConnection, mxBeanName, clazz);
return ManagementFactory.getPlatformMXBean(mBeanServerConnection, clazz);
}
/**
* 访问指定程序的 MXBean
*/
public static <T extends PlatformManagedObject> List<T> visitMBeans(int pid, Class<T> clazz) throws Exception {
//第一种直接调用同一 Java 虚拟机内的 MXBean 中的方法。
// RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
// String vendor1 = mxbean.getVmVendor();
// System.out.println("vendor1:" + vendor1);
//第二种使用 MXBean 代理
VirtualMachine virtualMachine = VirtualMachine.attach(Integer.toString(pid));
JMXServiceURL jmxServiceURL = JVMUtil.getJMXServiceURL(virtualMachine);
if (jmxServiceURL == null) {
return null;
}
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null);
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
return ManagementFactory.getPlatformMXBeans(mBeanServerConnection, clazz);
}
9.4 访问MXBean demo
ThreadMXBean threadMXBean = (ThreadMXBean) visitMBean(pid, clazz);
long[] threadIds = threadMXBean.getAllThreadIds();
System.out.println("ThreadCount: " + threadMXBean.getThreadCount());
System.out.println("AllThreadIds: " + threadMXBean.getAllThreadIds());
System.out.println("CurrentThreadUserTime: " + threadMXBean.getCurrentThreadUserTime());
// for (int i = 0; i < threadIds.length; i++) {
// ThreadInfo info = threadMXBean.getThreadInfo(threadIds[i]);
// System.out.println(info);
// }
List<GarbageCollectorMXBean> collectorMXBeanList2 = visitMBeans(pid, GarbageCollectorMXBean.class);
System.out.println(collectorMXBeanList2);
for(GarbageCollectorMXBean GarbageCollectorMXBean : collectorMXBeanList2){
System.out.println("gc name:" + GarbageCollectorMXBean.getName());
System.out.println("CollectionCount:" + GarbageCollectorMXBean.getCollectionCount());
System.out.println("CollectionTime" + GarbageCollectorMXBean.getCollectionTime());
}
9.6 demo
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.lang.management.*;
import java.util.List;
/**
* java.lang.management包 监控
*/
public class JVMManagementDemo {
/**
* {@link ManagementFactory}
* ClassLoadingMXBean 用于 Java 虚拟机的类加载系统的管理接口。
* CompilationMXBean 用于 Java 虚拟机的编译系统的管理接口。
* GarbageCollectorMXBean 用于 Java 虚拟机的垃圾回收的管理接口。
* MemoryManagerMXBean 内存管理器的管理接口。
* MemoryMXBean Java 虚拟机的内存系统的管理接口。
* MemoryPoolMXBean 内存池的管理接口。
* OperatingSystemMXBean 用于操作系统的管理接口,Java 虚拟机在此操作系统上运行。
* RuntimeMXBean Java 虚拟机的运行时系统的管理接口。
* ThreadMXBean Java 虚拟机线程系统的管理接口。
*
* @cmd
* start : javaw -jar -Xms10M -Dapplication=simpleDemo simpleDemo-1.0-SNAPSHOT.jar
* stop : taskkill /F /PID 1201
*
* @param args
*/
public static void main(String[] args) throws Exception {
// 获取本机信息
showJvmInfo();
showMemoryInfo();
showSystem();
showClassLoading();
showCompilation();
showThread();
showGarbageCollector();
showMemoryManager();
showMemoryPool();
// 代理获取 远程 进程信息
/* String tag = "simpleDemo";
int pid = AbstractProjectCommander.getInstance().getPid(tag);*/
int pid = 12345;
Class clazz = ThreadMXBean.class;
ThreadMXBean threadMXBean = (ThreadMXBean) visitMBean(pid, clazz);
long[] threadIds = threadMXBean.getAllThreadIds();
System.out.println("ThreadCount: " + threadMXBean.getThreadCount());
System.out.println("AllThreadIds: " + threadMXBean.getAllThreadIds());
System.out.println("CurrentThreadUserTime: " + threadMXBean.getCurrentThreadUserTime());
// for (int i = 0; i < threadIds.length; i++) {
// ThreadInfo info = threadMXBean.getThreadInfo(threadIds[i]);
// System.out.println(info);
// }
List<GarbageCollectorMXBean> collectorMXBeanList2 = visitMBeans(pid, GarbageCollectorMXBean.class);
System.out.println(collectorMXBeanList2);
for(GarbageCollectorMXBean GarbageCollectorMXBean : collectorMXBeanList2){
System.out.println("gc name:" + GarbageCollectorMXBean.getName());
System.out.println("CollectionCount:" + GarbageCollectorMXBean.getCollectionCount());
System.out.println("CollectionTime" + GarbageCollectorMXBean.getCollectionTime());
}
}
/**
* Java 虚拟机的运行时系统
*/
public static void showJvmInfo() {
RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
String vendor = mxbean.getVmVendor();
System.out.println("jvm name:" + mxbean.getVmName());
System.out.println("jvm version:" + mxbean.getVmVersion());
System.out.println("jvm bootClassPath:" + mxbean.getBootClassPath());
System.out.println("jvm start time:" + mxbean.getStartTime());
}
/**
* Java 虚拟机的内存系统
*/
public static void showMemoryInfo() {
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = mem.getHeapMemoryUsage();
System.out.println("Heap committed:" + heap.getCommitted() + " init:" + heap.getInit() + " max:"
+ heap.getMax() + " used:" + heap.getUsed());
}
/**
* Java 虚拟机在其上运行的操作系统
*/
public static void showSystem() {
OperatingSystemMXBean op = ManagementFactory.getOperatingSystemMXBean();
System.out.println("Architecture: " + op.getArch());
System.out.println("Processors: " + op.getAvailableProcessors());
System.out.println("System name: " + op.getName());
System.out.println("System version: " + op.getVersion());
System.out.println("Last minute load: " + op.getSystemLoadAverage());
}
/**
* Java 虚拟机的类加载系统
*/
public static void showClassLoading(){
ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
System.out.println("TotalLoadedClassCount: " + cl.getTotalLoadedClassCount());
System.out.println("LoadedClassCount" + cl.getLoadedClassCount());
System.out.println("UnloadedClassCount:" + cl.getUnloadedClassCount());
}
/**
* Java 虚拟机的编译系统
*/
public static void showCompilation(){
CompilationMXBean com = ManagementFactory.getCompilationMXBean();
System.out.println("TotalCompilationTime:" + com.getTotalCompilationTime());
System.out.println("name:" + com.getName());
}
/**
* Java 虚拟机的线程系统
*/
public static void showThread(){
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
System.out.println("ThreadCount: " + thread.getThreadCount());
System.out.println("AllThreadIds: " + thread.getAllThreadIds());
System.out.println("CurrentThreadUserTime: " + thread.getCurrentThreadUserTime());
//......还有其他很多信息
}
/**
* Java 虚拟机中的垃圾回收器。
*/
public static void showGarbageCollector(){
List<GarbageCollectorMXBean> gc = ManagementFactory.getGarbageCollectorMXBeans();
for(GarbageCollectorMXBean GarbageCollectorMXBean : gc){
System.out.println("GC name:" + GarbageCollectorMXBean.getName());
System.out.println("CollectionCount:" + GarbageCollectorMXBean.getCollectionCount());
System.out.println("CollectionTime" + GarbageCollectorMXBean.getCollectionTime());
}
}
/**
* Java 虚拟机中的内存管理器
*/
public static void showMemoryManager(){
List<MemoryManagerMXBean> mm = ManagementFactory.getMemoryManagerMXBeans();
for(MemoryManagerMXBean eachmm: mm){
System.out.println("name:" + eachmm.getName());
System.out.println("MemoryPoolNames:" + eachmm.getMemoryPoolNames().toString());
}
}
/**
* Java 虚拟机中的内存池
*/
public static void showMemoryPool(){
List<MemoryPoolMXBean> mps = ManagementFactory.getMemoryPoolMXBeans();
for(MemoryPoolMXBean mp : mps){
System.out.println("name:" + mp.getName());
System.out.println("CollectionUsage:" + mp.getCollectionUsage());
System.out.println("type:" + mp.getType());
}
}
/**
* 访问指定程序的 MXBean
*/
public static <T extends PlatformManagedObject> T visitMBean(int pid, Class<T> clazz) throws Exception {
//第一种直接调用同一 Java 虚拟机内的 MXBean 中的方法。
// RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
// String vendor1 = mxbean.getVmVendor();
// System.out.println("vendor1:" + vendor1);
//第二种使用 MXBean 代理
VirtualMachine virtualMachine = VirtualMachine.attach(Integer.toString(pid));
JMXServiceURL jmxServiceURL = JVMUtil.getJMXServiceURL(virtualMachine);
if (jmxServiceURL == null) {
return null;
}
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null);
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
// return ManagementFactory.newPlatformMXBeanProxy(mBeanServerConnection, ManagementFactory.THREAD_MXBEAN_NAME, ThreadMXBean.class);
// ManagementFactory.newPlatformMXBeanProxy(mBeanServerConnection, mxBeanName, clazz);
return ManagementFactory.getPlatformMXBean(mBeanServerConnection, clazz);
}
/**
* 访问指定程序的 MXBean
*/
public static <T extends PlatformManagedObject> List<T> visitMBeans(int pid, Class<T> clazz) throws Exception {
//第一种直接调用同一 Java 虚拟机内的 MXBean 中的方法。
// RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
// String vendor1 = mxbean.getVmVendor();
// System.out.println("vendor1:" + vendor1);
//第二种使用 MXBean 代理
VirtualMachine virtualMachine = VirtualMachine.attach(Integer.toString(pid));
JMXServiceURL jmxServiceURL = JVMUtil.getJMXServiceURL(virtualMachine);
if (jmxServiceURL == null) {
return null;
}
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null);
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
return ManagementFactory.getPlatformMXBeans(mBeanServerConnection, clazz);
}
}
Java进程监控的更多相关文章
- 轻量级监控平台之java进程监控脚本
轻量级监控平台之java进程监控脚本 #!/bin/bash #进程监控脚本 #功能需求: 上报机器Java进程的进程ID,对应的端口号service tcp端口号,tomcat http 端口号,以 ...
- 初识阿里开源的本地Java进程监控调试工具arthas(阿尔萨斯)
转载自:https://www.cnblogs.com/linhui0705/p/9795417.html 上个月,阿里开源了一个名为Arthas的监控工具.恰逢近期自己在写多线程处理业务,由此想到了 ...
- 通过JDK常用工具监控Java进程的内存占用情况
目录 1 JDK 工具的使用 2 查看 GC 日志信息 3 添加 JMS 远程监控 Tomcat是一款常用的Web容器, 它是运行在 JVM(Java Virtual Machine) 中的一个Jav ...
- Linux记录-JMX监控JAVA进程
3.修改xxx.sh 加入export JAVA_OPTS="-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.mana ...
- 利用VisualVm和JMX远程监控Java进程
自Java 6开始,Java程序启动时都会在JVM内部启动一个JMX agent,JMX agent会启动一个MBean server组件,把MBeans(Java平台标准的MBean + 你自己创建 ...
- JVisualVM监控本地Java进程
一.基于JVisualVM的可视化监控 1.打开C:\Program Files\Java\jdk1.8.0_131\bin下的jvisualvm.exe 2.打开后,会列出本机所有的Java进程 3 ...
- Zabbix 通过 JMX 监控 java 进程
参考: [ JMX monitoring ] [ Zabbix Java gateway ] [ JMX Monitoring (Java Gateway) not Working ] [ Monit ...
- 使用JDK自带的jmap和jhat监控处于运行状态的Java进程
对于处于运行状态中的Java进程,JDK自带了很多工具,允许Java开发人员监控运行进程中的各种状态,比如该进程内部创建了多少个对象实例,消耗了多少内存,等等. 本文基于JDK1.8而写成. 我下面写 ...
- JVM探秘:VisualVM监控远程Java进程
VisualVM在Java 8中是JDK自带的一个图形化工具,项目主页 VisualVM,在后续版本中可能会从JDK移除. VisualVM可以监控Java进程的CPU与内存占用情况,可以监控Java ...
随机推荐
- 从客户端中检测到有潜在危险的 request.form值
这里只说ASP.NET MVC的解决方法,ASP.NET已经不碰了. 给控制器加上[ValidateInput(false)]特性即可忽略含有HTML标记的内容. [HttpPost] [Valida ...
- 存储过程:SET Transaction Isolation Level Read语法的四种情况
这几天一直在弄存储过程,现在在这里跟大伙共享下资料: SET Transaction Isolation Level Read UNCOMMITTED 使用这句东东呢可以分为四种情况,现在就在这里逐一 ...
- Python中的memoryview
Python中的memoryview提供了类似C语言指针的功能,有了memoryview,如果某个Object支持buffer protocol,那么就可以通过memory去访问到他的内部. Pyth ...
- 特殊方法 之 len __repr__ __str__
关于len, 如果x是一个内置类型的实例,那么len(x)的速度回非常快,背后的原因是CPython会直接从一个C结构体里读取对象的长度,完全不用调用任何方法,获取一个集合中的元素的数量是一个很常见的 ...
- Sql中partition by的使用
partition by关键字是oracle中分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,partition by用于给结果集分 ...
- 关于npm audit fix
https://blog.csdn.net/weixin_40817115/article/details/81007774 npm audit : npm@5.10.0 & npm@6,允许 ...
- js 获取窗口大小
//获得窗口大小 function findDimensions() //函数:获取尺寸 { var point = {}; ...
- pyinstaller打包程序包含openpyxl库问题解决
带有openpyxl库时,直接打包,总会失败: 原因:看本地文件...Anaconda3\Lib\site-packages\PyInstaller\hooks\hook-openpyxl.py 发现 ...
- tinymce+粘贴word图片例子
tinymce是很优秀的一款富文本编辑器,可以去官网下载.https://www.tiny.cloud 这里分享的是它官网的一个收费插件powerpaste的旧版本源码,但也不影响功能使用. http ...
- http 内容协商返回最适合的内容
内容协商返回最命适的内容AcceptAccept-CharsetAccept-EncodingAccept-LanguageContent-Language内容协商技术有以下 3 种类型.服务器驱动协 ...