JVM调优之GC日志分析及可视化工具介绍

news2024/11/17 17:37:50

JVM调优之GC日志分析及可视化工具介绍

文章目录

  • JVM调优之GC日志分析及可视化工具介绍
    • GC日志参数
      • GC日志参数
      • 常用垃圾收集器参数
    • GC日志分析
      • 日志的含义
      • 使用 ParNew + Serial Old 的组合进行内存回收
      • 使用 Parallel Scavenge + Parallel Old 的组合进行内存回收
      • 大对象回收分析
    • 日志分析工具
      • 日志工具简介
      • 测试准备
      • GCeasy
      • GCViewer

GC日志是一个很重要的工具,它准确记录了每一次的GC的执行时间和执行结果,通过分析GC日志可以优化堆设置和GC设置,或者改进应用程序的对象分配模式。

GC日志参数

不同的垃圾收集器,输出的日志格式各不相同,但也有一些相同的特征。熟悉各个常用垃圾收集器的GC日志,是进行JVM调优的必备一步。 解析GC日志,首先需要收集日志,常用的有以下JVM参数用来打印输出日志信息:

GC日志参数

参数说明
-XX:+PrintGC打印简单GC日志。 类似:-verbose:gc
-XX:+PrintGCDetails打印GC详细信息
-XX:+PrintGCTimeStamps输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps输出GC的时间戳(以日期的形式)
-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息
-Xloggc:…/logs/gc.log指定输出路径收集日志到日志文件

例如,使用如下参数启动:

-Xms28m
-Xmx28m
//开启记录GC日志详细信息(包括GC类型、各个操作使用的时间),并且在程序运行结束打印出JVM的内存占用情况
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation 开启滚动生成日志
-Xloggc:E:/logs/gc.log

常用垃圾收集器参数

参数描述
UseSerialGC虚拟机在运行在 Client 模式下的默认值,打开此开关后,使用 Serial+Serial Old 收集器组合进行内存回收
UseParNewGC使用 ParNew + Serial Old 收集器组合进行内存回收
UseConcMarkSweepGC使用 ParNew + CMS + Serial Old 的收集器组合尽心内存回收,当 CMS 出现 Concurrent Mode Failure 失败后会使用 Serial Old 作为备用收集器
UseParallelOldGC使用 Parallel Scavenge + Parallel Old 的收集器组合
UseParallelGC使用 Parallel Scavenge + Serial Old (PS MarkSweep)的收集器组合
SurvivorRatio新生代中 Eden 和任何一个 Survivor 区域的容量比值,默认为 8
PretenureSizeThreshold直接晋升到老年代对象的大小,单位是Byte
UseAdaptiveSizePolicy动态调整 Java 堆中各区域的大小以及进入老年代的年龄
ParallelGCThreads设置并行 GC 时进行内存回收的线程数
GCTimeRatioGC 时间占总时间的比率,默认值为99,只在 Parallel Scavenge 收集器的 时候生效
MaxGCPauseMillis设置 GC 最大的停顿时间,只在 Parallel Scavenge 收集器的时候生效
CMSInitiatingOccupancyFraction设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认是 68%,仅在 CMS 收集器上生效
CMSFullGCsBeforeCompaction设置 CMS 收集器在进行多少次垃圾回收之后启动一次内存碎片整理
UseG1GC使用 G1 (Garbage First) 垃圾收集器
MaxGCPauseMillis设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(sox goal), JVM 会尽量去达成这个目标.
G1HeapRegionSize使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个 heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值 为 32Mb

GC日志分析

日志的含义

GC 日志理解起来十分简单,因为日志本来就是要给开发人员看的,所以设计的很直观。

举个例子,我们来分别说明各个部分所代表的含义:

[GC (Allocation Failure) [PSYoungGen: 6146K->904K(9216K)] 6146K->5008K(19456K), 0.0038730secs] [Times: user=0.08 sys=0.00, real=0.00 secs]

将上面 GC 日志抽象为各个部分,然后我们再分别说明各个部分的含义

