JVM-性能优化工具 MAT

一、MAT下载和安装

1、概述

MAT(Memory Analyzer Tool)工具是一款功能强大的]ava堆内存分析器。可以用于查找内存泄漏以及查看内存消耗情况。MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。

2、下载地址:

https://www.eclipse.org/mat/downloads.php

我目前电脑的JDK安装环境是1.8的,所以需要下载对应JDK1.8版本的MAT版本
在这里插入图片描述
在这里插入图片描述

3、安装

下载后解压,点击MemoryAnalyzer.exe进行启动
在这里插入图片描述

4、安装出现的报错问题

4.1、MAT版本和JDK版本不一致

问题描述:
要是直接下载最新版的MAT,可能需要高版本JDK才行。启动是需要JDK11或者更高的版本,我本地JDK版本是1.8,所以会报JDK版本不适合。
在这里插入图片描述
解决方法:
在MemoryAnalyzer.ini 中加入指定jdk的地址, (jdk不用安装直接下载 解压指定bin/javaw.exe就可)

-vm
D:/java/jdk1.8.0_211/binbin/javaw.exe
-vm
D:/java/jdk1.8.0_211/binbin/javaw.exe
-startup
plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.700.v20180518-1200
-vmargs
-Xmx1024m

4.2、堆dump文件较大、使用MAT打开的时候总是抛出 Java Heap Error

问题描述:
有时候线上产生的堆dump文件较大,如果你的hprof文件没有问题的话,使用MAT打开的时候总是抛出 Java Heap Error. 可能是默认的1024m内存不够用了。

解决办法:
找到MAT的安装目录,找到MemoryAnalyzer.ini 修改其中的-Xmx即可
在这里插入图片描述
将-Xmx1024m 调大即可
在这里插入图片描述

5、jmap命令拿到dump日志文件

jmap是Java虚拟机自带的一个命令行工具,可以用来生成JVM内存快照(Heap dump)文件。以下是使用jmap命令生成dump文件的步骤:

jmap -dump:format=b,file=heap.bin <pid>

通常情况下,在生产环境中使用jmap命令生成Heap dump文件时,建议把生成的文件下载到本地进行分析,以减少对生产环境的干扰。另外,在生成Heap dump文件时,一定要确保Java应用程序正常运行,否则可能会导致生成的文件不完整或者无法正确解析。

二、MAT工具排查分析OOM

1、故障现象:

集群应用服务器在高并发请求的情况下会不定时地因为响应超时而报警,但是很快又超时解除,恢复正常,如此反复,让运维人员非常苦恼。
原因分析: 来到一家新公司,一个重构项目的开发人员估计搞不动了,最后选择跑路,我的到来正好接盘了这个有好多bug的项目。配合测试功能测试完结束后,进行压测。发现查询接口只要并发一起来就会出现错乱的现象。先是排查原先写的代码是否有问题,没发现问题,然后我以为是脏读,调整的事务的隔离级别等等方法,发现还是解决不了。最后没办法在方法上加了一个synchronized锁,再进行压测时,虽然吞吐量不高,但是不会有报错的现象。等开始正式切换系统进行上线时。因为每天会有至少20w的查询量。只要某个时间段只要请求量很高就会出现连接超时的现象。当时也想到是因为加了synchronized造成高并发请求下,很多请求一直在等待,最后因为时间太久而造成的超时。所以我就下载了dump文件,使用MAT工具进行分析。果然是这个锁造成的。这是的我已经对代码稍微熟悉了,分析什么造成的错乱现象。发现代码有一处用到了共享变量,造成每次高并发去请求出现的错乱现象。我当时心里。。。
在这里插入图片描述
参考https://blog.csdn.net/cl939974883/article/details/124581664 文档,确实是synchronized造成的
在这里插入图片描述
下面详说一下MAT如何分析dump文件

2、MAT 分析 OOM 问题通常思路:

  1. 通过支配树功能或直方图功能查看消耗内存最大的类型,来分析内存泄露的大概原因;
  2. 查看那些消耗内存最大的类型、详细的对象明细列表,以及它们的引用链,来定位内存泄露的具体点;
  3. 配合查看对象属性的功能,可以脱离源码看到对象的各种属性的值和依赖关系,帮助我们理清程序逻辑和参数;
  4. 辅助使用查看线程栈来看 OOM 问题是否和过多线程有关,甚至可以在线程栈看到 OOM 最后一刻出现异常的线程。

