Java21虚拟线程:我的锁去哪儿了?

news2024/11/28 20:39:27

0 前言

最近的文章中,我们详细介绍了当我们迁移到 Java 21 并将代际 ZGC 作为默认垃圾收集器时,我们的工作负载是如何受益的。虚拟线程是我们在这次迁移中兴奋采用的另一个特性。

对虚拟线程新手,它们被描述为“轻量级线程,大大减少编写、维护和观察高吞吐量并发应用程序的工作量。”威力来自在阻塞操作发生时,能够通过延续自动挂起和恢复,从而释放底层操作系统线程以供其他操作重用。在适当的上下文中利用虚拟线程可以解锁更高的性能。

本文讨论了在 Java 21 上部署虚拟线程的过程中遇到的一个特殊情况。

1 问题

工程师向性能工程和 JVM 生态系统团队提出了几份关于间歇性超时和挂起实例的独立报告。仔细检查后,我们注意到一组共同的特征和症状。在所有受影响的应用程序中,它们都在 Java 21 上运行,使用 SpringBoot 3 和嵌入式 Tomcat 在 REST 端点上提供流量。经历问题的实例甚至在这些实例上的 JVM 仍然运行时就停止了流量服务。一个清晰的、标志着这个问题开始的症状是 closeWait 状态的套接字数量持续增加:

  • Tomcat服务器的吞吐量在某个时间点突然下降到几乎为0,表明服务器可能停止处理新的请求
  • 同时,处于closeWait状态的套接字数量持续增加,这通常表示网络连接没有被正确关闭
  • 这两个指标的变化是相关的,可能表明存在严重的网络或应用程序问题,导致连接无法正常关闭,并最终影响了服务器的处理能力

2 收集的诊断

停留在 closeWait 状态的套接字表明远程对等方关闭了套接字,但本地实例从未关闭它,大概是因为应用程序未能这样做。这通常表明应用程序处于异常状态,这种情况下应用程序线程转储可能会揭示额外的洞察。

为解决这问题,我们首先利用警报系统捕获处于这种状态的实例。由于我们定期收集并持久化所有 JVM 工作负载的线程转储,我们通常可通过检查这些来自实例的线程转储来追溯行为。然而,惊讶发现我们所有的线程转储都显示一个完全空闲的 JVM,无任何明确的活动。

回顾最近变化,我们发现这些受影响的服务启用了虚拟线程,我们知道虚拟线程的调用栈不会出现在 jstack 生成的线程转储中。为了获得包含虚拟线程状态的更完整的线程转储,使用 “jcmd Thread.dump_to_file” 命令。作为最后的手段,我们还从实例中收集了一个堆转储。

3 分析

线程转储揭示了数千个“空白”虚拟线程:

#119821 "" virtual

#119820 "" virtual

#119823 "" virtual

#120847 "" virtual

#119822 "" virtual
...

这些是 VT(虚拟线程),其中创建了线程对象,但尚未开始运行,因此没有堆栈跟踪。事实上,空白 VT 的数量与 closeWait 状态的套接字数量大致相同。为了理解我们所看到的,我们首先需要了解 VT 的工作原理。

虚拟线程不是 1:1 映射到专用的 OS 级线程。相反,可将其视为计划到 fork-join 线程池中的任务。当虚拟线程进入阻塞调用时,如等待 Future,它会放弃它占据的 OS 线程,并简单地保留在内存中,直到它准备恢复。与此同时,OS 线程可以被重新分配以执行同一 fork-join 池中的其他 VT。这允许我们将许多 VT 多路复用到仅有的几个底层 OS 线程上。JVM 术语中,底层 OS 线程被称为“载体线程”,虚拟线程可“安装”在执行时和“卸载”在等待时。

虚拟线程的优秀深入描述可以在JEP 444。

在我们的环境下,对 Tomcat 使用了阻塞模型,实际上在请求的生命周期内保留了一个工作线程。通过启用虚拟线程,Tomcat 切换到虚拟执行。每个传入的请求都会创建一个新的虚拟线程,该线程简单地被计划在 Virtual Thread Executor 上作为一个任务。可见 Tomcat 在 这里 创建了 VirtualThreadExecutor

将这些信息联系回我们的问题,症状对应于 Tomcat 不断为每个传入的请求创建一个新的 web 工作 VT,但是没有可用的 OS 线程将它们安装上去的状态。

4 Tomcat 为啥卡住了?

