JavaAgent 技术原理及实战

JavaAgent 技术原理及实战

  • 1、引子
  • 2、JavaAgent 简单示例:方法开始和结束时打印日志
    • 2.1 创建 Agent
    • 2.2 编写验证 agent 功能的测试类
      • 2.2.1 使用JavaAgent 静态加载方式
      • 2.2.2 使用 JavaAgent 动态加载方式
    • 2.3、小结
  • 3、JavaAgent
    • 3.1 JavaAgent是什么?
    • 3.2 原理解析
      • 3.2.1 JVMTI
      • 3.2.2 Instrumentation
      • 3.2.3 JavaAgent 的加载
        • 3.2.3.1 静态加载
        • 3.2.3.2 动态加载
  • 4、实战:使用JavaAgent实现全链路监控基础版
    • 4.1 概述
    • 4.2 技术环境
    • 4.3、常见问题
      • 4.3.1 JavaAgent
      • 4.3.2 常用的修改字节码的工具?
      • 4.3.3 为什么跨线程不能透传traceId?如何解决?
      • 4.3.4 ThreadLocal、InheritableThreadLocal和TransmittableThreadLocal三者区别?
      • 4.3.5 Spring Boot Starter?
      • 4.3.6 JavaAgent与Spring AOP和AspectJ之间有什么区别?
  • 源码下载

在这里插入图片描述

1、引子

线上故障群发来一条用户投诉:用户抱怨页面加载时间过长,有时甚至超时。这时,你首先检查了服务器和数据库,但并未发现有问题。你尝试在开发环境中复现问题,但一切运行正常。这个问题只在生产环境的特定时段出现,常规的Debug方式并不奏效。

此刻的你感到困惑和无助,不禁开始怀疑:是不是应用程序的某个部分在增加系统的延迟?有没有方法可以帮助你追踪代码的运行过程,看一看到底哪里出现了问题?

此时,JavaAgent技术闪耀登场。这项技术可以帮助你深入观察应用程序的运行状态,助你洞察问题的根源。那么我们就一起来探索一下JavaAgent技术。

2、JavaAgent 简单示例:方法开始和结束时打印日志

2.1 创建 Agent

创建javaagent-demo工程,目录结构如下:
在这里插入图片描述
新建 pom.xml​,引入 javassist​ 用来修改目标类的字节码,增加自定义代码。通过 maven-assembly-plugin 插件打包自定义的 agent jar。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><parent><artifactId>javaagent-demo</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>agent-demo</artifactId><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.25.0-GA</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.1</version><configuration><descriptorRefs><!--将应用的所有依赖包都打到jar包中。如果依赖的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar--><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><!-- 设置manifest配置文件--><manifestEntries><!--Premain-Class: 代表 Agent 静态加载时会调用的类全路径名。--><Premain-Class>com.atu.DemoAgent</Premain-Class><!--Agent-Class: 代表 Agent 动态加载时会调用的类全路径名。--><Agent-Class>com.atu.DemoAgent</Agent-Class><!--Can-Redefine-Classes: 是否可进行类定义。--><Can-Redefine-Classes>true</Can-Redefine-Classes><!--Can-Retransform-Classes: 是否可进行类转换。--><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration><executions><execution><!--绑定到package生命周期阶段上--><phase>package</phase><goals><!--绑定到package生命周期阶段上--><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>${maven.compiler.source}</source><target>${maven.compiler.target}</target></configuration></plugin></plugins></build></project>

编写agent核心代码 DemoAgent.java,我们使用了premain()​静态加载方式,agentmain()​动态加载方式。并用到了Instrumentation​类结合javassist代码生成库进行字节码的修改。

