深入浅出解析 JVM 中的 Safepoint

news2024/10/6 18:33:59

1. 初识 Safepoint-GC 中的 Safepoint

最早接触 JVM 中的安全点概念是在读《深入理解 Java 虚拟机》那本书垃圾回收器章节的内容时。相信大部分人也一样,都是通过这样的方式第一次对安全点有了初步认识。不妨,先复习一下《深入理解 Java 虚拟机》书中安全点那一章节的内容。

书中是在讲解垃圾收集器 - 垃圾收集算法的章节引入安全点的介绍,为了快速准确地完成 GC Roots 枚举,避免为每条指令都生成对应的 OopMap 造成大量存储空间的浪费,只在 “特定的位置” 生成对应的 OopMap,这些位置被称为安全点。然后,书中提到了安全点位置的选择标准是:是否能让程序长时间执行;所以会在方法调用、循环跳转、异常跳转等处才会产生安全点。

书中还提到了 JVM 如何在 GC 时让用户线程在最近的安全点处停顿下来:抢先式中断和主动式中断。抢先式中断不需要线程的执行代码主动去配合,在 GC 发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。而主动式中断的思想是当 GC 需要中断线程时,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时不停地主动去轮询这个标志,一旦发现中断标志为真就自己在最近的安全点上主动中断挂起。现在基本上所有虚拟机实现都采用主动式中断方式来暂停线程响应 GC 事件。

总结一下初识安全点学到的知识点:

  • JVM GC 时需要让用户线程在安全点处停顿下来(Stop The World)
  • JVM 会在方法调用、循环跳转、异常跳转等处放置安全点
  • JVM 通过主动中断方式到达全局 STW:设置一个标志位,各个线程执行过程时不停地主动去轮询这个标志,一旦发现中断标志为真就自己在最近的安全点上主动中断挂起。

以上基本上就是《深入理解 Java 虚拟机》这本书对 JVM 安全点的所有介绍了,当时觉得安全点还是很好理解,认为安全点就是在垃圾回收时为了 STW 而设计的。

后来发现,经过一些线上问题和网上看到有关安全点有趣的示例,发现安全点其实也不简单,不只有 GC 才会用到安全点;简单的代码如果写的不当,安全点也会带来一些莫名其妙的问题;其在 JVM 内部的实现以及 JIT 对它的优化,也经常让人摸不着头脑。本文尝试在初识安全点后已知知识点的基础上,通过一段简单的示例代码,多问几个为什么,来进一步更全面的了解一下安全点。

2. 通过一段示例代码深入剖析 Safepoint

2.1  示例代码

这段示例代码可直接复制到本地运行,本文所有对示例代码的运行环境都是 jdk 1.8。


public static AtomicInteger *counter* = new AtomicInteger(0);

public static void main(String[] args) throws Exception{

long startTime = System.*currentTimeMillis*();

Runnable runnable = () -> {

System.*out*.println(*interval*(startTime) + "ms后," + Thread.*currentThread*().getName() + "子线程开始运行");

for(int i = 0; i < 100000000; i++) {

*counter*.getAndAdd(1);

}

System.*out*.println(*interval*(startTime) + "ms后," + Thread.*currentThread*().getName() + "子线程结束运行, counter=" + *counter*);

};

Thread t1 = new Thread(runnable, "zz-t1");

Thread t2 = new Thread(runnable, "zz-t2");

t1.start();

t2.start();

System.*out*.println(*interval*(startTime) + "ms后,主线程开始sleep.");

Thread.*sleep*(1000L);

System.*out*.println(*interval*(startTime) + "ms后,主线程结束sleep.");

System.*out*.println(*interval*(startTime) + "ms后,主线程结束,counter:" + *counter*);

}

private static long interval(Long startTime) {

return System.*currentTimeMillis*() - startTime;

}

}


示例代码中主线程启动两个子线程,然后主线程睡眠 1s,通过打印时间来观察主线程和子线程的执行情况。按道理来说这里主线程和两个子线程独立并发,没有任何显性的依赖,主线程的执行是不会受子线程影响的:主线程睡眠结束后会直接结束。但是执行结果却和期望不一样。

执行结果如下方动图展示:

从执行结果看,主线程在启动两个线程后进入睡眠状态,代码中指定睡眠时间为 1s,但是主线程却在 3s 多之后才睡眠结束。是什么导致了主线程睡过头了呢,从结果来看主线程睡觉结束时间和子线程结束时间是一致的。所以,我们有理由怀疑主线程没有按时提前结束应该是被两个子线程阻塞了。

2.2  先给结论

由于 VMThread 的某些操作需要 STW,主线程在 sleep 结束前进入了 JVM 全局安全点,然后主线程要等待其他线程全部进入安全点,所以主线程被长时间没有进入安全点的其他线程给阻塞了。

2.3  验证结论

添加 JVM 打印安全点日志参数 -XX:+PrintSafepointStatistics 后再执行上面的实例代码,结果如下截图:

可以从安全点日志中看到,JVM 想要执行 no vm operation,这个操作需要线程进入安全点,整个期间有 12 个线程,正在运行的线程有两个,需要等待这两个线程进入安全点,等待耗时 2251ms。

加上 -XX:+SafepointTimeout 和 -XX:SafepointTimeoutDelay=2000 参数后执行代码可以进一步看等待哪两个线程进入安全点。 

果然和猜测的一样,没有到达安全点的两个线程正是示例代码中定义的 zz-t1 和 zz-t2 线程。

2.4  为什么

到这里这个示例的执行结果的原因已经有了结论并且得到了验证,基本上已经知其然了。但是如果深入思考一下,初识安全点时学到的知识点还不能解释,所以为了知其所以然,这里提了几个为什么。

(1)为什么会进入安全点

换句话问,是什么触发了进入安全点?

由初识安全点得到的基础知识知道进入安全点需要两个条件:

  • JVM 操作设置了主动中断标志
  • 运行的代码中存在安全点

首先想到的是 GC 触发 JVM 设置主动中断标志,加上 -XX:-PrintGC 再执行示例代码并没有打印 GC 日志,可以排除掉 GC。

既然不是 GC,还是再回到安全点日志上寻找线索吧,发现有个 vmop (虚拟机操作类型):no vm operation  关于 no vm operation,网上有大神通过解析 JVM 源码得到了结论,这里不对 JVM 源码展开做详细解读,直接给结论:

在 JVM 正常运行的时候,如果设置了进入安全点的间隔,就会隔一段时间判断是否有代码缓存要清理,如果有,会进入安全点。这个触发条件不是 VM 操作,所以会将 _vmop_type 设置成 - 1,输出日志的时候打印对应的 「no vm operation」,也就是我们看到的安全点日志。

在 VM 操作为空的情况下,只要满足以下 3 个条件,也是会进入安全点的:

1、VMThread 处于正常运行状态

2、设置了进入安全点的间隔时间

3、SafepointALot 是否为 true 或者是否需要清理

用 Java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal 2>&1 | grep Safepoint 命令查看 JVM 关于安全点的默认参数:

发现 GuaranteedSafepointInterval 默认设置成了 1 秒,每隔 1s 就会尝试进入安全点。

那么,修改 GuaranteedSafepointInterval 参数值,看看是否能阻止进入安全点。

GuaranteedSafepointInterval 参数是 JVM 诊断参数,修改这个参数的值,需要配合 -XX:+UnlockDiagnosticVMOptions 一起使用。

另外不建议在线上对这个参数的值做修改。

  • 关闭定时进入安全点

通过 -XX:GuaranteedSafepointInterval = 0 关闭定时进入安全点,看看代码运行结果是怎么样的

由运行结果可以看出,关闭定时进入安全点后,主线程睡眠 1s 后正常结束,不受其他线程阻塞。从安全点日志看,之前等待进入安全点的两个线程也没有了。

  • 调大定时进入安全点间隔时间

由打印的执行结果可以看到子线程运行时间是 3s 多,如果把进入安全点间隔时间调整为 5s,即在子线程结束之后再尝试进入安全点是不是也能避免等待子线程进入安全点呢?修改参数 -XX:GuaranteedSafepointInterval = 5000 调整安全点间隔时间再次执行结果:

从执行结果可以看出,调大安全点间隔时间和关闭定时进入安全点的效果是一样的,也可以避免等待子线程进入安全点的。

(2)主线程是在哪里进入的安全点

从示例代码在默认 JVM 参数执行结果看,主线程睡眠时间超过了 3s,事实上主线程是在Thread.sleep()方法内部进入安全点。 这里对 JVM 安全点实现的源码简单做一下分析:

Safepoint 实现源代码:Safepoint.cpp

读源码太费劲,看注释吧,所幸从注释中也能找到答案。上面截图的注释说在程序进入 Safepoint 的时候,Java 线程可能正处于的五种不同的状态,针对不同的状态的不同处理机制。假设现在有一个操作触发了某个 VM 线程所有线程需要进入 SafePoint,如果其他线程现在:

  • 运行字节码:运行字节码时,解释器会看线程是否被标记为 poll armed,如果是,VM 线程调用 SafepointSynchronize::block(JavaThread *thread) 进行 block。
  • 运行 native 代码:当运行 native 代码时,VM 线程略过这个线程,但是给这个线程设置 poll armed,让它在执行完 native 代码之后,它会检查是否 poll armed,如果还需要停在 SafePoint,则直接 block。
  • 运行 JIT 编译好的代码:由于运行的是编译好的机器码,直接查看本地 local polling page 是否为脏,如果为脏则需要 block。这个特性是在 Java 10 引入的 JEP 312: Thread-Local Handshakes 之后,才是只用检查本地 local polling page 是否为脏就可以了。
  • 处于 BLOCK 状态:在需要所有线程需要进入 SafePoint 的操作完成之前,不许离开 BLOCK 状态
  • 处于线程切换状态或者处于 VM 运行状态:会一直轮询线程状态直到线程处于阻塞状态(线程肯定会变成上面说的那四种状态,变成哪个都会 block 住)。

再看一下 Thread.sleep 方法的声明,就和上面 Safepoint.cpp 源码注释截图红框对上了,Thread.sleep 正是一个 native 方法。

Thread.sleep (0) 在 RocketMQ 中的妙用

上面这段代码是 RocketMQ 的一段代码,16 年最早版本的实现 for 循环内每循环 1000 次会调用一次 Thread.sleep(0),这貌似是一段无用的代码,作者真实的目的是为了在这里放置一个安全点,避免 for 循环运行时间过长导致系统长时间 SWT。从代码的变更记录看,22 年 9 月份有人对这段代码换了一种写法:把 for 循环变量类型定义成 long 型,同时注释掉了循环内部 Thread.sleep(0) 代码,为什么可以这样写以及为什么要这样写这里先按下不表。

(3)子线程为什么无法进入安全点

现在已经知道了主线程为什么进入会进入安全点,以及主线程在哪里进入的安全点,按照已知知识点 JVM 会在循环跳转处和方法调用处放置安全点,为什么子线程没有进入安全点?

可数循环和不可数循环

JVM 为了避免安全点过多带来过重的负担,对循环有一项优化措施,认为循环次数较少的话,执行时间应该不会太长,所以使用 int 类型和范围更小的数据类型作为索引值的循环默认是不会被放置安全点的。这种循环被称为可数循环,相对应的,使用 long 或者范围更大的数据类型作为索引值的循环就被称为不可数循环,将被放置安全点。

在示例代码中,子线程的循环索引值数据类型是 int,也就是可数循环,所以 JVM 没有在循环跳转处放置安全点。

把循环索引值数据类型改成 long 型,循环成为不可数循环,就能够成功在循环跳转处放置安全点,避免子线程长时间无法进入安全点阻塞主线程。

从上面的执行结果可以看到,把循环索引值数据类型改成 long 型,主线程在睡眠 1s 之后立即结束了睡眠,并没有等待子线程的执行。

到这里,也就知道为什么上面贴的 RocketMQ 大那段代码,把循环索引值数据类型改成 long 型可以替换循环内部 Thread.Sleep(0) 达到放置安全点的目的了。

其实,还可以通过 -XX:+UseCountedLoopSafepoints 参数关闭 JVM 对可数循环放置安全点的优化。下面的执行结果可以看出,添加了 -XX:+UseCountedLoopSafepoints 参数后,也能让运行结果到达预期。

还有一个疑惑

仔细看实例代码,发现子线程循环体内调用了 AtomicInteger 类的 getAndAdd 方法,再深入看 jdk getAndAdd 方法的实现,发现底层是调用了 sun.misc.Unsafe#getIntVolatile 这个方法和 Thread.sleep 方法一样,也是一个 native 方法,为什么这里没有进入像 Thread.sleep 方法一样进入安全点?