OS 线程咋了,它们在忙啥?正如 这里 描述,如虚拟线程在 synchronized 块或方法内执行阻塞操作,它将被固定到底层 OS 线程。这正是这里发生的情况。这是从卡住的实例获得的线程转储中的一个相关片段:

#119515 "" virtual
      java.base/jdk.internal.misc.Unsafe.park(Native Method)
      java.base/java.lang.VirtualThread.parkOnCarrierThread(VirtualThread.java:661)
      java.base/java.lang.VirtualThread.park(VirtualThread.java:593)
      java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
      java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
      java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
      java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
      java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
      zipkin2.reporter.internal.CountBoundedQueue.offer(CountBoundedQueue.java:54)
      zipkin2.reporter.internal.AsyncReporter$BoundedAsyncReporter.report(AsyncReporter.java:230)
      zipkin2.reporter.brave.AsyncZipkinSpanHandler.end(AsyncZipkinSpanHandler.java:214)
      brave.internal.handler.NoopAwareSpanHandler$CompositeSpanHandler.end(NoopAwareSpanHandler.java:98)
      brave.internal.handler.NoopAwareSpanHandler.end(NoopAwareSpanHandler.java:48)
      brave.internal.recorder.PendingSpans.finish(PendingSpans.java:116)
      brave.RealSpan.finish(RealSpan.java:134)
      brave.RealSpan.finish(RealSpan.java:129)
      io.micrometer.tracing.brave.bridge.BraveSpan.end(BraveSpan.java:117)
      io.micrometer.tracing.annotation.AbstractMethodInvocationProcessor.after(AbstractMethodInvocationProcessor.java:67)
      io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor.proceedUnderSynchronousSpan(ImperativeMethodInvocationProcessor.java:98)
      io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor.process(ImperativeMethodInvocationProcessor.java:73)
      io.micrometer.tracing.annotation.SpanAspect.newSpanMethod(SpanAspect.java:59)
      java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
      java.base/java.lang.reflect.Method.invoke(Method.java:580)
      org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
...

这堆栈跟踪中,进入了 brave.RealSpan.finish(RealSpan.java:134) 的同步。这个虚拟线程实际上被固定了 - 它被安装在一个实际的 OS 线程上,即使在等待获取可重入锁时也是如此。有 3 个 VT 在这种确切状态,另一个 VT 被识别为 “<redacted> @DefaultExecutor - 46542”,它也遵循相同的代码路径。这 4 个虚拟线程在等待获取锁时被固定。由于应用程序部署在具有 4 个 vCPU 的实例上,支撑 VT 执行的 fork-join 池 也包含 4 个 OS 线程。现在我们已经用尽了它们,没有其他虚拟线程可以取得任何进展。这解释了:

  • 为啥 Tomcat 停止处理请求
  • 为啥 closeWait 态的套接字数量不断攀升

事实上,Tomcat 在套接字上接受连接,创建请求以及与之相关的虚拟线程,并将此请求/线程传递给执行器进行处理。然而,新创建的 VT 无法被调度,因为 fork-join 池中的所有 OS 线程都被固定并且从未释放。因此,这些新创建的 VT 被困在队列中,同时仍然持有套接字。

5 谁拥有锁?

现在我们知道 VT 正在等待获取锁,下一个问题是谁拥有锁?回答这个问题是理解最初触发这个条件的关键。通常,线程转储通过 “- locked <0x…> (at …)” 或 “Locked ownable synchronizers” 指示谁拥有锁,但我们的线程转储中没有出现这些。事实上,jcmd 生成的线程转储中没有包含锁定/停车/等待信息。这是 Java 21 的一个限制,并将在未来版本中得到解决。仔细梳理线程转储,我们发现总共有 6 个线程竞争同一个 ReentrantLock 和相关的 Condition。这六个线程中的四个在前一节中详细说明。这是另一个线程:

#119516 "" virtual
      java.base/java.lang.VirtualThread.park(VirtualThread.java:582)
      java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
      java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
      java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:990)
      java.base/java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:153)
      java.base/java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
      zipkin2.reporter.internal.CountBoundedQueue.offer(CountBoundedQueue.java:54)
      zipkin2.reporter.internal.AsyncReporter$BoundedAsyncReporter.report(AsyncReporter.java:230)
      zipkin2.reporter.brave.AsyncZipkinSpanHandler.end(AsyncZipkinSpanHandler.java:214)
      brave.internal.handler.NoopAwareSpanHandler$CompositeSpanHandler.end(NoopAwareSpanHandler.java:98)
      brave.internal.handler.NoopAwareSpanHandler.end(NoopAwareSpanHandler.java:48)
      brave.internal.recorder.PendingSpans.finish(PendingSpans.java:116)
      brave.RealScopedSpan.finish(RealScopedSpan.java:64)
      ...

