Log4j2异步打印可变对象的问题

现象

应用代码如下:

        Test test = new Test();test.setA(1);test.setB("1");log.info("before modification: {} \t ",test);test.setA(2);test.setB("2");log.info("after modification: {} \t ",test);

问题应用的日志控制台输出和文件输出为:

2023-11-18 20:45:32.391 INFO  [main]  before modification: ExpressServerApplication.Test(a=2, b=2) 	 
2023-11-18 20:45:32.391 INFO  [main] after modification: ExpressServerApplication.Test(a=2, b=2) 	 

修改前后打的日志都是改之后的内容a=2, b=2。

但是同样的依赖版本和Log4j2配置 新起了一个Spring Boot 应用进行自测,日志输出确又是正常的:

2023-11-18 21:19:28.750 INFO  [main] before modification: DemoLoggingApplication.Test(a=1, b=1) 	 
2023-11-18 21:21:21.029 INFO  [main] after modification: DemoLoggingApplication.Test(a=2, b=2) 	 

pom依赖和版本如下:


<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>// 异步日志支持
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.0</version>
</dependency>// 配置文件是YAML或YML时需添加该依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

日志门面使用Slf4j;日志实现使用Log4j2,使用混合异步方式,其配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration><Properties><property name="baseLogDir">./app/log</property><property name="logPattern">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%logger{0}:%line][%thread] %X{logger_id} -[TID: %X{EagleEye-TraceID}] %msg%n</property></Properties><Appenders><Console name="console" target="SYSTEM_OUT"><PatternLayout pattern="${logPattern}"/><ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/></Console><RollingFile name="business" fileName="${baseLogDir}/app.log"filePattern="${baseLogDir}/main/%d{yyyy-MM-dd}/app-%d{yyyy-MM-dd}-%i.log"><PatternLayout pattern="${logPattern}"/><ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/><!--            滚动策略 --><Policies><!--                时间间隔--><TimeBasedTriggeringPolicy interval="1" modulate="true"/><SizeBasedTriggeringPolicy size="200M"/></Policies><DefaultRolloverStrategy max="99999"><Delete basePath="${baseLogDir}/main" maxDepth="3"><IfFileName glob="app-20*"/><IfLastModified age="30d"/></Delete></DefaultRolloverStrategy></RollingFile><RollingFile name="error" fileName="${baseLogDir}/error.log"filePattern="${baseLogDir}/error/%d{yyyy-MM-dd}/error-%d{yyyy-MM-dd}-%i.log"><PatternLayout pattern="${logPattern}"/><ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/><Policies><TimeBasedTriggeringPolicy interval="1" modulate="true"/><SizeBasedTriggeringPolicy size="200M"/></Policies><DefaultRolloverStrategy max="99999"><Delete basePath="${baseLogDir}/error" maxDepth="3"><IfFileName glob="error-20*"/><IfLastModified age="30d"/></Delete></DefaultRolloverStrategy></RollingFile></Appenders><Loggers><AsyncRoot level="info" includeLocation="true"><AppenderRef ref="console"/><AppenderRef ref="business"/><AppenderRef ref="error"/></AsyncRoot></Loggers>
</Configuration>

原因剖析

异步基本原理

Log4j2异步日志基于 LMAX Disruptor 实现,借助该并发框架,在多线程场景下,Asynchronous Loggers 相比于Log4j1.x和Logback在吞吐量上提升18倍且在延迟上低了几个数量级。

首先简单介绍一下 Disruptor 框架。

Disruptor

LMAX是一家交易平台,交易系统需要达到 低延时高吞吐 两个目标。而阻碍Java系统以上目标的两个原因:

  • CPU缓存行失效
  • Locks需要进入内核态,代价大

由此诞生无锁的Disruptor并发框架。

Disruptor 借助Ring Buffer数据结构,Ring Buffer用于存放线程间通信的内容.

详见:
高性能队列——Disruptor
Log4j 2 Async Logging 的原理和实现

Log4j2异步日志

异步日志的优点:

  • 高吞吐
  • log方法耗时短,低延时