是的,好可怕,确实被优化了,被 JIT 给优化了。为了验证是被 JIT 优化了,可以用

-Djava.compiler=NONE 关闭 JIT 然后看一下运行结果。

从运行结果看,关闭了 JIT 优化后,主线程确实在睡眠 1s 后立即结束了,不过子线程运行的时间比 JIT 优化开启时多了不少。所以,JIT 还是能够带来一定的性能优化的,有时也会带来一些奇怪的现象。

3. 更全面的安全点定义

区别于初识安全点的时候局限于 GC 中的安全点概念,这里给安全点一个比较全面的定义:

Safepoint 可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,线程可以暂停。在 SafePoint 保存了其他位置没有的一些当前线程的运行信息,供其他线程读取。这些信息包括:线程上下文的任何信息,例如对象或者非对象的内部指针等等。我们一般这么理解 SafePoint,就是线程只有运行到了 SafePoint 的位置,他的一切状态信息,才是确定的,也只有这个时候,才知道这个线程用了哪些内存,没有用哪些;并且,只有线程处于 SafePoint 位置,这时候对 JVM 的堆栈信息进行修改,例如回收某一部分不用的内存,线程才会感知到,之后继续运行,每个线程都有一份自己的内存使用快照,这时候其他线程对于内存使用的修改,线程就不知道了,只有再进行到 SafePoint 的时候,才会感知。

4. 什么时候会进入 Safepoint

当 VM Thread 需要做 vm  操作时会让线程进入安全点,vm 操作类型有很多,可以参考 VM_OP_ENUM 源码 vmOperations.hpp。下面是几种经常发生的进入 Safepoint 的情形:

(1)GC:由于需要每个线程的对象使用信息,以及回收一些对象,释放某些堆内存或者直接内存,所以需要 进入 Safepoint 来 Stop the world;

(2)定时进入 SafePoint:每经过 -XX:GuaranteedSafepointInterval 配置的时间,都会让所有线程进入 Safepoint,一旦所有线程都进入,立刻从 Safepoint 恢复。这个定时主要是为了一些没必要立刻 Stop the world 的任务执行,可以设置 -XX:GuaranteedSafepointInterval=0 关闭这个定时。

(3)由于 jstack,jmap 和 jstat 等命令,会导致 Stop the world:这种命令都需要采集堆栈信息,所以需要所有线程进入 Safepoint 并暂停。

(4)偏向锁取消:锁大部分情况是没有竞争的(某个同步块大多数情况都不会出现多线程同时竞争锁),所以可以通过偏向来提高性能。即在无竞争时,之前获得锁的线程再次获得锁时,会判断是否偏向锁指向我,那么该线程将不用再次获得锁,直接就可以进入同步块。但是高并发的情况下,偏向锁会经常失效,导致需要取消偏向锁,取消偏向锁的时候,需要 Stop the world,因为要获取每个线程使用锁的状态以及运行状态。

(5)Java Instrument 导致的 Agent 加载以及类的重定义:由于涉及到类重定义,需要修改栈上和这个类相关的信息,所以需要 Stop the world

(6)Java Code Cache 相关:当发生 JIT 编译优化或者去优化,需要 OSR 或者 Bailout 或者清理代码缓存的时候,由于需要读取线程执行的方法以及改变线程执行的方法,所以需要 Stop the world

5. 避免 Safepoint 副作用

Safepoint 在一定程度上是可以理解成是为了让所有用户线程停顿(Stop The World)而设计的。STW 对应用系统来说是一件很可怕的事情,JVM 不论是在 GC 还是在其他的 VM 操作上都在努力避免 STW 和减少 STW 时间。

安全点最主要的副作用就是可能导致 STW 时间过长,应该极力避免这点副作用。

对第一个进入安全点的线程来说,STW 是从它进入安全点开始的,如果有某个线程一直无法进入安全点就会导致进入安全点的时间一直处于等待状态,进而导致 STW 的时间过长。所以,应避免线程执行过长无法进入安全点的情况。

可数循环体内执行时间过长以及 JIT 优化导致无法进入安全点的问题是最常见的无法进入安全点的情况。在写大循环的时候可以把循环索引值数据类型定义成 long。

在高并发应用中,偏向锁并不能带来性能提升,反而因为偏向锁取消带来了很多没必要的某些线程进入安全点 。所以建议关闭:-XX:-UseBiasedLocking