[a(b)[c:d->e(f), g secs] h->i(j), k secs] [Times: user:l sys=m, real=n secs]

  • a: GC 或者是 Full GC
  • b: 用来说明发生这次 GC 的原因
  • c: 表示发生GC的区域,这里表示是新生代发生了GC,上面那个例子是因为在新生代中内存不够给新对象分配了,然后触发了 GC
  • d: GC 之前该区域已使用的容量
  • e: GC 之后该区域已使用的容量
  • f: 该内存区域的总容量
  • g: 表示该区域这次 GC 使用的时间
  • h: 表示 GC 前整个堆的已使用容量
  • i: 表示 GC 后整个堆的已使用容量
  • j: 表示 Java 堆的总容量
  • k: 表示 Java堆 这次 GC 使用的时间
  • l: 代表用户态消耗的 CPU 时间
  • m: 代表内核态消耗的 CPU 时间
  • n: 整个 GC 事件从开始到结束的墙钟时间(Wall Clock Time)

使用 ParNew + Serial Old 的组合进行内存回收

设置JVM参数

-Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -XX:SurvivorRatio=8

测试代码

public class TestGCLog01 {
	private static final int _1MB = 1024*1024;
	/**
	* VM参数:
	* 1. -Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -XX:SurvivorRatio=8
	*/
	public static void testAllocation() {
		byte[] allocation1, allocation2, allocation3, allocation4;
		allocation1 = new byte[2*_1MB];
		allocation2 = new byte[2*_1MB];
		allocation3 = new byte[2*_1MB];
		allocation4 = new byte[4*_1MB]; //出现一次 Minor GC
	}
}

打印结果

[GC (Allocation Failure) [ParNew: 8021K->647K(9216K), 0.0032114 secs] 8021K->6791K(19456K), 0.0032618 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 9216K, used 4910K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  52% used [0x00000000fec00000, 0x00000000ff029858, 0x00000000ff400000)
  from space 1024K,  63% used [0x00000000ff500000, 0x00000000ff5a1fc0, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
 Metaspace       used 3303K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

结果分析

首先我们通过JVM参数-Xms20M、-Xmx20M、-Xmn10M 把Java堆大小设置为20MB,不可扩展。其中10M分配给新生代,另外10M分配给老生代。
在这里插入图片描述

通过上面的GC日志我们可以看出一开始出现了一次,因为没有写明时FullGC,说明这次GC是一次Young GC(MinorGC)。 引起GC的原因是 内存分配失败 ,因为分配allocation的时候,Eden区已经没有足够的区域来分配了,所以发生来本次 MinorGC 。经过 MinorGC 之后,由ParNew后面的参数可知,新生代的已使用容量从8021->647(总容量是9216K,我们设置的10M,实际运行时会比设置的效小一点)。

8021K->6791K(19456K)表示的是整个堆空间大小的变化,GC后堆空间大小由原先的8021K,减为6791K,19456K是堆的总大小(仍然比我们设置的20M要小一点)。

可以看到整个堆的内存总量却几乎没有减少,原因就是,由于我们设置的年轻代大小为10M,由8:1:1的比例划分Eden,S0,S1区域大小可知,Eden大小为8M,S0大小为1M,S1大小为1M。当创建allocation1~3字节数组后,Eden区大小已经使用了6M,不够存放字节数组allocation4。

此时就会进行一次Young GC(Minor GC),由于发现新生代没有可以回收的对象,且剩余内存不足以存储allocation4。所以不得不使用内存担保将allocation1~3 三个对象提前转移到老年代。此时再在 Eden 区域为 allocation 分配 4MB 的空间,因此最后我们发现 Eden 区域占用了 4MB(used 4910K),老年代占用6MB(used 6144K)。

什么是内存担保

在JVM的内存分配时,内存分配担保机制就是当在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。

这个担保机制,是在jdk6才默认开启的,参数为【-XX:+HandlePromotionFailure】,现在用的最多的版本都是jdk8,所以这个参数可以忽略。参数HandlePromotionFailure的含义就是,是否允许担保失败,因为这里涉及到一个概率问题,也就是担保的核心判断条件:老年代最大的连续空间,是否大于历次晋升到老年代的对象的平均大小。

如果大于,就说明老年代很可能能够装载这次的新生代晋升对象,这时候不需要执行Full GC,冒险执行一次Youth GC,然后将新生代中存活的对象晋升到老年代。 如果没有这个担保机制,就会直接执行Full GC,这样对性能的影响频次会增加。当然,如果小于,就说明这次担保很可能失败,需要执行Full GC。所以担保机制,是为了减少Full GC的执行频次,提高了应用的性能。

使用 Parallel Scavenge + Parallel Old 的组合进行内存回收

设置参数:

-Xms20M -Xmx20M -Xmn10M -XX:+UseParallelGC -XX:+PrintGCDetails -XX:SurvivorRatio=8

测试代码

public class TestGCLog02 {
    private static final int _1MB = 1024*1024;
    /**
     * VM参数:
     * 1. -Xms20M -Xmx20M -Xmn10M -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+PrintGCDetails
-XX:SurvivorRatio=8
     */
    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2*_1MB];
        allocation2 = new byte[2*_1MB];
        allocation3 = new byte[2*_1MB];
        allocation4 = new byte[4*_1MB]; //出现一次 Minor GC
    }

    public static void main(String[] args) {
        testAllocation();
    }
}