异步日志的缺点:

  • 异步打日志时错误处理

  • 打印可变内容可能打印错误内容:In some rare cases, care must be taken with mutable messages. Most of the time you don’t
    need to worry about this (这句话不合事实) . Log4 will ensure that log messages like logger.debug(“My object is {}”, myObject) will use the state of the myObject parameter at the time of the call to logger.debug(). The log
    message will not change even if myObject is modified later. It is safe to asynchronously log mutable objects because most Message implementations built-in to Log4j take a snapshot of the parameters. There are some exceptions however: MapMessage and StructuredDataMessage are mutable by design (事实还包含ParameterizedMessage): fields can be added to these messages after the message object was created. These messages should not be modified after they are logged with asynchronous loggers or asynchronous appenders; you may or may not see the modifications in the resulting log output. Similarly, custom Message implementations should be designed with asynchronous use in mind, and either take a snapshot of their parameters at construction time, or document their thread-safety characteristics.

  • 不利于CPU资源少的运行环境,因为需要额外后台线程进行磁盘IO

  • 如果打印日志比刷到磁盘还要快时,会导致队列排满,此时默认策略为使用当前线程执行append操作(类似线程池的CallerRun拒绝策略)。

详见:Asynchronous Loggers for Low-Latency Logging

现象原因

经过调试发现问题应用和自测应用传入Ring Buffer的LoggerEvent类型不同:

  • 问题应用:Log4jLogEvent
  • 自测应用:MutableLogEvent

Log4j2是否开启LocalThreads配置项影响了LogEventFactory的实现类,从而生成了不同的LoggerEvent
。关键代码就在org.apache.logging.log4j.util.Constants中,:

package org.apache.logging.log4j.util;public final class Constants {// 是否是WEB,问题应用是WEB,自测应用不是WEB,public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty("log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet"));// 问题应用关闭了ThreadLocals,自测应用开启了ThreadLocalspublic static final boolean ENABLE_THREADLOCALS;// ...static {ENABLE_THREADLOCALS = !IS_WEB_APP && PropertiesUtil.getProperties().getBooleanProperty("log4j2.enable.threadlocals", true);}
}

是否开启ThreadLocals直接影响了LoggerEventFacotry的实现类:

package org.apache.logging.log4j.core.config;
public class LoggerConfig extends AbstractFilterable {// ...static {// 是否指定了Log4jLogEventFactoryString factory = PropertiesUtil.getProperties().getStringProperty("Log4jLogEventFactory");if (factory != null) {try {Class<?> clazz = LoaderUtil.loadClass(factory);if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {LOG_EVENT_FACTORY = (LogEventFactory)clazz.newInstance();}} catch (Exception var2) {LOGGER.error("Unable to create LogEventFactory {}", factory, var2);}}// 没有指定,则看ThreadLocals是否开启// 开启则 ReusableLogEventFactory,会生成 MutableLogEvent,如问题应用// 关闭则 DefaultLogEventFactory,会生成 Log4jLogEvent,如自测应用if (LOG_EVENT_FACTORY == null) {LOG_EVENT_FACTORY = (LogEventFactory)(Constants.ENABLE_THREADLOCALS ? new ReusableLogEventFactory() : new DefaultLogEventFactory());}}
}

在自测应用中禁用ThreadLocals,复现了错误打印可变对象内容的现象。

由此,产生3个疑问:

  1. 开启Threadlocal时的ReusableLogEventFactory有什么优势?
  2. 为什么WEBAPP必须关闭ThreadLocals?
  3. 为什么从Log4jLogEvent打印可变对象的日志会错?
Garbage-free Steady State Logging

之前的许多日志框架会生成很多临时对象,比如log event objects, Strings, char arrays, byte arrays,这会造成GC压力。针对GC问题,2.6版本的Log4j2后提供2个模式:

  • garbage free mode: objects and buffers are reused and no temporary objects are allocated as much as possible
  • low garbage mode: not completely garbage free but does not use ThreadLocal fields, it’s the default mode when Log4j
    detects it is running in a web application.

所以ReusableLogEventFactory利用ThreadLocals提供LogEvent的重用,避免频繁GC。

WEBAPP的Log4j2必须禁用ThreadLocals

To avoid causing memory leaks, Log4j will not use these ThreadLocals when it detects that it is used in a web
application (when the javax.servlet.Servlet class is in the classpath, or when system property log4j2.is.webapp is set
to “true”).

WEBAPP能在不重启Servlet容器的情况下重新加载应用,如果使用ThreadLocals,共用的线程池的线程的ThreadLocals会引用老版本的LogEvent,这样LogEvent不会被GC到,导致内存泄漏。

如果保证线程池不会被公用且重新加载应用时线程池也会关闭再重启,就可以开启Garbage-free

详见:
Why WEBAPP not using ThreadLocals
why log4j gc free logging is not suitable for webapp

关于这里的ThreadLocal,可以看下京东的技术文章:记一次疑似JVM内存泄漏的排查过程

格式化日志内容的时机