请注意,虽然这个线程似乎经历了完成跨度的相同代码路径,但它没有经过 synchronized 块。最后是第 6 个线程:

#107 "AsyncReporter <redacted>"
      java.base/jdk.internal.misc.Unsafe.park(Native Method)
      java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:221)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:754)
      java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1761)
      zipkin2.reporter.internal.CountBoundedQueue.drainTo(CountBoundedQueue.java:81)
      zipkin2.reporter.internal.AsyncReporter$BoundedAsyncReporter.flush(AsyncReporter.java:241)
      zipkin2.reporter.internal.AsyncReporter$Flusher.run(AsyncReporter.java:352)
      java.base/java.lang.Thread.run(Thread.java:1583)

这实际上是一个普通的平台线程,而不是虚拟线程。特别注意这个堆栈跟踪中的行号,这个线程似乎在内部 acquire() 方法 之后 阻塞了 完成等待。换句话说,这个调用线程在进入 awaitNanos() 时拥有锁。我们知道锁是在这里明确获取的 here。然而,当等待完成时,它无法重新获取锁。总结我们的线程转储分析:

Thread ID/nameVirtual?“synchronized” block?Pinned?Waiting for the lock?
#119513 ""YesYesYesYes
#119514 ""YesYesYesYes
#119515 ""YesYesYesYes
#119517 "<redacted> @DefaultExecutor - 46542"YesYesYesYes
#119516 ""YesNoNoYes
#107 "AsyncReporter " NoNoN/AYes

在分析了锁的争夺情况后,我们发现有5个虚拟线程和1个常规线程正在等待锁。其中4个虚拟线程被固定在了fork-join池的OS线程上。尽管如此,我们仍然没有关于谁拥有该锁的信息。由于无法从线程转储中获取更多信息,我们合乎逻辑的下一步是查看堆转储并内省锁的状态。

6 检查锁

在堆转储中找到锁是相对直接的。使用出色的Eclipse MAT工具,我们检查了AsyncReporter非虚拟线程的堆栈上的对象,以识别锁对象。推理锁的当前状态可能是我们调查中最棘手的部分。大多数相关代码都可以在AbstractQueuedSynchronizer.java中找到。虽然我们不声称完全理解其内部工作原理,但我们逆向工程了足够的信息以匹配我们在堆转储中看到的内容。下面的图表说明了我们的发现:

首先,exclusiveOwnerThread字段为null(2),表示没有人拥有该锁。我们在列表的头部有一个“空”的ExclusiveNode(3)(waiternullstatus已清除),后面是另一个ExclusiveNode,其waiter指向争夺锁的虚拟线程之一——#119516(4)。我们发现唯一清除exclusiveOwnerThread字段的地方是在ReentrantLock.Sync.tryRelease()方法中(源代码链接)。在那里,我们还设置了state = 0,与我们在堆转储中看到的状态相匹配(1)。

考虑到这一点,我们追溯了释放锁的代码路径。在成功调用tryRelease()之后,持有锁的线程尝试向列表中的下一个等待者发出信号。此时,即使锁的所有权实际上已经释放,持有锁的线程仍然在列表的头部。列表中的下一个节点指向即将获取锁的线程。

为了理解这种信号是如何工作的,让我们看看AbstractQueuedSynchronizer.acquire()方法中的锁定获取路径。极度简化地说,它是一个无限循环,线程尝试获取锁,如果尝试不成功,则停车:

while(true) {
   if (tryAcquire()) {
      return; // 锁已获取
   }
   park();
}

当持有锁的线程释放锁并发出信号以取消阻塞下一个等待线程时,被取消阻塞的线程将再次遍历这个循环,给它又一次获取锁的机会。事实上,我们的线程转储表明我们所有的等待线程都停在了第754行。一旦被取消阻塞,成功获取锁的线程最终将进入这段代码块中,有效地重置列表的头部并清除对等待者的引用。