jstack,jmap 和 jstat 等命令,也会导致进入安全点。所以,生产环境应该关闭 Thead dump 的开关,避免 dump 时间过长导致应用 STW 时间过长。

参考文献:

[1] 《深入理解 java 虚拟机》

[2]http://psy-lob-saw.blogspot.com/2015/12/safepoints.html

[3]https://xie.infoq.cn/article/a80542aca7ad53efaaab1a27a

[4]https://zhuanlan.zhihu.com/p/161710652

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

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

相关文章

初识Linux篇:第二篇

初识Linux&#xff1a;第二篇 初识Linux&#xff1a;第二篇1.操作系统2.命令行3.Linux的基本指令3.1.ls指令3.2pwd指令3.3cd指令3.4touch指令3.5mkdir指令3.6.rmdir指令 && rm 指令 4.yum中有趣的程序4.1小火车4.2牛4.3Linux_logo(企鹅)4.4在Linux上打开网页 总结 初识…

【Spring全家桶与Mybatis】Spring环境下整合Mybatis(纯注解方式)

⭐️前面的话⭐️ 本文已经收录到《Spring框架全家桶系列》专栏&#xff0c;本文将介绍在Spring环境下整合mybatis。 &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文…

520快给你喜欢的女生发个表白软件吧!【手把手教学】

文章目录 项目介绍一、创建项目二、设计窗体三、添加事件总结 项目介绍 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 开发工具&#xff1a;Visual Studio 2022 本项目是用C# Winform开发的一个小软件。 实现非常简单&#xff0c;下面看一下这个软件运行…

Vector - CAPL - CANoe硬件CANCANFD参数

如何更改与 CAPL 的 CAN 总线通信的波特率&#xff1f; 解决办法 通常我们常见的配置方法主要有两种方法可以使用 CAPL 更改波特率&#xff0c;使用函数 setBtr或使用 canSetConfiguration、canFdSetConfiguration。 1. setBtr(long channel, byte btr0, byte btr1) setBtr仅…

flink watermark介绍及watermark的窗口触发机制

Flink的三种时间 在谈watermark之前&#xff0c;首先需要了解flink的三种时间概念。在flink中&#xff0c;有三种时间戳概念&#xff1a;Event Time 、Processing Time 和 Ingestion Time。其中watermark只对Event Time类型的时间戳有用。这三种时间概念分别表示&#xff1a; …

[golang gin框架] 30.Gin 商城项目- 购物车商品确认页面以及收货地址的增删改查

一.界面展示 购物车页面 增加功能&#xff1a; 展示用户加入的购物车数据&#xff0c;并点击‘去结算’按钮&#xff0c; 判断是否选中商品 确认订单页面 展示 选中的购物车商品数据(商品标题&#xff0c;图片&#xff0c;数量等)以及 结算的数据(总的价格&#xff0c;总的数量…

【Spring】初识MyBatis (二)

&#xff08;接上一篇【Spring】[初识MyBatis&#xff08;一&#xff09;]&#xff09; 目录 1.2 根据用户名模糊查询用户信息2 添加客户3 更新用户4 删除用户 1.2 根据用户名模糊查询用户信息 【示例6-2】模糊查询的实现只需要在映射文件中通过元素编写相应的SQL语句&#x…

华为手机如何进入开发者模式?连接studio真机调试

对于安卓开发者来说&#xff0c;真机调试是非常好的选择&#xff0c;对电脑配置也没有过分的要求。如果采用Android Studio自带安卓虚拟机调试&#xff0c;真的很慢&#xff0c;一点都不友好。 真机调试的步骤&#xff1a;打开设置->关于手机->版本号&#xff0c;然后连…

并发编程12:AQS

文章目录 12.1 前置知识12.2 AQS入门级别理论知识12.2.1 是什么&#xff1f;12.2.2 AQS为什么是JUC内容中最重要的基石12.2.3 能干嘛&#xff1f;12.2.4 小总结 12.3 AQS源码分析前置知识储备12.3.1 AQS内部体系架构图12.3.2 AQS内部体系架构----AQS自身12.3.1 AQS内部体系架构…

一、H3C-NE实验-抓包实验