3、使用MAT定位问题:

  • 定位问题方式一:

    现在有一个OOM后得到的堆转储文件 java_pid29569.hprof,现在要使用 MAT 的直方图支配树线程栈OQL 等功能来分析此次 OOM 的原因。

    首先,用 MAT 打开后先进入的是概览信息界面,可以看到整个堆是 437.6MB:
    在这里插入图片描述
    那么,这 437.6MB 都是什么对象呢?

    如图所示,工具栏的第二个按钮可以打开直方图,直方图按照类型进行分组,列出了每个类有多少个实例,以及占用的内存。可以看到,char[]字节数组占用内存最多,对象数量也很多,结合第二位的 java.lang.String 类型对象数量也很多,大概可以猜出(String 使用 char[]作为实际数据存储)程序可能是被字符串占满了内存,导致 OOM。
    在这里插入图片描述
    char[]上点击右键,选择 List objects->with incoming references,就可以列出所有的 char[]实例,以及每个 char[]的整个引用关系链:
    在这里插入图片描述
    随机展开一个 char[],如下图所示:
    在这里插入图片描述
    接下来,我们按照红色框中的引用链来查看,尝试找到这些大 char[]的来源:

  • 在①处看到,这些 char[]几乎都是 10000 个字符、占用 20000 字节左右(char 是 UTF-16,每一个字符占用 2 字节);

  • 在②处看到,char[]被 Stringvalue 字段引用,说明 char[]来自字符串;

  • 在③处看到,StringArrayListelementData 字段引用,说明这些字符串加入了一个 ArrayList 中;

  • 在④处看到,ArrayList 又被 FooServicedata 字段引用,这个 ArrayList 整个 RetainedHeap 列的值是 431MB。

  • 左侧的蓝色框可以查看每一个实例的内部属性,图中显示 FooService 有一个 data 属性,类型是 ArrayList

    Retained Heap(深堆):代表对象本身和对象关联的对象占用的内存;

    Shallow Heap(浅堆):代表对象本身占用的内存。
    比如,我们的 FooService 中的 data 这个 ArrayList 对象本身只有 16 字节,但是其所有关联的对象占用了 431MB 内存。这些就可以说明,肯定有哪里在不断向这个 List 中添加 String 数据,导致了 OOM。

    如果我们希望看到字符串完整内容的话,可以右键选择 Copy->Value,把值复制到剪贴板或保存到文件中:
    在这里插入图片描述
    这里,我们复制出的是 10000 个字符 a(下图红色部分可以看到)。
    看到这些,我们已经基本可以还原出真实的代码是怎样的了,定位到了问题代码。

  • 定位问题方式二:
    其实,我们之前使用直方图定位 FooService,已经走了些弯路。你可以点击工具栏中第三个按钮(下图左上角的红框所示)进入支配树界面。这个界面会按照对象的 Retained Heap 倒序直接列出占用内存最大的对象。
    在这里插入图片描述
    可以看到,第一位就是 FooService,整个路径是 FooSerice->ArrayList->Object[]->String->char[](蓝色框部分),一共有 21523 个字符串(绿色方框部分)。通常使用这种方式可以一步到位的定位出问题所在。

  • 借助MAT寻到具体问题原因
    我们就从内存角度定位到 FooService 是根源了。那么,OOM 的时候,FooService 是在执行什么逻辑呢?

    为解决这个问题,我们可以点击工具栏的第五个按钮(下图红色框所示)。打开线程视图,首先看到的就是一个名为 main 的线程(Name 列),展开后果然发现了 FooService
    在这里插入图片描述
    先执行的方法先入栈,所以线程栈最上面是线程当前执行的方法,逐一往下看能看到整个调用路径。

  • 因为我们希望了解 FooService.oom() 方法,看看是谁在调用它,它的内部又调用了谁,所以选择以 FooService.oom() 方法(蓝色框)为起点来分析这个调用栈。

  • 往下看整个绿色框部分,oom() 方法被 OOMApplicationrun 方法调用,而这个 run 方法又被 SpringAppliction.callRunner 方法调用。

  • 看到参数中的 CommandLineRunner 你应该能想到,OOMApplication 其实是实现了 CommandLineRunner 接口,所以是 SpringBoot 应用程序启动后执行的。

  • FooService 为起点往上看,从紫色框中的 CollectorsIntPipeline,大概也可以猜出,这些字符串是由 Stream 操作产生的。

  • 再往上看,可以发现在 StringBuilderappend 操作的时候,出现了 OutOfMemoryError 异常(黑色框部分),说明这这个线程抛出了 OOM 异常。

    我们看到,整个程序是 Spring Boot 应用程序,那么 FooService 是不是 SpringBean 呢,又是不是单例呢?

    如果能分析出这点的话,就更能确认是因为反复调用同一个 FooServiceoom 方法,然后导致其内部的 ArrayList 不断膨胀。

    点击工具栏的第四个按钮(如下图红框所示),来到 OQL 界面。在这个界面,我们可以使用类似 SQL 的语法,在 dump 中搜索数据(你可以直接在 MAT 帮助菜单搜索 OQL Syntax,来查看 OQL 的详细语法)。
    在这里插入图片描述
    比如,输入如下语句搜索 FooService 的实例:

    SELECT * FROM org.geekbang.time.commonmistakes.troubleshootingtools.oom.FooService
    

    在这里插入图片描述
    可以看到只有一个实例,然后我们通过 List objects 功能搜索引用 FooService 的对象:
    在这里插入图片描述
    可以看到,一共两处引用:

  • 第一处是,OOMApplication 使用了 FooService

  • 第二处是一个 ConcurrentHashMap。可以看到,这个 HashMapDefaultListableBeanFactorysingletonObjects 字段,可以证实 FooServiceSpring 容器管理的单例的 Bean