更简洁地重新陈述,拥有锁的线程被列表的头部节点引用。释放锁会通知列表中的下一个节点,而获取锁会将列表的头部重置为当前节点。这意味着我们在堆转储中看到的内容反映了一个线程已经释放了锁但下一个线程尚未获取它的状态。这是一个本应是瞬态的奇怪中间状态,但我们的JVM却卡在了这里。我们知道线程#119516已经被通知并且即将获取锁,因为我们在列表头部识别出的ExclusiveNode状态。然而,线程转储显示线程#119516继续等待,就像其他争夺相同锁的线程一样。我们如何调和线程和堆转储之间所看到的情况?

7 无处运行的锁

知道线程#119516实际上已经被通知,我们回到线程转储中重新检查线程的状态。回想一下,我们总共有6个线程在等待锁,其中4个虚拟线程每个都被固定在OS线程上。这4个线程在获取锁并退出同步块之前不会放弃它们的OS线程。#107 "AsyncReporter <redacted>"是一个常规平台线程,所以如果它获取了锁,没有什么可以阻止它继续进行。这让我们剩下最后一个线程:#119516。它是一个VT,但它没有被固定在OS线程上。即使它被通知取消停车,它也无法继续,因为fork-join池中没有更多的OS线程可以调度它。正是这里发生的情况——尽管#119516被信号取消停车,它不能离开停车状态,因为fork-join池被其他4个等待获取相同锁的VT占据。那些被固定的VT在获取锁之前都不能继续。这是经典死锁问题的变体,但我们有一个锁和一个由fork-join池所代表的4个许可的信号量。

现在我们知道确切发生了什么,很容易想出一个可重现的测试用例。

8 结论

虚拟线程预计将通过减少与线程创建和上下文切换相关的开销来提高性能。尽管Java 21还有一些尖锐的边缘,虚拟线程在很大程度上实现了它们的承诺。在我们寻求更高性能的Java应用程序的过程中,我们看到进一步采用虚拟线程是实现该目标的关键。我们期待Java 23及以后,它带来了大量的升级,希望解决虚拟线程和锁定原语之间的集成问题。

这次探索仅突出了性能工程师解决的问题类型之一。我们希望这种对我们解决问题方法的一瞥对其他人在未来的调查中具有价值。

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化

  • 活动&券等营销中台建设

  • 交易平台及数据中台等架构和开发设计

  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化

  • LLM Agent应用开发

  • 区块链应用开发

  • 大数据开发挖掘经验

  • 推荐系统项目

    目前主攻市级软件项目设计、构建服务全社会的应用系统。

参考:

  • 编程严选网

    本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

word建立目录以及修改页码

1、为word建立新的目录 &#xff08;1&#xff09;选中word中的标题设置为第几级标题&#xff0c;将所有的标题均设置完成。最后可以鼠标右击标题&#xff0c;对不同的标题字体大小等进行设置。右击-->修改-->格式-->段落 &#xff08;2&#xff09;在word中插入新的…

springboot039基于Web足球青训俱乐部管理后台系统开发(论文+源码)_kaic

毕业设计(论文) 基于Web的足球青训俱乐部管理后台系统的设计与开发 学生姓名 XXX 学 号 XXXXXXXX 分院名称 XXXXXXXX 专业班级 XXXXX 指导教师 XXXX …

12.个人博客系统(Java项目基于spring和vue)

目录 1.系统的受众说明 2.相关技术介绍 2.1 B/S 简介 2.2 JAVA 简介 2.3 vue简介 2.4 SSM和Springboot简介 3.可行性分析 3.1 技术可行性分析 3.2 经济可行性分析 3.3 操作可行性 4.系统设计 4.1 系统总流程 4.2 博主用例 4.3 游客用例 4.4 系统类 4.…

llm 论文淘金,大模型精选论文解读,让你从大模型零基础到进阶

技术报告 没啥说的&#xff0c;当下最最最有含金量的论文&#xff0c;值得反复阅读。重点留意&#xff1a;数据清洗方法、pretrain 数据配比、pretrain 超参数、退火阶段、sft 的 task 种类、sft 的数据量级、dpo / ppo 训练技巧&#xff0c;合成数据方法等。 我个人觉着&…

Java项目实战II基于Spring Boot的毕业就业信息管理系统设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着高校扩…

014_django基于大数据运城市二手房价数据可视化系统的设计与实现2024_3ahrxq75

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

【C++】编码最全详解

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f525; 所属专栏&#xff1a;C深入学习笔记 &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 一、什么编码&#xff1f; 编码&am…

DDR Study - LPDDR Initial