public class DemoAgent {/*** 被转换的类*/private static String TRANSFORM_CLASS = "com.atu.Test";public static void premain(String agentArgs, Instrumentation inst) {// 在这里可以对应用程序进行字节码转换// 例如,添加一个Transformerinst.addTransformer(new MyClassTransformer());}/*** 动态加载。Java agent指定的premain方法,会在main方法之前被调用*/public static void agentmain(String args, Instrumentation inst) {System.out.println("agentmain start!");inst.addTransformer(new MyClassTransformer());Class<?>[] classes = inst.getAllLoadedClasses();if (classes != null) {for (Class<?> c : classes) {if (c.isInterface() || c.isAnnotation() || c.isArray() || c.isEnum()) {continue;}if (c.getName().equals(TRANSFORM_CLASS)) {try {System.out.println("retransformClasses start, class: " + c.getName());/** retransformClasses()对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。* retransformClasses()可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性*/inst.retransformClasses(c);System.out.println("retransformClasses end, class: " + c.getName());} catch (UnmodifiableClassException e) {System.out.println("retransformClasses error, class: " + c.getName() + ", ex:" + e);e.printStackTrace();}}}}System.out.println("agentmain end!");}
}
public class MyClassTransformer implements ClassFileTransformer {/*** 被转换的类*/private static String TRANSFORM_CLASS = "com.atu.Test";@Overridepublic byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined, ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {// 在这里可以修改字节码// 例如,打印类名try {className = className.replace("/", ".");if (className.equals(TRANSFORM_CLASS)) {final ClassPool classPool = ClassPool.getDefault();final CtClass clazz = classPool.get(TRANSFORM_CLASS);for (CtMethod method : clazz.getMethods()) {/** Modifier.isNative(methods[i].getModifiers())过滤本地方法,否则会报* javassist.CannotCompileException: no method body  at javassist.CtBehavior.addLocalVariable()* 报错原因如下* 来自Stack Overflow网友解答* Native methods cannot be instrumented because they have no bytecodes.* However if native method prefix is supported ( Transformer.isNativeMethodPrefixSupported() )* then you can use Transformer.setNativeMethodPrefix() to wrap a native method call inside a non-native call* which can then be instrumented*/if (Modifier.isNative(method.getModifiers())) {continue;}method.insertBefore("System.out.println(\"" + clazz.getSimpleName() + "."+ method.getName() + " start.\");");method.insertAfter("System.out.println(\"" + clazz.getSimpleName() + "."+ method.getName() + " end.\");", false);}return clazz.toBytecode();}} catch (Exception e) {e.printStackTrace();}return classfileBuffer;}
}

编译打包:

在这里插入图片描述

2.2 编写验证 agent 功能的测试类

在这里插入图片描述

public class Test {public static void main(String[] args) throws InterruptedException {System.out.println("helloworld!");Thread.sleep(20000);}
}

2.2.1 使用JavaAgent 静态加载方式

在 IDEA 的 Run/Debug Configurations 中,点击 Modify options,勾选上 add VM options,在 VM options 栏增加 -javaagent:全路径\agent-demo-1.0-SNAPSHOT-jar-with-dependencies.jar

运行 Test.java 的 main 方法,可以看到控制台日志:
在这里插入图片描述

2.2.2 使用 JavaAgent 动态加载方式

动态加载不是通过 -javaagent: 的方式实现,而是通过 Attach API 的方式。

编写调用 Attach API 的测试类:

public class AttachMain {public static void main(String[] args) throws Exception {// agentmain()方法所在jar包String jar = "E:\\projects\\mycode\\javaagent-demo\\agent-demo\\target\\agent-demo-1.0-SNAPSHOT-jar-with-dependencies.jar";for (VirtualMachineDescriptor virtualMachineDescriptor : VirtualMachine.list()) {// 针对指定名称的JVM实例if (virtualMachineDescriptor.displayName().equals("com.atu.Test")) {System.out.println("将对该进程的vm进行增强:com.atu.Test的vm进程, pid=" + virtualMachineDescriptor.id());// attach到新JVMVirtualMachine vm = VirtualMachine.attach(virtualMachineDescriptor);// 加载agentmain所在的jar包vm.loadAgent(jar);// detachvm.detach();}}}
}

先直接运行 com.atu.Test#main​,注意不用加 -javaagent: 启动参数。
在这里插入图片描述约 5 秒后,再运行 com.atu.AttachMain#main,可以看到 com.atu.AttachMain#main 打印的日志:

将对该进程的vm进行增强:com.atu.Test的vm进程, pid=15544

之后可以看到 com.atu.Test#main打印的日志中多了记录方法运行开始和结束的内容。

helloworld!
agentmain start!
retransformClasses start, class: com.atu.Test
retransformClasses end, class: com.atu.Test
agentmain end!

2.3、小结

可以看到静态加载或动态加载相同的 agent,都能实现了记录记录方法运行开始和结束日志的功能。

下面进入正题:什么是JavaAgent?

3、JavaAgent

3.1 JavaAgent是什么?

JavaAgent 是一种特殊的类,它提供了一种能力,使得我们可以在Java程序运行期间,对加载到JVM中的类进行字节码层面的修改和增强。

简单来说,JavaAgent能够“拦截”类加载过程,在类被使用前“改变”它的行为。

JavaAgent本质上可以理解为一个插件,该插件就是一个精心提供的jar包,这个jar包通过JVMTI(JVM Tool Interface)完成加载,最终借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成对目标代码的修改。

JavaaAgent通常用在如性能监控(Profiler)、代码热替换、动态追踪等领域。一些知名的工具,比如JRebel(代码热替换)、SkyWalking(性能监控)就是基于JavaAgent 实现的。

3.2 原理解析

3.2.1 JVMTI

JVMTI​ (JVM Tool Interface)是 Java 虚拟机对外提供的 Native 编程接口,通过 JVMTI ,外部进程可以获取到运行时 JVM 的诸多信息,比如线程、GC 等。

JVMTI是基于事件驱动的,JVM每执行一定的逻辑就会触发一些事件的回调接口,通过这些回调接口,用户可以自行扩展实现自己的逻辑。

JVMTI 是一套 Native 接口,在 Java SE 5 之前,要实现一个 Agent 只能通过编写 Native 代码来实现。从 Java SE 5 开始,可以使用 Java 的Instrumentation 接口(java.lang.instrument)来编写 Agent。无论是通过 Native 的方式还是通过 Java Instrumentation 接口的方式来编写 Agent,它们的工作都是借助 JVMTI 来进行完成。

Instumentation API 可以支持 Java 语言实现 agent 功能,但是 JVMTI 功能比 Instumentation API 更强大。

3.2.2 Instrumentation

Instrumentation 是 Java 提供的 JVM 接口,该接口提供了一系列查看和操作 Java 类定义的方法,例如修改类的字节码、向 classLoader 的 classpath 下加入 jar 文件等。使得开发者可以通过 Java 语言来操作和监控 JVM 内部的一些状态,进而实现 Java 程序的监控分析,甚至实现一些特殊功能(如 AOP、热部署)。

Instrumentation接口中最常用的方法是 addTransformer(ClassFileTransformer transformer)​,这个方法可以在类加载时做拦截,对输入的类的字节码进行修改,其参数是一个ClassFileTransformer接口:

public interface ClassFileTransformer {/*** 传入参数表示一个即将被加载的类,包括了classloader,classname和字节码byte[]* 返回值为需要被修改后的字节码byte[]*/byte[]transform(  ClassLoader         loader,String              className,Class<?>            classBeingRedefined,ProtectionDomain    protectionDomain,byte[]              classfileBuffer)throws IllegalClassFormatException;
}

3.2.3 JavaAgent 的加载

JavaAgent 支持静态加载和动态加载。

3.2.3.1 静态加载

静态加载,即 JVM 启动时加载,对应的是 premain()​ 方法。通过 vm 启动参数-javaagent 将 agent jar 挂载到目标 JVM 程序,随目标 JVM 程序一起启动。