实验一&#xff1a;抓包实验&#xff08;PING包&#xff09; 实验拓扑结构图 1. 修改设备名称 步骤1&#xff1a;启动设备 步骤2&#xff1a;在路由器1&#xff0c;进入系统视图&#xff0c;并修改设备名称为R1 步骤3&#xff1a;在路由器2&#xff0c;进入系统视图&#xf…

【Java|基础篇】类和对象

文章目录 1. 前言2. 什么是面向对象3. 类的定义4. 类的实例化5. 对象的构造及初始化6. this引用7. 总结 1. 前言 本篇文章主要讲解了下面三个问题 类的定义和实例化构造方法this关键字 2. 什么是面向对象 众所周知面向过程和面向对象是两种重要的编程思想,而Java是属于面向…

C语言函数大全-- v 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- v 开头的函数 1. va_start 1.1 函数说明 函数声明函数功能void va_start(va_list ap, last_arg);用于初始化一个 va_list 类型的变量&#xff0c;使其指向可变参数列表中的第一个参数 参数&#xff1a; ap&#xff1a; 一个指向 va_…

我的创作纪念日(个人感悟)

昨天2023年5月10日是我成为创作者的第128天纪念日&#xff0c;感谢CSDN官方的纪念信让我铭记这特殊的一天。 机缘 要说与CSDN的初次相遇&#xff0c;还是2022年的高考结束完的暑假&#xff0c;当时对于大学的学习没有什么概念&#xff0c;当初的高考志愿报的有计算机相关的专…

spring集成mybatis的原理

spring是怎样和mybatis继承的&#xff1f; 在idea里点mapper.queryOne()直接跳到了接口或xml&#xff0c;它究竟是怎样利用jdbc执行的&#xff1f; 我直接调用mapper.queryOne是怎么使用的sqlsession&#xff1f;怎么去connect的&#xff1f; mybatis是怎样根据mapper找到对应的…

【Java面试】Java并发基础(1)

文章目录 1. 可见性&#xff0c;有序性&#xff0c;原子性2. java中并发和并行3. 通常线程有哪几种使用方式? 1. 可见性&#xff0c;有序性&#xff0c;原子性 可见性&#xff08;Visibility&#xff09;&#xff1a; 指一个线程修改了共享变量的值之后&#xff0c;其他线程能…

学习网络通信必备的DNS解析和Socket通信知识

OkHttp是一个开源的网络请求框架&#xff0c;由Square公司开发。它通过封装Java底层的HttpURLConnection和Okio等库&#xff0c;提供一个简单易用的API&#xff0c;让开发人员能够方便地向服务器发送HTTP/HTTPS请求&#xff0c;支持异步请求和响应回调&#xff0c;并提供丰富的…

未来已来, 新能源与IT的技术碰撞;学习Android车载开发的必然趋势

Android工程师前景 Android车载工程师扮演着关键的角色&#xff0c;他们致力于将最新的Android技术和汽车技术相结合&#xff0c;为汽车行业提供优质的产品。随着越来越多的汽车制造商投资于智能汽车技术和车联网技术&#xff0c;Android车载工程师成为了一个越来越重要的职业…

MATLAB实现二维稳态导热

MATLAB实现二维稳态导热 一、理论基础二、代码实现 一、理论基础 步骤&#xff1a; Step.1 二维模型传热控制微分方程的确定&#xff0c;具体推导可以在任何一本传热学的书中找到。 d 2 T d x 2 d 2 T d y 2 0 \frac{d^{2}T}{dx^{2}}\frac{d^{2}T}{dy^{2}}0 dx2d2T​dy2d2T…

Ansible 自动化运维工具(一)——部署以及命令行模块

文章目录 一、 ansible 的概述1、ansible简介2.、官方网站3、ansible 的特点4、ansible的工作机制5、ansible的组成模块 二、ansible部署1、Asible的安装 三、ansible 命令行模块1、command 模块2、shell 模块3、cron 模块4、user 模块5、group 模块6、copy 模块7、file 模块8、…

【ros/ros2】ros1和ros2的区别-要点记录

dds data distribution service&#xff0c;数据分发服务 rcl ros client libraries&#xff0c;ros客户端库文件 rmw ros middle ware interface&#xff0c;ros中间件接口 lcn life cycle node&#xff0c;生命周期节点&#xff0c;受控节点 lmn lifecycle manageme…