参考来源&#xff1a;JESD209-4B 在之前的DDR Study - Basic Understanding中介绍了DDR的基础概念&#xff0c;从这篇文章开始&#xff0c;会基于LPDDR4依次按照如下顺序对LPDDR内容进行简单分析&#xff1a; LPDDR Initial → LPDDR Write Leveling and DQ Training → LPDDR …

【Jenkins】windows安装步骤

【Jenkins】windows安装步骤 官网使用WAR包方式运行浏览器访问Jenkinswindows-installer安装安装过程问题解决This account either does not hava the privilege to logon as a service or the account was unable to be verified 安装成功修改jenkins.xml启动jenkins访问jenki…

如何测试IP速度?

了解代理的连接速度是否快速是确保网络使用效率和体验的关键因素之一。本文来为大家如何有效地评估和测试代理IP的连接速度&#xff0c;以及一些实用的方法和工具&#xff0c;帮助用户做出明智的选择和决策。 一、如何评估代理IP的连接速度 1. 使用在线速度测试工具 为了快速…

阿里云云盘在卸载时关联到PHP进程,如何在不影响PHP进程情况下卸载磁盘

1.问题&#xff1a; 在使用umount /dev/vdc1 卸载磁盘时&#xff0c;提示如下&#xff0c;导致无法在Linux系统下卸载磁盘 umount /dev/vdc1 umount: /var/www/html/*/eshop/IFile3: target is busy.(In some cases useful info about processes that usethe device is found…

鸿蒙Next设备上的ProxyMan、Charles网络抓包配置教程

一、Proxyman配置 1. 导出证书 ProxyMan菜单栏依次点击 证书—>导出—>根证书为PEM 然后保存.pem文件传送(如hdc命令<下文会有介绍>)至鸿蒙Next设备存储任意位置 2. 安装证书 系统设置搜索“证书”&#xff0c;结果列表中点击“证书与凭据” 点击“从存储设备…

AI周报(10.13-10.19)

AI应用-清华校友用AI破解162个高数定理 加州理工、斯坦福和威大的研究人员提出了LeanAgent——一个终身学习&#xff0c;并能证明定理的AI智能体。LeanAgent会根据数学难度优化的学习轨迹课程&#xff0c;来提高学习策略。并且&#xff0c;它还有一个动态数据库&#xff0c;有效…

数据结构练习题4(链表)

1两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4]…

Sqli-labs less-27

Sqli-labs less-27 过滤union\select绕过方式 ### 1. 逻辑绕过 例&#xff1a; 过滤代码 union select user,password from users 绕过方式 1 && (select user from users where userid1)‘admin’### 2.十六进制字符绕过 select ——> selec\x74 union——>un…

AutoFixture:.NET 的假数据生成工具

上次推荐过《Bogus&#xff1a;.NET的假数据生成利器》方便我们制造假数据测试。今天继续推荐另外一个也是非常流行的工具。 01 项目简介 AutoFixture 是一个用于 .NET 的测试工具&#xff0c;它允许开发者在单元测试中自动生成随机的测试数据。它支持广泛的数据类型&#xf…

充电桩高压快充发展趋势

一、为什么要升级充电电压 1、新能源发展的困境 随着电动汽车加快发展&#xff0c;用户对电动汽车接受度不断提高&#xff0c;充电问题是影响电动车普及的重要因素&#xff0c;用户快速补能的需求强烈&#xff0c;例如节假日经常会遇到&#xff0c;高速充电1小时&#xff0c;…

编码方式知识整理【ASCII、Unicode和UTF-8】

编码方式 一、ASCII编码二、Unicode 编码三、UTF-8编码四、GB2312编码五、GBK编码 计算机中对数据的存储为二进制形式&#xff0c;但采用什么样的编码方式存储&#xff0c;效率更高。主要编码方式有 ASCII、Unicode、UTF-8等。 英文一般为1个字节&#xff0c;汉字一般为3个字节…

智联云采 SRM2.0 testService SQL注入漏洞复现

0x01 产品简介 智联云采是一款针对企业供应链管理难题及智能化转型升级需求而设计的解决方案,针对企业供应链管理难题,及智能化转型升级需求,智联云采依托人工智能、物联网、大数据、云等技术,通过软硬件系统化方案,帮助企业实现供应商关系管理和采购线上化、移动化、智能…

Pytorch复习(二)

一、非线性回归 import torch import matplotlib.pyplot as plt from torch import nn,optim from torch.autograd import Variable import numpy as npx_data np.linspace(-2,2,200)[:,np.newaxis] noise np.random.normal(0,0.2,x_data.shape) y_data np.square(x_data) …