4、结论

到现在为止,虽然没看程序代码,但是已经大概知道程序出现 OOM 的原因和大概的调用栈了。再贴出程序来对比一下,果然和我们看到得一模一样:

@SpringBootApplication
public class OOMApplication implements CommandLineRunner {@AutowiredFooService fooService;public static void main(String[] args) {SpringApplication.run(OOMApplication.class, args);}@Overridepublic void run(String... args) throws Exception {//程序启动后,不断调用Fooservice.oom()方法while (true) {fooService.oom();}}
}
@Component
public class FooService {List<String> data = new ArrayList<>();public void oom() {//往同一个ArrayList中不断加入大小为10KB的字符串data.add(IntStream.rangeClosed(1, 10_000).mapToObj(__ -> "a").collect(Collectors.joining("")));}
}

到这里,我们使用 MAT 工具从对象清单、大对象、线程栈等视角,分析了一个 OOM 程序的堆转储。可以发现,有了堆转储,几乎相当于拿到了应用程序的源码 + 当时那一刻的快照,OOM 的问题无从遁形。

三、jvm-jps、jinfo、jstat、jstack、jmap 基本使用

给系统定位问题的时候,知识经验是基础,应用数据是依据,工具是手段,在jvm中,我们常见的数据包括: 运行日志、堆栈信息、GC信息、线程快照(threaddump/javacode)、堆快照(heapdump/hporf),jdk提供给我们了很实用的工具来分析,定位解决这些问题,这些工具包含于jdk中,并且以java实现,方便在不同的环境中不用安装其他依赖库即可使用,很是方便。下面分别介绍 jps、jinfo、jstat、jstack、jmap,本文使用的jdk版本为1.8.0_11

1、jps ( jvm process status tool ) 虚拟机进程工具

配置项作用
-q忽略主类的名称,只输出pid
-m输出启动类main函数的参数
-l输出主类名,如果进程执行的为jar,则输出jar路径
-v输出具体进程启动时jvm参数

1.命名格式

jps [options] pid

2.常用方式

  • jps -lv : 输出启动类名与启动时jvm参数,可以方便的看到各个tomcat的自定义参数配置
  • jps -lv |grep project_name : 在上述基础上过滤出自己想要查看的项目的信息

2、jinfo ( configuration info for java ) 显示虚拟机配置信息

1.常用用法