  1. 加载 JavaAgent:JavaAgent 中的 class 通常是由 system calss loader(默认AppClassLoader) 加载。
  2. 调用 premain方法: JVM 加载完成 JavaAgent 的入口类之后,会调用其 premain 方法
  3. 使用 Instrumentation API:通过 premain 方法,JavaAgent 得到了 Instrumentation 的实例。JavaAgent 可以使用这个 Instrumentation 实例来注册一个或多个 ClassFileTransformer,或者进行其它需要的操作。这是 JavaAgent 真正开始发挥作用的地方。
  4. Instrumentation API 调用 JVMTI: JVMTI 是 JNI (Java Native Interface) 的一部分,提供了丰富的接口供 Instrumentation API 和 JVM 进行交互。
  5. 执行 ClassFileTransformer:当 Instrumentation 需要载入类时,如果 Java Agent 对该类注入了 ClassFileTransformer,JVMTI 会回调 Instrumentation API,然后调用对应的 ClassFileTransformer.transform 方法。在 transform 方法中,JavaAgent 可以修改类的字节码。
  6. premain() 方法会调用 Instrumentation API,然后 Instrumentation API 调用 JVMTI(JVMTI 的内容将在后面补充),在需要加载的类需要被加载时,会回调 JVMTI,然后回调 Instrumentation API,触发 ClassFileTransformer.transform(),最终修改 class 的字节码。
  7. 加载主类并启动应用: 所有的 JavaAgent 加载和初始化完成后,JVM 会准备开始加载主类并执行其 main 方法。

在这里插入图片描述

ClassFileTransformer.transform:是 Java Instrumentation API 中的核心方法,它的作用是在类文件被 JVM 加载之前,对其进行字节码级别的转换和修改。

3.2.3.2 动态加载

JVM运行时加载,可以在 main 函数开始运行之后再运行。通过Attach API​动态地加载 JavaAgent,对应的是 agentmain() 方法。

基本流程:

  1. 创建一个 VirtualMachine 实例:首先,使用 VirtualMachineDescriptor 获取要附加的Java虚拟机的描述,并通过 VirtualMachine.attach() 方法连接到该JVM。
  2. 加载JavaAgent:在连接到 JVM 后,使用 VirtualMachine.loadAgent() 方法加载JavaAgent的jar包。
  3. 修改字节码
  4. 断开连接:改完成后,通过 VirtualMachine.detach() 方法断开与JVM的连接。所有的更改都会在目标JVM中保存,不会影响到运行Agent的JVM。

4、实战:使用JavaAgent实现全链路监控基础版

4.1 概述

  1. 利用 javassist 对 Log 框架进行切面增强。
  2. 利用 Spring 的拦截器技术实现了 Web 请求的 traceId 初始赋值。
  3. 通过整合 dubbo SPI,结合 dubbox 调用拦截器,实现 traceId 的拦截及赋值。
  4. 引入TransmittableThreadLocal 解决父子线程上下文传递的问题。

4.2 技术环境

Javassist 3.25.0-GA+Dubbox 2.6.5+Spring Boot 2.0.5.RELEASE+Transmittable 2.12.2

4.3、常见问题

4.3.1 JavaAgent

JavaAgent 是一种特殊的 Java 程序,它利用 Java 的 Instrumentation 机制在运行时改变或分析其他Java 程序的行为,并对其进行监控、调试、或性能优化等操作。

有了 JavaAgent 技术,可以在字节码这个层面对类和方法进行修改,可以把 JavaAgent 理解成一种代码注入的方式,或者可以说 JavaAgent 就是 JVM 层面的代理程序。

4.3.2 常用的修改字节码的工具?

  1. ASM
  2. Javassist
  3. ByteBuddy

4.3.3 为什么跨线程不能透传traceId?如何解决?

我们常用的日志框架,比如 Logback,Log4j 等,通过使用 MDC(Mapped Diagnostic Context,映射调试上下文)在多线程环境下记录日志。

我们的请求链路ID,也是借助 MDC 实现传递的。

MDC 是以线程为基础的存储结构,每个线程都有其自己的一份独立的 MDC 数据。这是通过底层的 ThreadLocal 实现的,ThreadLocal 为每个线程提供了一个独立的数据副本,每个线程都只能看到及修改自己的 ThreadLocal 副本数据,而看不到其他线程的数据,这样可以有效避免数据之间的相互影响。

那么当异步方法切换线程的时候,就会出现上下文信息传递丢失的问题。从而导致 TraceID 丢失的问题。

我们需要在父线程中手动获取并传递 MDC 数据到子线程,解决数据跨线程传递的问题。

4.3.4 ThreadLocal、InheritableThreadLocal和TransmittableThreadLocal三者区别?

1)、ThreadLocal

ThreadLocal主要是为每个ThreadLocal对象创建一个ThreadLocalMap来保存对象和线程中的值的映射关系。

当创建一个ThreadLocal对象时会调用get()或set()方法,在当前线程的中查找这个ThreadLocal对象对应的Entry对象,如果存在,就获取或设置Entry中的值;否则,在ThreadLocalMap中创建一个新的Entry对象。

ThreadLocal类的实例被多个线程共享,每个线程都拥有自己的ThreadLocalMap对象,存储着自己线程中的所有ThreadLocal对象的键值对。

ThreadLocal的实现比较简单,但需要注意的是,如果使用不当,可能会出现内存泄漏问题,因为ThreadLocalMap中的Entry对象并不会自动删除。

2)、InheritableThreadLocal

InheritableThreadLocal的实现方式和ThreadLocal类似,但不同之处在 Thread 类的 init() 方法中,当创建新的线程时,会调用 inheritThreadLocals(parentThread) 方法,这个方法就是将父线程的 InheritableThreadLocalMap (注意这里并不是 ThreadLocalMap)复制一份到子线程中。

局限性:InheritableThreadLocal 支持子线程访问在父线程的核心思想是在创建线程的时候将父线程中的本地变量值复制到子线程,即复制的时机为创建子线程时。

线程池能够复用线程,减少线程的频繁创建与销毁,如果使用 InheritableThreadLocal ,那么线程池中的线程拷贝的数据来自于第一个提交任务的外部线程,即后面的外部线程向线程池中提交任务时,子线程访问的本地变量都来源于第一个外部线程,造成线程本地变量混乱。

3)、TransmittableThreadLocal

