2024-4-10 群讨论:JFR 热点方法采样实现原理

以下来自本人拉的一个关于 Java 技术的讨论群。关注公众号:hashcon,私信拉你

什么是 JFR 热点方法采样,效果是什么样子?

其实对应的就是 jdk.ExecutionSample 和 jdk.NativeMethodSample 事件

57929a0c3ccd34a8ea2b041a603f5051.jpeg

c108e30ed2e9e20f6ce3641b807f51b3.jpeg

76c70305c49e63f72941d5e2ee2b128b.jpeg

这两个事件是用来采样的,采样的频率是可以配置的,默认配置在:default.jfc(https://github.com/openjdk/jdk/blob/master/src/jdk.jfr/share/conf/jfr/default.jfc):

<event&nbsp;name="jdk.ExecutionSample">
&nbsp;&nbsp;&nbsp;&nbsp;<setting&nbsp;name="enabled"&nbsp;control="method-sampling-enabled">true</setting>
&nbsp;&nbsp;&nbsp;&nbsp;<setting&nbsp;name="period"&nbsp;control="method-sampling-java-interval">20 ms</setting>
</event>

<event&nbsp;name="jdk.NativeMethodSample">
&nbsp;&nbsp;&nbsp;&nbsp;<setting&nbsp;name="enabled"&nbsp;control="method-sampling-enabled">true</setting>
&nbsp;&nbsp;&nbsp;&nbsp;<setting&nbsp;name="period"&nbsp;control="method-sampling-native-interval">20 ms</setting>
</event>

默认都是启用的,都是 20ms 一次。这个听上去消耗很大,实际上消耗很小的,详见下一节原理。

采样的原理是?

一切从源码出发https://github.com/openjdk/jdk/blob/master/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp:

//固定开启一个线程,用于 jfr java 方法与原生方法采样
void&nbsp;JfrThreadSampler::run()&nbsp;{
&nbsp;&nbsp;assert(_sampler_thread ==&nbsp;nullptr,&nbsp;"invariant");

&nbsp;&nbsp;_sampler_thread =&nbsp;this;
&nbsp;&nbsp;//获取上次 java 方法采样时间与原生方法采样时间
&nbsp;&nbsp;int64_t&nbsp;last_java_ms =&nbsp;get_monotonic_ms();
&nbsp;&nbsp;int64_t&nbsp;last_native_ms = last_java_ms;
&nbsp;&nbsp;//然后,在一个死循环中,不断的等待采样间隔到达,然后对应采样
&nbsp;&nbsp;while&nbsp;(true) {
&nbsp;&nbsp;&nbsp;&nbsp;//省略等待采样间隔(就是上面的 20ms 配置)的代码
&nbsp;&nbsp;&nbsp;&nbsp;//采样 java 方法
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(next_j <= sleep_to_next) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;task_stacktrace(JAVA_SAMPLE, &_last_thread_java);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;last_java_ms =&nbsp;get_monotonic_ms();
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;//采样原生方法
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(next_n <= sleep_to_next) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;task_stacktrace(NATIVE_SAMPLE, &_last_thread_native);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;last_native_ms =&nbsp;get_monotonic_ms();
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
}

采样原生方法和 java 方法的代码是一样的,都是调用&nbsp;task_stacktrace&nbsp;方法,这个方法的实现:

static&nbsp;const&nbsp;uint MAX_NR_OF_JAVA_SAMPLES =&nbsp;5;
static&nbsp;const&nbsp;uint MAX_NR_OF_NATIVE_SAMPLES =&nbsp;1;

void&nbsp;JfrThreadSampler::task_stacktrace(JfrSampleType type, JavaThread** last_thread)&nbsp;{
&nbsp;&nbsp;ResourceMark rm;
&nbsp;&nbsp;//对于 java 方法采样,会采样 MAX_NR_OF_JAVA_SAMPLES 即 5 个线程的 java 方法
&nbsp;&nbsp;EventExecutionSample samples[MAX_NR_OF_JAVA_SAMPLES];
&nbsp;&nbsp;//对于原生方法采样,会采样 MAX_NR_OF_NATIVE_SAMPLES 即 1 个线程的原生方法
&nbsp;&nbsp;EventNativeMethodSample samples_native[MAX_NR_OF_NATIVE_SAMPLES];
&nbsp;&nbsp;JfrThreadSampleClosure&nbsp;sample_task(samples, samples_native);

&nbsp;&nbsp;const&nbsp;uint sample_limit = JAVA_SAMPLE == type ? MAX_NR_OF_JAVA_SAMPLES : MAX_NR_OF_NATIVE_SAMPLES;
&nbsp;&nbsp;uint num_samples =&nbsp;0;
&nbsp;&nbsp;JavaThread* start =&nbsp;nullptr;
&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;elapsedTimer sample_time;
&nbsp;&nbsp;&nbsp;&nbsp;sample_time.start();
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//获取所有线程列表
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MutexLocker&nbsp;tlock(Threads_lock);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ThreadsListHandle tlh;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JavaThread* current = _cur_index !=&nbsp;-1&nbsp;? *last_thread :&nbsp;nullptr;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;JfrBuffer* enqueue_buffer =&nbsp;get_enqueue_buffer();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert(enqueue_buffer !=&nbsp;nullptr,&nbsp;"invariant");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//然后,遍历线程,收集采样数据,直到达到前面提到的 MAX_NR_OF_JAVA_SAMPLES 或 MAX_NR_OF_NATIVE_SAMPLES
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(num_samples < sample_limit) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current =&nbsp;next_thread(tlh.list(), start, current);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(current ==&nbsp;nullptr) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(start ==&nbsp;nullptr) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;start = current;&nbsp;&nbsp;// remember the thread where we started to attempt sampling
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(current->is_Compiler_thread()) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert(enqueue_buffer->free_size() >= _min_size,&nbsp;"invariant");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//判断线程状态是否是符合采样的,并采样
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(sample_task.do_sample_thread(current, _frames, _max_frames, type)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;num_samples++;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;enqueue_buffer =&nbsp;renew_if_full(enqueue_buffer);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*last_thread = current;&nbsp;&nbsp;// remember the thread we last attempted to sample
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;sample_time.stop();
&nbsp;&nbsp;&nbsp;&nbsp;log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sample_time.seconds(), sample_task.java_entries(), sample_task.native_entries());
&nbsp;&nbsp;}
&nbsp;&nbsp;if&nbsp;(num_samples >&nbsp;0) {
&nbsp;&nbsp;&nbsp;&nbsp;sample_task.commit_events(type);
&nbsp;&nbsp;}
}

如何判断线程是否符合采样并采样的呢?这个是在&nbsp;sample_task.do_sample_thread&nbsp;方法中判断的,这个方法的实现:

bool&nbsp;JfrThreadSampleClosure::do_sample_thread(JavaThread* thread, JfrStackFrame* frames, u4 max_frames, JfrSampleType type)&nbsp;{
&nbsp;&nbsp;assert(Threads_lock->owned_by_self(),&nbsp;"Holding the thread table lock.");
&nbsp;&nbsp;//判断线程是否是被排除的,一般 VM 线程是被排除的
&nbsp;&nbsp;if&nbsp;(is_excluded(thread)) {
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;
&nbsp;&nbsp;}

&nbsp;&nbsp;bool&nbsp;ret =&nbsp;false;
&nbsp;&nbsp;//设置线程的 trace flag
&nbsp;&nbsp;thread->set_trace_flag();&nbsp;&nbsp;
&nbsp;&nbsp;//保证线程 trace flag 可见性,仅针对 UseSystemMemoryBarrier 为 true 的情况,默认是 false
&nbsp;&nbsp;if&nbsp;(UseSystemMemoryBarrier) {
&nbsp;&nbsp;&nbsp;&nbsp;SystemMemoryBarrier::emit();
&nbsp;&nbsp;}
&nbsp;&nbsp;
&nbsp;&nbsp;if&nbsp;(JAVA_SAMPLE == type) {
&nbsp;&nbsp;&nbsp;&nbsp;//判断线程是否是处于 RUNNABLE 或者 RUNNING 并且是在运行 java 代码的状态
&nbsp;&nbsp;&nbsp;&nbsp;//如果是,则采样
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(thread_state_in_java(thread)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret =&nbsp;sample_thread_in_java(thread, frames, max_frames);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}&nbsp;else&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;assert(NATIVE_SAMPLE == type,&nbsp;"invariant");
&nbsp;&nbsp;&nbsp;&nbsp;//判断线程是否是处于 RUNNABLE 或者 RUNNING 并且是在运行原生代码的状态
&nbsp;&nbsp;&nbsp;&nbsp;//如果是,则采样
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(thread_state_in_native(thread)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret =&nbsp;sample_thread_in_native(thread, frames, max_frames);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;&nbsp;clear_transition_block(thread);
&nbsp;&nbsp;return&nbsp;ret;
}

总结看来,JFR 采样的原理就是:

  1. 一个固定的线程,不断的等待采样间隔到达,然后对应采样
  2. 采样的时候,遍历所有线程,判断线程是否符合采样条件,符合则采样
  3. 采样的时候,对于 java 方法采样,会采样最多 5 个线程的 java 方法,对于原生方法采样,会采样最多 1 个线程的原生方法
  4. 采样的时候,判断线程是否符合采样条件,主要是判断线程是否是处于 RUNNABLE 或者 RUNNING 并且是在运行 java 代码或者原生代码的状态

与 async-profiler 的应用场景对比

这两个 JFR 时间一般用于构建 JFR 火焰图,我之前定位代码高 CPU 消耗瓶颈很多是通过这个定位,有一个例子是:https://juejin.cn/post/7325623087209742374

其中这个火焰图:

40296ff516636603ad761cdd4d785723.jpeg

就是 JFR 的&nbsp;jdk.ExecutionSample&nbsp;和&nbsp;jdk.NativeMethodSample&nbsp;事件结合了&nbsp;jdk.ContainerCPUUsage&nbsp;和&nbsp;jdk.ThreadCPULoad&nbsp;事件构建的火焰图。

async profiler 的采样方式,和 JFR 的不同。JFR 的是尽量保持低消耗,但是对于 Java 方法一次采样对于运行 Java 代码的最多 5 个线程,对于 Native 的最多 1 个,但是全局基本不加锁,也不加安全点导致全局暂停,所以消耗很低,并且一般足以定位高 CPU 消耗瓶颈问题(参考上面我发的定位一个实际问题的链接)。async profiler 的采样方式,对于原生方法更详细,对于 Java 方法一般需要 JVM 启动的时候打开&nbsp;-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints,否则只能采集到 Java 安全点时候的方法。因为默认 JVM 为了提高性能,只在安全点的时候添加 Debug 信息用于定位问题带上方法调用信息,加上前面的&nbsp;-XX:+DebugNonSafepoints&nbsp;会去掉限制,在所有位置加上 Debug 信息以及日志记录,这样 async profiler 才能采集到详细的 Java 方法调用信息。所以整体上 async profiler 的采样方式更详细,但是消耗也更大。

建议是,长期开着 JFR,遇到问题优先回溯 JFR,如果 JFR 无法定位问题,再使用 async profiler。

个人简介:个人业余研究了 AI LLM 微调与 RAG,目前成果是微调了三个模型:

  1. 一个模型是基于 whisper 模型的微调,使用我原来做的精翻的视频按照语句段落切分的片段,并尝试按照方言类别,以及技术类别分别尝试微调的成果。用于视频字幕识别。
  2. 一个模型是基于 Mistral Large 的模型的微调,识别提取视频课件的片段,辅以实际的课件文字进行识别微调。用于识别课件的片段。
  3. 最后一个模型是基于 Claude 3 的模型微调,使用我之前制作的翻译字幕,与 AWS、Go 社区、CNCF 生态里面的官方英文文档以及中文文档作为语料,按照内容段交叉拆分,进行微调,用于字幕翻译。

目前,准确率已经非常高了。大家如果有想要我制作的视频,欢迎关注留言。

本人也是开源代码爱好者,贡献过很多项目的源码(Mycat 和 Java JFRUnit 的核心贡献者,贡献过 OpenJDK,Spring,Spring Cloud,Apache Bookkeeper,Apache RocketMQ,Ribbon,Lettuce、 SocketIO、Longchain4j 等项目 ),同时也是深度技术迷,编写过很多硬核的原理分析系列(JVM)。本人也有一个 Java 技术交流群,感兴趣的欢迎关注。

另外,一如即往的是,全网的所有收益,都会捐赠给希望工程,坚持靠爱与兴趣发电。

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

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

相关文章

睿尔曼复合机器人之底盘操作流程

以操作流程为例&#xff0c;介绍底盘的操作流程。 开机&#xff1a;长按电源按钮&#xff0c;蜂鸣器短响两声&#xff0c;当第三声变长鸣后松开&#xff0c;等待机器开机。 使用&#xff1a; 建立通讯&#xff1a;主要采用无线WiFi与底盘进行通讯连接 无线连接方式&#xff…

副业天花板流量卡推广,小白也可轻松操作

在如今的互联网时代&#xff0c;手机已经不仅仅是一款工具&#xff0c;更像是我们生活中的一部分&#xff0c;那么手机卡也是必需品&#xff0c;但存在的问题就是:很多手机卡的月租很贵&#xff0c;流量也不够用。所以大家都在寻找一个月租低&#xff0c;流量多的卡&#xff0c…

计算机网络—HTTPS协议详解:工作原理、安全性及应用实践

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;ヒューマノイド—ずっと真夜中でいいのに。 1:03━━━━━━️&#x1f49f;──────── 5:06 &#x1f504; ◀️ ⏸…

NC251500 coin

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目背景 假如我那时握住的不是硬币&#xff0c;而是 ... 题意简述 Rikka 和 Yuuta 在玩游戏&#xff0c;每一次他们会抛一枚硬币&#xff0c;正面向上的概率是 p&#xff0c;反面向上的概率是…

C++设计模式:享元模式(十一)

1、定义与动机 概述&#xff1a;享元模式和单例模式一样&#xff0c;都是为了解决程序的性能问题。面向对象很好地解决了"抽象"的问题&#xff0c;但是必不可免得要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大豆可以忽略不计。但是某些情况&#…

NAPI 类对象导出及其生命周期管理(下)

4. 样例工程源码剖析 工程的模板是Native C,模型是Stage。源码剖析主要围绕以下几个文件 4.1. NAPI导出对象和生命周期管理具体实现 4.1.1. 定义NapiTest类及方法 Napi.h文件内容如下&#xff1a; #ifndef __NAPI_TEST_H__ #define __NAPI_TEST_H__#include "napi/nat…

数据集学习

1&#xff0c;CIFAR-10数据集 CIFAR-10数据集由10个类的60000个32x32彩色图像组成&#xff0c;每个类有6000个图像。有50000个训练图像和10000个测试图像。 数据集分为五个训练批次和一个测试批次&#xff0c;每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机…

【科技】2024最新微信机器人一键部署教程

外话 话说上次写文章好像又过了几个月了…… 其实还是因为马上小升初的各种密考&#xff0c;其它地方不知道&#xff0c;反正广东这块名校基本上都得密考考进去 笔者连考几次都惨不忍睹…… 不过5月份会有一个信息技术特长生招生&#xff0c;看看能不能吧~ 正文 先说&#xff…

第四百五十五回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 使用方法 3. 内容总结 我们在上一章回中介绍了"overlay_tooltip用法"相关的内容&#xff0c;本章回中将介绍onBoarding包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的onBo…

流动人员人事档案管理信息系统

流动人员人事档案管理信息系统是一种用于管理流动人员的人事档案的信息系统。该系统可以对流动人员的基本信息、工作经历、学历教育、培训记录、奖惩记录等进行管理和统计。通过该系统&#xff0c;可以方便地查询和维护流动人员的人事档案信息&#xff0c;提高人力资源管理的效…

核心api实操-Activiti7从入门到专家(5)

背景 上一节已经搭建了&#xff0c;具体的开发环境&#xff0c;数据库&#xff0c;并且找了一个可以用bpmnjs流程设计器&#xff0c;这一些&#xff0c;我们对核心api做个基础的实操&#xff0c;有个感性的认知&#xff0c;另外对数据库和基本数据流动有个理解。 部署 模板部…

从零自制docker-9-【管道实现run进程和init进程传参】

文章目录 命令行中输入参数长度过长匿名管道从父进程到子进程传参[]*os.File{}os.NewFile和io.ReadAllexe.LookPathsyscall.Execstrings.Split(msgStr, " ")/bin/ls: cannot access : No such file or directory代码 命令行中输入参数长度过长 用户输入参数过长或包…

CSS基础:border-radius圆角边框的4种写法规则以及网页实战应用的3个场景

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合集 265篇…

SpringBoot 中的日志原来是这么工作的

在有些场景&#xff0c;能通过调整日志的打印策略来提升我们的系统吞吐量,你知道吗&#xff1f; 我们以Springboot集成Log4j2为例&#xff0c;详细说明Springboot框架下Log4j2是如何工作的&#xff0c;你可能会担心&#xff0c;如果是使用Logback日志框架该怎么办呢&#xff1…

01_QT编译报错:Cannot find file:问题解决

QT编译报错&#xff1a;Cannot find file:问题解决 报错原因&#xff1a;创建路径存在中文字符&#xff0c;将文件路径改为英文字符即可

多线程java

多线程的创建 前两种方法无法返回直接结果,而有的线程执行完毕后需要返回结果 方式一:java是通过java.lang.Thread类的对象来代表线程的 启动线程必须调用strat方法,不是调用run方法不要把主线程任务放在启动子线程之前 //1.让子类继承Thread线程类 public class MyThread …

阿里云服务器公网带宽费用全解析(不同计费模式)

阿里云服务器公网带宽怎么收费&#xff1f;北京地域服务器按固定带宽计费一个月23元/M&#xff0c;按使用流量计费0.8元/GB&#xff0c;云服务器地域不同实际带宽价格也不同&#xff0c;阿里云服务器网aliyunfuwuqi.com分享不同带宽计费模式下带宽收费价格表&#xff1a; 公网…

集群监控原理

3.1.2.集群监控原理 Sentinel基于心跳机制监测服务状态&#xff0c;每隔1秒向集群的每个实例发送ping命令&#xff1a; •主观下线&#xff1a;如果某sentinel节点发现某实例未在规定时间响应&#xff0c;则认为该实例主观下线。 •客观下线&#xff1a;若超过指定数量&…

Qt 中的项目文件解析和命名规范

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;QT❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、Qt项目文件解析 1、.pro 文件解析 2、widget.h 文件解析 3、main.cpp 文件解析 4、widget.cpp…

分享2024高校专业建设思路及建设效果

广东泰迪智能科技股份有限公司成立于2013年&#xff0c;是一家专业从事大数据、人工智能等数据智能技术研发、咨询和培训的高科技企业&#xff0c;公司基于十余年的数据智能产业实践经验&#xff0c;构建“产、岗、课、赛、证、文”融通的特色应用型人才培养模式&#xff0c;助…