  • jinfo pid : 显示jvm系统属性与vm参数信息
  • jinfo -flags pid : 显示jvm vm参数信息,如最大最小堆,默认堆,垃圾收集器参数等
  • jinfo -sysprops pid : 显示jvm系统属性
  • jinfo -flag : 显示特定vm参数值,例如 jinfo -flag MaxHeapSize pid 输出pid的最大堆内存

3、jstat ( jvm statistics monitoring tool) 收集虚拟机各方面运行数据

1、语法格式

jstat [ option pid [interval[s|ms] [count]]]

说明: interval 表示循环时间间隔,默认单位为ms,可以在直接使用s/ms指定单位,如 60ms/1s, count 表示输出几次 例:

jstat gc pid 1s 20

每1s查询一次gc情况,查询20次

2、option 详解(主要分三类:类装载、垃圾收集、运行期编译状况)

配置项作用
-class监视类装载、卸载数量、总空间以及类装载所耗费的时间
-gc监视Java堆,包括Eden区、两survivor区、老年代、永久代等的容量、已用空间、GC时间合计等
-gccapacity与-gc基本相同,但关注点为Java堆各个区域使用到的最大、最小空间
-gcutil与-gc基本相同,但关注点为Java堆各个区域已使用空间占总空间的百分比
-gccause与-gcutil功能相同,但会额外输出导致上一次GC产生的原因
-gcnew监控新生代GC情况
-gcnewcapacity与-gcnew基本相同,但关注最大,最小空间
-gold监控老年代GC情况
-goldcapacity与-gcold基本相同,但关注最大,最小空间
-compiler输出被JIT编译过的方法、耗时等信息
-printcomplilation输出已经被JIT编译的方法

3、查看类装载卸载情况 jstat -class pid

属性释义
Loaded装载总数量
Bytes装载总大小
Unloaded卸载类的数量
Time加载和卸载类总共的耗时

4、查看GC情况 jstat -gc pid

属性释义
S0C新生代survivor0容量
S1C新生代survivor1容量
S0U新生代survivor0已使用大小
S1U新生代survivor1已使用大小
EC新生代eden区容量
EU新生代eden区已使用大小
OC老年代容量
OU老年代已使用大小
MC元数据容量,即方法区容量
MU元数据已使用空间
CCSC压缩类空间大小
CCSU压缩类空间使用大小
YGC新生代gc次数(young gc)
YGCT新生代gc时间(s)
FGC老生代gc次数(full gc)
GCT总的gc时间,包括young gc和full gc

5、查看GC情况,以百分比显示

jstat -gcutil pid

6、查看新生代GC情况

jstat -gcnew pid
属性释义
S0C新生代survivor0容量
S1C新生代survivor1容量
S0U新生代survivor0已使用大小
S1U新生代survivor1已使用大小
TT对象在新生代存活的次数
MTT对象在新生代存活的最大次数
DSS期望的幸存区大小
ECeden区大小
EUeden区已使用大小
YGCyoung gc次数
YGCTyoung gc 时间 (秒)

7、查看老年代GC情况

jstat -gcold pid

8、查看各空间容量

jstat -gccapacity pid
属性释义
NGCMN新生代最小容量
NGCMX新生代最大容量
NGC当前新生代容量
S0Csurvivor0大小
S1Csurvivor0大小
ECEDEN区大小
OGCMN老年代最小容量
OGCMX老年代最大容量
OGC当前老年代大小
MCMN元数据最小容量
MCMX元数据最大容量
CCSMN最小压缩类空间大小
CCSMX最大压缩类空间大小
CCSC当前压缩类空间大小)

9、查看编译情况

jstat -compiler pid

4、jmap ( memory map for java ) 虚拟机堆快照工具

1、常用用法