TransmittableThreadLocal 是阿里巴巴开源的专门解决 InheritableThreadLocal 的局限性,实现线程本地变量在线程池的执行过程中,能正常的访问父线程设置的线程变量。

TTL 的设计理念在于,每次线程执行任务时,都会备份当前线程的 TTL 值,然后从提交任务的线程那里拷贝一份新的 TTL 值到当前线程,
任务执行完成后,再将备份的 TTL 值恢复回当前线程。

具体流程如下:

  1. 任务提交到线程池时,首先会将提交任务的线程(父线程)的 TTL 值拷贝一份,然后作为本次任务执行的上下文。
  2. 线程获取到任务执行时,先将线程原先的 TTL 值进行备份,然后将第一步拷贝的 TTL 值设置到线程中。
  3. 任务执行完成后,线程将自身的 TTL 值设置回第二步备份的值。

通过这种方式,TTL 成功解决了在使用 InheritableThreadLocal 时线程池中由于线程复用导致的问题,确保了每次任务执行时,线程内的 TTL 值都是我们期望的那个值。

4.3.5 Spring Boot Starter?

Spring Boot Starter 是 Spring Boot 框架提供的一种特性,它是一种提供依赖项的方式,可以帮助开发人员快速集成各种第三方库和框架。

Spring Boot Starter 的目的是简化 Spring 应用程序的依赖管理,将一组相关的依赖项打包在一起,并提供一个依赖项描述文件,使开发人员可以快速集成。

Spring Boot Starter 本质上是一个包含了必要依赖和自动配置类的 Maven 依赖(是一系列依赖集合),它能够自动配置应用程序的运行环境,并提供默认的配置选项,让开发人员可以快速开始开发。

举个例子,如果在 Spring Boot 项目中使用 Spring MVC,需要引入多个与Spring MVC相关的依赖,包括 spring-webmvc、spring-web等,这时候如果使用 spring-boot-starter-web 这个starter,只需要添加一个依赖就可以了,它会包含使用 Spring MVC 所需要的所有依赖。

4.3.6 JavaAgent与Spring AOP和AspectJ之间有什么区别?

1)、JavaAgent

Java Agent 是 Java 5 引入的一种机制,它能够通过预处理(Pre-processing)和类转换(Class Transformation)的方式,修改已有的字节码。

Java Agent 通常在 JVM 启动或者类加载时进行操作。每当一个类被 JVM 加载,都会调用 Java Agent 的代理方法,以便进行类字节码的转换。
因此,Java Agent 更底层,使用复杂,但功能十分强大。

2)、AspectJ

AspectJ 是最早的切面编程框架之一,并且它提供了非常强大的切面编程能力。
AspectJ 通过类似于 Java 语言的 AspectJ 语言来书写切面,并且提供了一个 AspectJ 编译器,将 AspectJ 代码编译成可以运行的字节码。
AspectJ 支持更多更细粒度的切入点,如方法调用,实例创建等,能够在运行时进行热替换和精细控制,其功能强大,但使用和学习成本较高。

3)、Spring AOP