打印结果

[GC (Allocation Failure) [ParNew: 6146K->872K(9216K), 0.0032114 secs] 6146K->4976K(19456K), 0.0032618 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
  PSYoungGen total 9216K, used 7339K [0x00000000ff600000, 0x0000000100000000,0x0000000100000000)
    eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc50e68,0x00000000ffe00000)
    from space 1024K, 85% used [0x00000000ffe00000,0x00000000ffeda020,0x00000000fff00000)
    to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  ParOldGen total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000,0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
Metaspace used 3420K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 371K, capacity 388K, committed 512K, reserved 1048576K

结果分析

日志结果与上面的日志结果基本类似,只是使用了不同的垃圾回收器,这里不再赘述。

大对象回收分析

大对象直接进入老年代。 虚拟机提供一个参数 -XX:PretenureSizeThreshold 用来设置直接在老年代分配的对象的大小,如果对象大于这个值就会直接在老年代分配。这样做的目的是避免在 Eden 区及两个Survivor 区之间发生大量的内存复制。

参数

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3145728

测试代码

public class TestGCLog03 {
	private static final int _1MB = 1024 * 1024;
	/**
	* VM参数:(参数序号对应实验序号)
	* -Xms20M -Xmx20M -Xmn10M -XX:+UseParNewGC -XX:+PrintGCDetails -
	XX:PretenureSizeThreshold=3145728
	*/
	public static void testPreteureSizeThreshold() {
		byte[] allocation;
		allocation = new byte[4 * _1MB];
	}
    public static void main(String[] args) {
		testPreteureSizeThreshold();
	}
}

执行结果