  • jmap -heap pid 查看当前jvm heapdump与垃圾收集器的使用情况
  • jmap -dump:format=b,file=/temp/filename.hprof pid 转储堆快照,生成hprof文件到指定路径
  • jmap -histo pid 列出当前heap中对象状况,附字节码与java对象映射表

5、jstack ( stack trace for java ) 虚拟机线程快照工具

效果演示: 会显示所有线程的各种信息,可以用来排查死锁,或线程长时间停滞的问题…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/114675.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

022-从零搭建微服务-短信服务(二)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;https://gitee.com/csps/mingyue 源码地址&#xff08;前端&#xff09;&#xff1a;https://gitee.com/csps…

(笔记一)利用open_cv在图像上进行点标记,文字注记,画圆、多边形、椭圆

&#xff08;1&#xff09;CV2中的绘图函数&#xff1a; cv2.line() 绘制线条cv2.circle() 绘制圆cv2.rectangle() 绘制矩形cv2.ellipse() 绘制椭圆cv2.putText() 添加注记 &#xff08;2&#xff09;注释 img表示需要绘制的图像color表示线条的颜色&#xff0c;采用颜色矩阵…

使用 Amazon Lambda 进行无服务器计算:云架构中的一场革命

引言 十年前,无服务器架构还像是痴人说梦。不再如此了! 有了 Amazon Lambda,我们现在可以建构和运行应用程序而不需要考虑服务器。云供应商会无缝地处理所有服务器的供应、扩展和管理。我们只需要关注代码。 这为云部署带来了前所未有的敏捷性、自动化和优化。但是,要发挥它的…

Flink SQL你用了吗?

分析&回答 Flink 1.1.0&#xff1a;第一次引入 SQL 模块&#xff0c;并且提供 TableAPI&#xff0c;当然&#xff0c;这时候的功能还非常有限。Flink 1.3.0&#xff1a;在 Streaming SQL 上支持了 Retractions&#xff0c;显著提高了 Streaming SQL 的易用性&#xff0c;使…

Spring框架知识点汇总

01.Spring框架的基本理解 关键字&#xff1a;核心思想IOC/AOP&#xff0c;作用&#xff08;解耦&#xff0c;简化&#xff09;&#xff0c;简单描述框架组成&#xff1b; Spring框架是一款轻量级的开发框架&#xff0c;核心思想是IOC&#xff08;反转控制&#xff09;和AOP&a…

Ubuntu 20.04.5 怎么安装微信

这是我的ubutun版本号 在这个系统装桌面版微信很多功能不健全。搜索了很多方法&#xff0c;这个算是不错的一个法子。 1.添加仓库 首次使用时&#xff0c;你需要运行如下一条命令将移植仓库添加到系统中。 wget -O- https://deepin-wine.i-m.dev/setup.sh | sh 2.应用安装 …

【01】弄懂共识机制PoW

基于工作量证明机制的共识机制PoW&#xff08;Proof of Work&#xff09; 特点就是多劳多特 共识过程 一个区块链系统中&#xff0c;交易历经多个步骤才能得以上链&#xff0c;并且需要经过多个节点的验证。以下是这些步骤的详细叙述&#xff1a; 交易进入交易池&#xff08;内…

MyBatis与spring集成

目录 MyBatis与Spring集成 导入pom依赖 导入generatorConfig.xml 导入spring-mybatis.xml 自动生成mapper文件 编写接口类&#xff1a;BookBiz aop整合PageHelper分页插件 编写分页查询 编写pagebean 编写PagerAspect类 测试结果 MyBatis与Spring集成 导入pom依赖 &l…

通过这 5 项 ChatGPT 创新增强您的见解

为什么绝大多数的人还不会使用chatGPT来提高工作效能&#xff1f;根本原因就在还不会循序渐进的发问与chatGPT互动。本文总结了5个独特的chatGPT提示&#xff0c;可以帮助您更好地与Chat GPT进行交流&#xff0c;以获得更清晰的信息、额外的信息和见解。 澄清假设和限制 用5种提…

面试必备:揭秘ArrayList和LinkedList,区别、优缺点与使用场景

大家好&#xff0c;我是你们的小米&#xff01;今天我要跟大家聊一个在面试中经常被问到的热门话题——ArrayList和LinkedList的区别、优缺点以及它们的使用场景。作为程序员&#xff0c;掌握这些知识点不仅可以在面试中脱颖而出&#xff0c;还能帮助我们更好地在项目中选择合适…

IntelliJ IDEA 2023.2.1使用Git时弹出“使用访问令牌登录”问题解决

文章目录 一、内网Git环境GitLabGogsGitea 二、外网Git环境GitHubGitee 升级为IntelliJ IDEA 2023.2.1后&#xff0c;使用Git时弹出“使用访问令牌登录”的窗口&#xff0c;习惯使用Git帐号密码登录的用户&#xff0c;面对这个突如其来的弹窗真的很懵。 一、内网Git环境 GitLa…

Android 中SettingsActivity(PreferenceFragmentCompat)的简单使用

如果你需要一个简单的APP设置&#xff0c;可以使用sharedPreferences进行存储&#xff0c;我们可以借助AndroidStudio快速创建一个用于设置的Activity&#xff0c;其实它是继承PreferenceFragmentCompat&#xff0c;存储方式用的就是sharedPreferences&#xff0c;只是帮我们节…

Springboot 接口方式硬通知实现ConfigurationProperties 、@Value 动态刷新

前言 看到这个文章标题&#xff0c;也许有的看官就觉得很多余&#xff0c; 因为Nacos 可以设置 NacosValue(value "${XXX}",autoRefreshed true) 实现动态刷新&#xff1b; 又因为cloud config的RefreshScope 实现动态刷新&#xff1b; 还有阿波罗...等 这…

[ros][ubuntu]ros在ubuntu18.04上工作空间创建和发布一个话题

构建catkin工作空间 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace cd ~/catkin_ws/ catkin_make 配置环境变量 echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc source ~/.bashrc 检查环境变量 echo $ROS_PACKAGE_PATH…

详解I2C

I2C&#xff08;也常写作 I I C IIC IIC&#xff0c; I 2 C I^2C I2C&#xff09;&#xff0c;全称为Inter-Integrated Circuit&#xff08;“互连集成电路”&#xff09;&#xff0c;用于在集成电路之间进行短距离数据传输。它由Philips&#xff08;现在的NXP半导体&#xff0…

Python爬虫基础之正则表达式

目录 一、什么是正则表达式&#xff1f; 二、re.compile()编译函数 三、group()获取匹配结果函数 四、常用匹配规则 4.1匹配单个字符 4.2匹配前字符次数 4.3匹配原生字符串 4.4匹配字符串开头和结尾 4.5分组匹配 五、re.match()开头匹配函数 六、re.search()全文搜索…

恒流电路的三种设计方案

作为硬件研发工程师相信对恒流电路不会陌生&#xff0c;本文介绍下三种恒流电路的原理图。 三极管恒流电路 三极管恒流电路 三极管的恒流电路&#xff0c;主要是利用Q2三极管的基级导通电压为0.6~0.7V这个特性&#xff1b;当Q2三极管导通&#xff0c;Q1三极管基级电压被拉低而…

lnmp架构-mysql2

4.mysql 组复制集群 首先对所有的节点重新初始化 因为对节点的数据一致性要求非常高 主从复制的时候 slave只会复制master的binlog日志 就是二进制日志 不会复制relay_log 在server1上 根据实际情况修改主机名和网段 log_slave_updateON 意思就是 当slave的sql线程做完之后…

【Unity-Cinemachine相机】虚拟相机(Virtual Camera)的本质与基本属性

我们可以在游戏进行时修改各个属性&#xff0c;但在概念上&#xff0c;最好将Virtual Camera 当作一种相机行为的“配置文件”&#xff0c;而不是一个组件。 我们的相机有几种行为就为它准备几种虚拟相机&#xff0c;比如角色移动就为它第三人称相机&#xff0c;瞄准就准备一个…

Ubuntu搭建web站点并发布公网访问【内网穿透】

文章目录 前言1. 本地环境服务搭建2. 局域网测试访问3. 内网穿透3.1 ubuntu本地安装cpolar3.2 创建隧道3.3 测试公网访问 4. 配置固定二级子域名4.1 保留一个二级子域名4.2 配置二级子域名4.3 测试访问公网固定二级子域名 前言 网&#xff1a;我们通常说的是互联网&#xff1b…