Spring AOP 是 Spring 框架提供的切面编程实现。
它主要利用 Java 的动态代理机制以及 CGLIB 库来在运行时动态地创建对象的代理。
与 AspectJ 相比,Spring AOP 更轻量级且简单,但其切入点种类有限,主要支持方法执行切点,不能做到类似 AspectJ 的构造函数或属性切入。
使用便捷,适用于一般的日志、事务等场景,对于复杂的切点和切面控制较为局限。

源码下载

源码

【文章参考】
【JVM】Java agent超详细知识梳理

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

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

相关文章

linux 软中断入门

在 linux 中&#xff0c;任务执行的载体有很多&#xff0c;包括线程&#xff0c;中断&#xff0c;软中断&#xff0c;tasklet&#xff0c;定时器等。但是从本质上来划分的话&#xff0c;任务执行的载体只有两个&#xff1a;线程和中断。软中断和 tasklet 的执行可能在中断中&am…

DevSecOps安全工具链介绍

目录 一、概述 二、安全工具链在平台中的定位 2.1 概述 2.2 分层定位 2.2.1 不同阶段的安全工具 2.2.2 安全工具金字塔 2.3 安全流水线集成概览 2.3.1 概述 2.3.2 标准流水线集成安全工具链概览图 三、安全工具链分类 3.1 概述 3.2 威胁建模类 3.2.1 威胁建模的概念…

计算机网络:数据链路层 - 封装成帧 透明传输 差错检测

计算机网络&#xff1a;数据链路层 - 封装成帧 & 透明传输 & 差错检测 数据链路层概述封装成帧透明传输差错检测 数据链路层概述 从数据链路层来看&#xff0c;主机 H1 到 H2 的通信可以看成是在四段不同的链路上的通信组成的&#xff0c;所谓链路就是从一个节点到相邻…

Android设备无线连接电脑及QXDM、QACT等工具的方法

首先样机和笔记本电脑连接同一wifi网络 adb root adb shell ifconfig复制inet addr地址 ping inet addr地址 adb tcpip 5555 adb connect (inet addr地址):5555 此时adb和机器使用wifi连接好了&#xff0c;可以拔出usb线 ipconfig查询电脑的IP地址 ipconfig使用adb在主机上…

canvas+javascript 实现贪吃蛇游戏

引言 在当今数字化时代&#xff0c;编程已经成为一种极具创造力和趣味性的活动。通过编写代码&#xff0c;我们可以创造出各种各样的应用程序和游戏&#xff0c;其中包括经典的贪吃蛇游戏。本文将向您介绍如何使用 JavaScript 编程语言制作一个简单而有趣的贪吃蛇游戏&#xf…

动态内存管理-错题合集讲解

空指针的解应用操作&#xff08;错误信息合集&#xff09; 越界访问 首先我们上一个代码&#xff0c;看看这个的代码的问题 这个代码的问题显而易见 &#xff0c;就是在循环里面&#xff0c;产生了越界访问的问题&#xff0c;这里你开辟了10个整形空间&#xff0c;但是从0-1…

【javaWeb 第九篇】功能接口开发流程以及常用注解

常用注解 准备-环境搭建开发规范开发流程 注解补充 准备-环境搭建 准备数据库表&#xff08;dept,emp&#xff09;准备后端SpringBoot环境 需要依赖&#xff1a; Web起步依赖&#xff0c;数据库驱动依赖&#xff0c;Mybatis依赖&#xff0c;lombok依赖配置文件application.pr…

9.Python类与对象

1 面向对象 类和对象都是面向对象中的重要概念。面向对象是一种编程思想&#xff0c; 即按照真实世界的思维方式构建软件系统。 例如&#xff0c;在真实世界的校园里有学生和老师&#xff0c;学生有学号、姓名、所 在班级等属性&#xff08;数据&#xff09;&#xff0c;还有…

MySQL核心命令详解与实战,一文掌握MySQL使用

文章目录 文章简介演示库表创建数据库表选择数据库删除数据库创建表删除表向表中插入数据更新数据删除数据查询数据WHERE 操作符聚合函数LIKE 子句分组 GROUP BY HAVINGORDER BY(排序) 语句LIMIT 操作符 分页查询多表查询-联合查询 UNION 操作符多表查询-连接的使用-JOIN语句编…