org.apache.logging.log4j.core.LogEvent中会携带org.apache.logging.log4j.message.Message信息,比如:

  • WEBAPP中Log4jLogEvent携带ParameterizedMessage
  • MutableLogEvent携带ReusableParameterizedMessage

不同类型的Message对于占位符{}的字符串填充对象的时机不同,填充时占位符的对象内容才是真正被log到appender的内容。

  • 对于ReusableParameterizedMessage,在org.apache.logging.log4j.core.impl.ReusableLogEventFactory#createEvent
    方法中调用MutableLogEvent#setMessage时会填充{},该过程在进入RingBuffer之前由log的线程在log.info方法调用中 同步进行。
  • 对于ParameterizedMessage,填充{}却是由Disruptor的handler线程池(只有一个线程,日志多时处理LogEvent延迟大)
    来完成,如下图所示,在handler填充{}对象时,log的线程可能已经把该对象改变内容了。

在这里插入图片描述

因此,WEBAPP打印日志时,传入的对象应为"不可变对象":

  • 要么类似String这种类型(String.valueOf(…)或转JSON)
  • 要么确保对象后续不再变更。

org.apache.logging.log4j.message.Message的注释说明也应证了这一点:

Note: Message objects should not be considered to be thread safe nor should they be assumed to be safely reusable even
on the same thread. The logging system may provide information to the Message objects and the Messages might be queued
for asynchronous delivery. Thus, any modifications to a Message object by an application should by avoided after
the Message has been passed as a parameter on a Logger method.

或者自定义LogEventFacotry,在createEvent时同步填充{}

  1. 创建自定义LogEventFacotry
package com.example.demologging;import java.lang.reflect.Field;
import java.util.List;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.impl.LogEventFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.PropertiesUtil;/*** {@link AbstractStringLayout}* @author: baotingyu* @date: 2023/11/19**/
public class SyncFormatLog4jLogEventFactory implements LogEventFactory {protected static final int MAX_STRING_BUILDER_SIZE = Math.max(1024, size("log4j.layoutStringBuilder.maxSize", 2048));private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();private static int size(String property, int defaultValue) {return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue);}protected static StringBuilder getStringBuilder() {StringBuilder result = threadLocal.get();if (result == null) {result = new StringBuilder(1024);threadLocal.set(result);}trimToMaxSize(result);result.setLength(0);return result;}protected static void trimToMaxSize(StringBuilder stringBuilder) {if (stringBuilder.length() > MAX_STRING_BUILDER_SIZE) {stringBuilder.setLength(MAX_STRING_BUILDER_SIZE);stringBuilder.trimToSize();}}public LogEvent createEvent(String loggerName, Marker marker, String fqcn, Level level, Message data, List<Property> properties, Throwable t) {if(data instanceof ParameterizedMessage){try {// ThreadLocal重用StringBuilderStringBuilder builder = getStringBuilder();((ParameterizedMessage) data).formatTo(builder);Field formattedMessage = data.getClass().getDeclaredField("formattedMessage");formattedMessage.setAccessible(true);formattedMessage.set(data,builder.toString());} catch (NoSuchFieldException | IllegalAccessException e) {System.err.println(this.getClass().getSimpleName()+"fail to createEvent:"+e.getMessage());}}return new Log4jLogEvent(loggerName, marker, fqcn, level, data, properties, t);}
}
  1. 指定自定义LogEventFacotry:在resources目录下创建log4j2.component.properties
    文件(log4j2按文件名称加载),内容为Log4jLogEventFactory=com.example.demologging.SyncFormatLogEventFactory

Java日志体系

顺便记录一下Java日志体系。

最初,Java并没有日志模块,都是通过System.outSystem.err输出。1996年,Ceki Gülcü 主导编写了 Log4j
日志框架;2002年,Java官方推出了一个日志模块JUL(Java Util Logging)
,但是这时候Log4j已经成了事实日志框架,用的人更多;Apache在日志框架上抽象了一套标准,即一套日志门面 JCL (Jakarta Commons Logging)JCL
日志门面可以兼容Log4j日志实现和JUL日志实现;但是JCL问题很多,Ceki
Gülcü再次发功,另写了一套日志门面Slf4j(Simple Logging Facade for Java),但是这个协议如何兼容之前的Log4jJUL
呢?通过桥接包做适配,那之前用JCL门面的怎么用Slf4j门面呢?通过桥接包做适配;接入日志实现还得加桥接包,太麻烦,不如自己搞个新的日志实现,于是Ceki
Gülcü又发功了,写了日志实现 Logback,直接接入Slf4j,且针对之前的Log4j进行了改进;Apache看Logback
改进很多,也“借鉴”Logback开发了新的日志实现 Log4j2,更甚者的Log4j2也搞了分离的设计,分化成log4j-api
log4j-corelog4j-api也是日志门面,log4j-core才是日志实现,包名没有2,我真服了。

在这里插入图片描述

以上,我们在选择日志时,可以引入多个日志门面但只能引入一个日志实现。代码中打日志都用日志门面的api打印,而日志配置文件根据你的日志实现不同而不同。

桥接包实现主要分为两部分(以log4j-slf4-impl为例)