Heap
 par new generation   total 9216K, used 2041K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  24% used [0x00000000fec00000, 0x00000000fedfe5d8, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
 Metaspace       used 3302K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

结果分析

通过上面的堆的内存占用情况很容易看出我们分配的4MB大小的对象直接被放到了老年代 。

日志分析工具

日志工具简介

GC日志可视化分析工具GCeasy和GCviewer。通过GC日志可视化分析工具,我们可以很方便的看到JVM各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优的时候是很有用的。

  • GCeasy是一款在线的GC日志分析器,可以通过GC日志分析进行内存泄露检测、GC暂停原因分析、JVM配置建议优化等功能,而且是可以免费使用在线分析工具 https://gceasy.io/index.jsp
  • GCViewer是一款实用的GC日志分析软件,免费开源使用,你需要安装jdk或者java环境才可以使用。软件为GC日志分析人员提供了强大的功能支持,有利于大大提高分析效率

测试准备

编写代码生成gc.log日志准备分析

public class TestGCLog04 {
	private static final int _1MB = 1024 * 1024;
	/**
	* -Xms100M -Xmx100M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -Xloggc:E://gc.log
	*/
	public static void main(String[] args) {
		ArrayList<byte[]> list = new ArrayList<byte[]>();
		for (int i = 0; i < 500; i++) {
			byte[] arr = new byte[1024 * 100];
			list.add(arr);
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

在E://gc.log生成日志如下:

Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for windows-amd64 JRE (1.8.0_281-b09), built on Dec  9 2020 13:54:07 by "" with MS VC++ 15.9 (VS2017)
Memory: 4k page, physical 24941088k(11627804k free), swap 29921824k(11624816k free)
CommandLine flags: -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
7.369: [GC (Allocation Failure) [PSYoungGen: 27575K->3048K(30720K)] 27575K->25159K(99328K), 0.0213202 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
Heap
 PSYoungGen      total 30720K, used 29262K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
  eden space 27648K, 94% used [0x00000000fdf00000,0x00000000ff899790,0x00000000ffa00000)
  from space 3072K, 99% used [0x00000000ffa00000,0x00000000ffcfa1a0,0x00000000ffd00000)
  to   space 3072K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x0000000100000000)
 ParOldGen       total 68608K, used 22111K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
  object space 68608K, 32% used [0x00000000f9c00000,0x00000000fb197d90,0x00000000fdf00000)
 Metaspace       used 3805K, capacity 4536K, committed 4864K, reserved 1056768K
  class space    used 420K, capacity 428K, committed 512K, reserved 1048576K

GCeasy

这是一个在线分析日志的工具,主要功能是免费的,存在部分收费,地址:https://gceasy.io/

把上篇博客生成的日志文件,上传分析,就会接到可视化界面

在这里插入图片描述

jvm堆
在这里插入图片描述

Allocated:各部分分配大小

Peak:峰值内存使用量

关键绩效指标

在这里插入图片描述

吞吐量:93.769%,运行应用程序的时间/(GC时间的比值+运行应用程序的时间)

平均GC停顿时间

最大GC停顿时间

GC停顿持续时间范围:时间范围、GC数量、百分百

交互式图表

在这里插入图片描述

左边菜单有很多:

GC之前的堆、GC之后的堆、GC持续时间、GC停顿持续时间、回收的内存字节、Young区内存变化、Old区内存变化、Metaspace内存变化、分配对象大小、对象从Young到Old内存大小变化

后序的内容有:GC统计信息、Minor GC/Full GC信息、内存泄漏、GC的原因等等,所以这个工具的功能真的很强大

我们可以对比一下,Parallel、CMS、G1的GC效率

GCViewer

GCViewer是一个小工具,可以可视化展示 生成的详细GC输出。支持Sun / Oracle,IBM,HP和BEA的Java虚拟机。它是GNU LGPL下发布的免费软件。

下载:https://sourceforge.net/projects/gcviewer/

使用简介:

java -jar gcviewer-1.37-SNAPSHOT.jar

在这里插入图片描述

打开之后,点击File->Open File打开我们的GC日志,可以看到如下图,图标是可以放大缩小的,主要内容就是红线圈住的部分,里面的内容跟上面的GCeasy的比较类似,具体的可以看下GitHub中的描述。

CViewer

GCViewer是一个小工具,可以可视化展示 生成的详细GC输出。支持Sun / Oracle,IBM,HP和BEA的Java虚拟机。它是GNU LGPL下发布的免费软件。

下载:https://sourceforge.net/projects/gcviewer /

使用简介:

java -jar gcviewer-1.37-SNAPSHOT.jar

打开之后,点击File->Open File打开我们的GC日志,可以看到如下图,图标是可以放大缩小的,主要内容就是红线圈住的部分,里面的内容跟上面的GCeasy的比较类似,具体的可以看下GitHub中的描述。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

药品溶出曲线数据库

药物在体外的溶出行为&#xff0c;可以用来预测体内的崩解、溶出和吸收情况&#xff0c;同时药物体外溶出行为能够在一定程度上反映出制剂的质量。而溶出曲线特别是不同溶出介质的多条溶出曲线&#xff0c;可更加全面、灵敏地反映出上述关键要素的变化。当药物溶出曲线中药物品…

电脑磁盘重新分配空间的简单步骤(无损数据空间转移)

目录 一、前言 遇到问题 解决方式 二、磁盘现状与实现目标 磁盘现状 实现目标 三、操作步骤 &#xff08;一&#xff09;关闭电脑磁盘加密 &#xff08;二&#xff09;下载安装分区助手 &#xff08;三&#xff09;分配空间教程 注意事项 磁盘空间移动成功 一、前…

芯片设计五部曲之二 | 图灵艺术家——数字IC

《芯片设计五部曲》&#xff1a;模拟IC、数字IC、存储芯片、算法仿真和总结篇&#xff08;排名不分先后 上一集我们已经说了&#xff0c;模拟IC&#xff0c;更像是一种魔法。 我们深度解释了这种魔法的本质&#xff0c;以及如何在模拟芯片设计的不同阶段&#xff0c;根据常见的…

千万别乱用!Lombok不是万能的

背景 在使用Lombok构建无参构造器的时候&#xff0c;同事同时使用了Data和Builder&#xff0c;造成了编译不通过的问题&#xff01; Data使用说明 Lombok的Data注解可以为我们生成无参构造方法和类中所有属性的Getter和Setter方法。这样在我们开发的过程中&#xff0c;我们就…

seaborn的调色板、刻度、边框、标签、数据集等的一些解释

文章目录前言数据集构建整体风格设置调色板x轴的刻度值设置sns.lineplot实例前言 seaborn是对matplotlib进一步封装的库&#xff0c;可以用更少的代码&#xff0c;画出更好看的图。 官网&#xff1a;https://seaborn.pydata.org/index.html 下面记录一下seaborn的基础用法 数…

【日常业务开发】策略+工厂模式优化 if...else判断逻辑

【日常业务开发】策略工厂模式优化 if...else判断逻辑场景策略工厂模式优化利用Spring自动注入的特点处理继承InitializingBean静态工厂方法调用处理注解CommandLineRunnerApplicationContextAware处理/ApplicationListener\<ContextRefreshedEvent>场景 业务中经常有支…

一行代码写一个谷歌插件 —— Javascript

回顾 前期 【提高代码可读性】—— 手握多个代码优化技巧、细数哪些惊艳一时的策略_0.活在风浪里的博客-CSDN博客代码优化对象策略https://blog.csdn.net/m0_57904695/article/details/128318224?spm1001.2014.3001.5501 目录 技巧一&#xff1a;谷歌插件 第一步: 第二步…

Tomcat的安装和使用

作者&#xff1a;~小明学编程 文章专栏&#xff1a;JavaEE 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 下载Tomcat tomcat文件介绍 启动Tomcat 简单的部署静态页面 HTTP 服务器&#xff0c;就是在 TCP 服务器的基础上&#xff0c;加上了一些额外的功能…

计算机网络 - 概述

文章目录前言一、计算机网络概述1.1、计算机网络在信息时代的作用1.2、Intnet概述网络、互连网&#xff08;互联网&#xff09;和因特网因特网发展阶段因特网的组成1.3、计算机网络的定义和分类定义分类1.4、报文交换方式电路交换分组交换报文交换三种交换方式对比1.5、性能指标…

5-1输入/输出管理-I/O管理概述

文章目录一.I/O设备二.I/O控制器/设备控制器三.I/O控制方式1.程序直接控制方式2.中断驱动方式3.DMA方式&#xff08;直接存储器存取&#xff09;4.通道控制方式四.I/O子系统的层次结构五.输入/输出应用程序接口&设备驱动程序接口&#xff08;一&#xff09;输入/输出应用程…

【学Vue就跟玩一样】组件-单文件组件

单文件组件在实际开发中是经常使用的&#xff0c;那么如何创建一个单文件组件呢&#xff1f;那么本篇就来简单入一下单文件组件。一&#xff0c;创建单文件组件 1.切换到你想要创建该文件的目录下&#xff0c;我这里切换的是desktop这个目录&#xff0c;当然&#xff0c;也可以…

大学高校供配电系统谐波危害及治理方案

摘要&#xff1a;安全科学用电是保障高校教学科研及办公的基础条件&#xff0c;随着现代化教学、电子图书馆等先进教学手段的不断引入&#xff0c;智能给排水、变频空调、电梯传送系统等配套设施以及电子镇流的照明灯具设备等大量非线性电力电子设备涌现&#xff0c;很多高等院…

「科普」带你认识5G基站

随着5G时代的到来&#xff0c;为了信号的稳定传输&#xff0c;为了覆盖面更广&#xff0c;5G基站作为5G规模组网的“先行军”&#xff0c;其建设至关重要。 那么&#xff0c;5G时代的基站是如何建设的呢&#xff1f;下面就来给大家介绍一下。 截至2022年末&#xff0c;我国移…

【Lilishop商城】No4-7.业务逻辑的代码开发,涉及到:商品模块

仅涉及后端&#xff0c;全部目录看顶部专栏&#xff0c;代码、文档、接口路径在&#xff1a; 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇会结合业务介绍重点设计逻辑&#xff0c;其中重点包括接口类、业务类&#xff0c;具体的结合源代…

当代年轻人超半数人为“单身贵族”,数据可视化教你如何应付春节相亲

春节临近&#xff0c;不知大家在完成“年度总结”的同时&#xff0c;是否完成找到对象这个任务&#xff0c;如果还是单身&#xff0c;建议大家准备好如何应对家里安排的相亲吧&#xff01;现如今男女比例严重失调&#xff0c;也相对产生多数的“单身贵族”&#xff0c;当代年轻…

牛客竞赛每日俩题 - Day14

目录 错排算法 三维数组的应用 错排算法 发邮件__牛客网 错排: 假设有n封信要装入到n个信封中&#xff0c;每封信应该要放到对应的信封中&#xff0c;比如: 信: A B C D... 信封: a b c d. ... 由于疏忽将信放置出错&#xff0c;总共有多少种可能性每封信都放错 假设:D(n)…

自定义类型详解

这次我们来看自定义类型&#xff0c;我们之前接触过的自定义类型就有数组和结构体&#xff0c;我们来详细解析一下这些自定义类型的特点&#xff0c;已经一些我们没接触过的自定义类型 目录 1.结构体 1.1结构体的基础知识 1.2结构体的声明 1.3特殊声明 1.4结构的自引用 …

Mac AndroidStudio生成签名文件,显示SHA1和MD5值

一、首先&#xff0c;生成签名文件 1.点击菜单栏中的Build的。 2.弹出窗体&#xff0c;创建新的KeyStore&#xff1a;如下图。 3.生成jks文件 4.填写keystore和key密码。ketystore密码和key密码在后面会用到。其他信息也不是很重要&#xff0c;自己差不多能记住就好。 5.然后点…

设计模式学习(五):State状态模式

一、什么是State模式 在面向对象编程中&#xff0c;是用类表示对象的。也就是说&#xff0c;程序的设计者需要考虑用类来表示什么东西。类对应的东西可能存在于真实世界中&#xff0c;也可能不存在于真实世界中。 在State模式中&#xff0c;我们用类来表示状态。在现实世界中&a…

【部署】Apache DolphinScheduler 伪集群部署

【部署】Apache DolphinScheduler&#xff08;海豚&#xff09; 伪集群部署&#xff08;Pseudo-Cluster&#xff09;Standalone极速体验版DolphinScheduler 伪集群部署前置准备工作本地部署环境准备 DolphinScheduler 启动环境配置用户免密及权限配置机器SSH免密登陆启动zookee…