本地GPU调用失败问题解决3重新配置anaconda环境(成功)

1、右键“以管理员身份”打开anaconda prompt conda create -n python 3.9 2、使用官方下载源的配置 3、修改conda下载超时 conda config --set remote_connect_timeout_secs 60 conda config --set remote_read_timeout_secs 100 查看配置结果conda config --show 配置内…

122、内网安全——域信息收集应用网络凭据CS插件AdfindBloodHound

文章目录 理解域域信息搜集 理解域 假设有1000台计算机&#xff0c;运维人员需要为每一台计算机进行软件的安装、环境部署&#xff0c;实际上运维人员不可能亲自对每一台计算机进行软件的安装和环境部署。实际&#xff0c;将所有1000台计算机放入一个域中&#xff0c;域内有一…

多传感器标定——相机内参标定

文章目录 一、前言二、内参标定流程三、如何提升标定精度四、精度验证五、内外参联合标定 一、前言 之前写过一篇文章&#xff08;相机内参、外参、畸变系数简介&#xff09;&#xff0c;感觉应该把这几个东西说的还算明白&#xff0c;但是里边并没有深究该如何进行标定&#…

牛客NC153 信封嵌套问题【中等 动态规划,最长递增子序列 Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/9bf77b5b018d4d24951c9a7edb40408f 相同的题目&#xff1a; https://www.lintcode.com/problem/602 思路 本质是求最长子序列问题envelopes 先按 w 升序排序&#xff0c;再按 h 降序 排序&#xff0c;只需考虑h…

一条SQL在MySQL中的执行过程

图解&#xff1a; 第⼀步&#xff1a;连接器 过程 1. 建⽴连接&#xff1a;与客户端进⾏ TCP 三次握⼿建⽴连接&#xff1b; 2. 校验密码&#xff1a;校验客户端的⽤户名和密码&#xff0c;如果⽤户名或密码不对&#xff0c;则会报错&#xff1b;3. 权限判断&#xff1a…

手机无线投屏到windows11电脑

1 安装无线投影组件 2 电脑端打开允许其他设备投影的开关 3 手机找到投屏选项 4 手机搜索可用设备连接即可 这里的官方文档给的不太好,给了一些让人眼花撩乱的信息,以下是经过整合的有效信息

金融衍生品市场

金融衍生品市场 衍生金融品的作用衍生金融工具远期合约期货合约期权 衍生金融品的作用 套期保值&#xff08;Hedging&#xff09; 组合多头头寸(long position)与空头头寸(short position)例&#xff1a;股票与股指期货 投机 衍生金融工具 远期合约 定义&#xff1a;在将来…

翻译: 硅谷软件工程师面试:准备所需的一切

没有人有时间去做成百上千道LeetCode题目&#xff0c;好消息是你实际上并不需要做那么多题目就能够在FAANG公司找到工作&#xff01; 我曾经在Grab工作&#xff0c;这是东南亚的一家共享出行公司&#xff0c;但我对工作感到沮丧&#xff0c;想要进入FAANG公司&#xff0c;但我…

【opencv】教程代码 —features2D(5)旋转相机的基本全景拼接

基本全景拼接 panorama_stitching_rotating_camera.cpp 将第二张图像进行透视变换后与第一张图像拼接 #include <iostream> // 包含了一些用于输入输出的函数 #include <opencv2/core.hpp> // 包含了OpenCV核心库的一些常用类和函数 #include <opencv2/imgpro…

Android视角看鸿蒙第十课-鸿蒙的布局之线性布局

Android视角看鸿蒙第十课-鸿蒙的布局之线性布局 导读 这篇文章开始&#xff0c;依次学习鸿蒙的八大布局&#xff0c;这是第一篇&#xff0c;所以顺带也会聊聊通用属性。 文档地址 文档地址 如何定义一个线性布局 Android中是使用LinearLayout来构建线性布局的&#xff0c…

ChatGPT chrome扩展下载与安装

官方下载地址 https://chromewebstore.google.com/detail/lpbhmlbicmgjpacbofijdfpcplfhakeo 截图 安装 离线安装 下载地址 https://static.xutongbao.top/app/chatgpt-chrome-crx-v0.0.7.zip 打开链接 chrome://extensions/ 人工智能学习网站 https://chat.xutongbao.to…