  • LoggerFactory: org.slf4j.ILoggerFactory 生成 Log4j2的 org.apache.logging.log4j.Logger
  • Logger: org.slf4j.Logger 桥接到 org.apache.logging.log4j.Logger

桥接类分别为:

  • LoggerFactory: log4j-slf4-impl包中的org.apache.logging.slf4j.Log4jLoggerFactory ,该工厂直接返回 log4j-slf4-impl
    org.apache.logging.slf4j.Log4jLogger
  • Logger: log4j-slf4-impl包中的org.apache.logging.slf4j.Log4jLogger

org.apache.logging.log4j.Logger,使用组合的方式在字段中组合了Log4j2的org.apache.logging.log4j.spi.ExtendedLogger
,很简单的实现。

org.apache.logging.slf4j.Log4jLoggerFactory作为桥接类,就有些技巧了,其UML图如下:

在这里插入图片描述

注意,上层的两个接口函数签名(函数名+参数列表)是一样的,也就是最下面的Log4jLoggerFactory只要实现该方法,就能满足两个接口的接口方法调用,而该方法又没有直接在Log4jLoggerFacotry中实现,而是在父抽象类AbstractLoggerAdapter中实现。

参考:Java日志系统历史从入门到崩溃

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

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

相关文章

组装盒示范程序

代码; #include <gtk-2.0/gtk/gtk.h> #include <glib-2.0/glib.h> #include <stdio.h>int main(int argc, char *argv[]) {gtk_init(&argc, &argv);GtkWidget *window;window gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDO…

用进程和线程完成TCP进行通信操作及广播和组播的通信

进程 代码 #include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <signal.h>#includ…

1958springboot VUE宿舍管理系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE宿舍管理系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09; &#xff0c;系统具有完整的源代码和数…

PyTorch实战:模型训练中的特征图可视化技巧

1.特征图可视化&#xff0c;这种方法是最简单&#xff0c;输入一张照片&#xff0c;然后把网络中间某层的输出的特征图按通道作为图片进行可视化展示即可。 2.特征图可视化代码如下&#xff1a; def featuremap_visual(feature, out_dirNone, # 特征图保存路径文件save_feat…

O2OA的数据库数据库配置-使用不同用户访问Oracle时报错-表或视图不存在

在使用Oracle数据库时&#xff0c;多个O2OA服务器同一个Oracle实例中使用不同的用户启动时&#xff0c;可能会遇到数据库访问的错误。本篇阐述此类问题以及解决方案。 一、先决条件&#xff1a; 1、O2OA已经下载并且解压到指定的目录&#xff1b; 2、Oracle数据库已经完成安…

90 Realistic Arctic Environment Textures snow(90+种逼真的北极环境纹理--雪、冰及更多)

一组90多个逼真的雪、冰、雪地岩石和其他被雪覆盖的地面纹理,供在雪地环境中使用。每个纹理都是可贴的/无缝的,并且完全兼容各种不同的场景--标准的Unity地形、Unity标准着色器、URP、HDRP等等都兼容。 所有的纹理都是4096x4096,并包括一个HDRP掩码,以完全支持HDRP。 特点。…

X86+FPGA, NXP+FPGA:工控稳“固”之选 赋能CPCI/VPX智能轨交新变革

工业IPC在目前大时代背景下面临机遇,但挑战同样也不少。在轨道交通领域&#xff0c;工控机必须具备高可靠性和稳定性&#xff0c;能够在复杂且严苛的工作环境中长时间无故障运行&#xff1b;需要满足严格的实时性和响应性能要求&#xff0c;确保能够迅速准确地处理传感器信号和…

椭圆的几何要素

椭圆的几何要素 flyfish 椭圆的方程为 x 2 a 2 y 2 b 2 1 \frac{x^2}{a^2} \frac{y^2}{b^2} 1 a2x2​b2y2​1。 长半轴 a a a&#xff08;绿色虚线&#xff09;和短半轴 b b b&#xff08;紫色虚线&#xff09;。 焦点 F 1 ( − c , 0 ) F1(-c, 0) F1(−c,0)&#…

暴雨讲堂|通往AGI的必由之路—AI agent是什么?

在三月份英伟达的新品发布会上&#xff0c;黄仁勋反复提及一个词汇— Generalist Embodied Agent&#xff0c;意为“通用具身智能体”&#xff0c;给观众留下了深刻的印象。其实具身智能指的是不同形态的拥有主动感知交互能力的机器人。其实&#xff0c;业界对它还有一个更为熟…

python如何安装ta-lib依赖包

在使用pip install ta-lib安装ta-lib库的时候,出现了以下错误信息。本文记录安装成功的整个过程。 解决办法 1、创建虚拟环境 我们使用conda进行创建一个python=3.8版本的虚拟环境 conda create -n python38 python==3.8激活创建好虚拟环境 conda activate python382、安装步…

媒体访谈 | 广告变现痛点有新解,俄罗斯市场成大热门?

今年一季度&#xff0c;中国自主研发游戏在海外市场实际销售收入达到了40.75亿美元&#xff0c;环比和同比均实现了超过5%的增长&#xff0c;出海&#xff0c;仍是游戏产品近些年来最主要的发展模式之一。 当今的市场环境正经历一系列深刻变革&#xff0c;移动游戏广告市场呈现…

IDEA集成Docker实现快捷部署

本文已收录于专栏 《运维》 目录 背景介绍优势特点操作步骤一、修改Docker配置二、配置Docker插件三、编写Maven插件四、构建Docker镜像五、创建Docker容器 总结提升 背景介绍 在我们手动通过Docker部署项目的时候&#xff0c;都是通过把打包好的jar包放到服务器上并且在服务器…

git提交遇见的<<<<<<<< HEAD无法运行程序问题

在项目文件目录下打开git bash Here 在命令行中输入 git reset --hard HEAD~1 进行复原 git reset --hard HEAD~1 即可

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析

ThreadPoolExecutor 状态 ThreadPoolExecutor 继承了 AbstractExecutorService&#xff0c;并实现了 ExecutorService 接口&#xff0c;用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态&#xff0c;后 29 位表示 …

python代码生成可执行文件

以下面转换图片尺寸的代码resize_images.py为例&#xff1a; 代码功能&#xff1a;原始图片放在img文件夹中&#xff0c;然后运行代码可以转换成指定分辨率&#xff0c;保存在同一目录下的新生成的文件夹中 import os import sys import cv2 from datetime import datetime f…

在React中,如何利用React.memo函数对函数组件进行优化?

React.memo 是 React 的一个高阶组件&#xff0c;用于对函数组件进行性能优化。它通过记忆化&#xff08;memoization&#xff09;来避免不必要的重新渲染。当组件的 props 没有变化时&#xff0c;React.memo 可以防止组件重新渲染&#xff0c;从而提高应用的性能。 使用 Reac…

wireshark抓包ssl数据出现ignored unknown record的原因

文章目录 前言一、出现原因二、wireshark抓包分析Ignored Unknown RecordTCP segment of a reassembled PDU 总结 前言 使用下面这个例子来观察记录层数据大于TCP MSS时用wireshark抓包出现ignored unknown record的情况并分析原因。 c语言利用openssl实现简单客户端和服务端&…

Rich Human Feedback for Text-to-Image Generation 读论文笔记

Rich Human Feedback for Text-to-Image Generation 读论文笔记 摘要方法细节收集数据的过程人类反馈确认 数据集VQA使用方法数据分析分数统计评价一致性&#xff08;pair alignment&#xff09; 实验模型模型架构模型变体模型其他优化 实验MetricsScoreHeatmapMisalignment 量…

什么是NLP-自然语言处理

什么是NLP-自然语言处理 什么是NLP开通NLP新建项目创建模型 什么是NLP NPL是面向算法小白用户的行业自适应标注、训练和服务平台。该产品支持文本实体抽取、文本分类、关键短语抽取、情感分析、关系抽取、短文本匹配、商品评价解析等 NLP 定制化算法能力&#xff0c;用户无需拥…

【变量与常量】

1.基本程序 容器用来存放物品 声明变量 num 是个用来装整数的变量 &#xff01; 一 个整数变量需要 4 个 byte 存储&#xff0c; 计算机 在内存里为你分配了 4 个 byte 。int num;在代码中&#xff0c;声明一个变量。 常用变量类型 存储不同类型的数据需要声明不同类型的变…