Java agent 使用详解

news2024/11/23 20:32:47

一、前言

于一个即将上线的应用来说,系统监控是必不可少的,为什么需要监控呢?应用是跑在服务器上的,应用在运行过程中会发生各自意想不到的问题,像大家熟知的OOM,mysql故障,服务器宕机,程序500等等,因此为了能够第一时间掌握应用运行过程中的各自异常状况,对于应用系统的监控是必要的,而在大多数情况下,应用自身的运行时异常占据了绝大的比例,因此合理的监控显得非常重要。

二、应用监控来源

设想一个场景,如果我们要监控一段程序的执行耗时,通常会有哪些做法呢?

  1. 方法前后添加起始,结束耗时;
  2. 使用aop技术跟踪方法的执行耗时;
  3. ......

上面谈到,在大多数情况下,应用自身的运行时异常占据了绝大的比例,所以在日常开发中,通过程序中日志埋点可以很好的帮助分析和定位问题,而说到底,还是监控代码段的执行,如果是一个简单的单体应用,怎么样都无所谓,毕竟简单嘛,但对于一个成熟的运行中的系统来说,尤其是那种分布式的应用平台,不管使用上面哪种技术,都涉及到应用内部程序的开发工作量,面临这种情况该怎么办呢?

是不是有一种技术可以在需要对既有代码做改动的前提下就能够监控代码运行的一些指标呢?

答案是肯定的,那就是接下来要讲的 java agent技术,也就是Java探针技术。

三、Java agent 是什么?

Java Agent技术,也称为Java代理、Java探针,它允许程序员利⽤其构建⼀个独⽴于应⽤程序的代理程序。

  • Java Agent 本质上就是一个 jar 包。
  • 对于普通的Jar包,通过指定类的 main 函数进行启动,但是 Java Agent 并不能单独启动,必须依附在一个 Java 应用程序运行。
  • 使用Java Agent可以实现在Java程序运行的过程中对其进行修改。

四、Java Agent 主要功能点

  • 在加载java文件前可以拦截字节码并做修改;
  • 在运行期间变更已加载的类的字节码;
  •  获取所有已经被加载过的类;
  • 获取所有已经被初始化过了的类;
  • 获取某个对象的大小;

以上这些功能,使得Java Agent在作为一个独立于Java应用程序的代理程序的同时,可以协助监测、运行甚至替换 JVM 上的程序。Agent的应用十分广泛,可用于实现Java IDE的调试功能、热部署功能、线上诊断⼯具和性能分析⼯具。

例如,百度网络攻击防护工具OpenRASP中就使用了Java Agent来对敏感函数进行插桩,以此实现攻击检测。大名鼎鼎的skywalking对于各类应用监测其底层实现也是基于Java Agent。

五、Java agent 加载过程

在开始编码之前,有必要对Java agent的原理做一些了解,我们知道,一个普通的java程序要运行起来,可以通过main函数运行,或者打成jar包之后指定启动类,说的再简单点,调用执行命令之后,jvm会通过类加载器加载class文件,然后运行起来;

 有了上面对于java agent的了解,java agent可以在加载java文件前拦截字节码并做修改的,执行过程如下图:

如果将两者结合起来看,更具体的过程如下图,Java agent的入场时机以及作用一目了然; 

六、Java agent 代理的两个入口函数

JVM启动支持加载agent代理,而agent代理本身就是一个JVM TI的客户端,其通过监听事件的方式获取Java应用运行状态,调用JVM TI提供的接口对应用进行控制。两个核心的入口函数如下:

// 用于JVM刚启动时调用,其执行时应用类文件还未加载到JVM
public static void premain(String agentArgs, Instrumentation inst);

// 用于JVM启动后,在运行时刻加载
public static void agentmain(String agentArgs, Instrumentation inst);

这两个入口函数分别对应于JVM TI专门提供了执行 字节码增强(bytecode instrumentation) 的两个接口,对这两个入口函数再做一些补充说明:

  • premain加载时刻增强(JVM 启动时加载),类字节码文件在JVM加载的时候进行增强;
  • agentMain动态增强(JVM 运行时加载),已经被JVM加载的class字节码文件,当被修改或更新时进行增强;

这两个接口都是从JDK 1.6开始支持,我们无需对上面JVM TI提供的两个接口规范了解太多,Java Agent和 Java Instrument类包 封装好了字节码增强的上述接口通信,JVM在加载agent时会先找函数1,如果没有发现函数1,则会寻找函数2;

七、Java agent 初体验

有了上面的基础储备后,接下来通过实际的代码演示来体验下Java agent的使用吧;

premain 使用演示一

1、变现一个类,提供一个premain的入口方法

import java.lang.instrument.Instrumentation;

public class MyPreMainAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("hello javaAgent");
    }

}

2、在工程的pom中添加premain打包插件

要注意的是<Premain-Class>标签中写的是上面premain所在类的完整路径

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>com.congge.agent.MyPreMainAgent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

3、将模块编译打成jar包

将这个jar包解压之后,里面有一个非常重要的文件

 打开该文件,内容如下,通过该文件,应用程序在使用java agent的jar包的时候才会正确加载premain对应的入口函数;

Manifest-Version: 1.0
Implementation-Title: java-agent
Implementation-Version: 1.0-SNAPSHOT
Built-By: 26393
Can-Redefine-Classes: true
Implementation-Vendor-Id: com.congge
Class-Path: javassist-3.27.0-GA.jar postgresql-42.3.1.jar checker-qual
 -3.5.0.jar
Can-Set-Native-Method-Prefix: true
Premain-Class: com.congge.agent.MyPreMainAgent
Can-Retransform-Classes: true
Created-By: Apache Maven 3.2.5
Build-Jdk: 1.8.0_171
Implementation-URL: https://spring.io/projects/spring-boot/spi_test/ja
 va-agent

4、编写测试类,启动引用上述的jar

public class AgentTest {

    public static void main(String[] args) {
        new UserService().sayHello("hell agent");
    }

}

启动时在启动参数中指定上面的jar

然后运行测试类,可以看到jar包中的premain方法的打印输出结果,是在测试类的方法打印结果之前输出;

premain 使用演示二

使用javaagent监控运行时内存使用情况

1、Metric 监控指标类

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.Arrays;
import java.util.List;


public class Metric {

    private static final long MB = 1048576L;

    public static void printMemoryInfo() {
        MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
        MemoryUsage headMemory = memory.getHeapMemoryUsage();

        String info = String.format("\ninit: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s\n",
                headMemory.getInit() / MB + "MB",
                headMemory.getMax() / MB + "MB", headMemory.getUsed() / MB + "MB",
                headMemory.getCommitted() / MB + "MB",
                headMemory.getUsed() * 100 / headMemory.getCommitted() + "%"

        );

        System.out.print(info);

        MemoryUsage nonheadMemory = memory.getNonHeapMemoryUsage();

        info = String.format("init: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s\n",
                nonheadMemory.getInit() / MB + "MB",
                nonheadMemory.getMax() / MB + "MB", nonheadMemory.getUsed() / MB + "MB",
                nonheadMemory.getCommitted() / MB + "MB",
                nonheadMemory.getUsed() * 100 / nonheadMemory.getCommitted() + "%"

        );
        System.out.println(info);
    }

    public static void printGCInfo() {
        List<GarbageCollectorMXBean> garbages = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean garbage : garbages) {
            String info = String.format("name: %s\t count:%s\t took:%s\t pool name:%s",
                    garbage.getName(),
                    garbage.getCollectionCount(),
                    garbage.getCollectionTime(),
                    Arrays.deepToString(garbage.getMemoryPoolNames()));
            System.out.println(info);
        }
    }

}

2、premain入口函数类

import java.lang.instrument.Instrumentation;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MyJvmAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is an perform monitor agent.");

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Metric.printMemoryInfo();
                Metric.printGCInfo();
            }
        }, 0, 3000, TimeUnit.MILLISECONDS);
    }

}

3、测试类

import java.util.ArrayList;
import java.util.List;

public class AgentTest {

    public static void main(String[] args) throws Exception {
        System.out.println("hello javaAgent");
        boolean is = true;
        while (is) {
            List<Object> list = new ArrayList<>();
            list.add("hello agent");
        }
    }

}

将上面的MyJvmAgent 配置到pom中,打包,然后像上面的案例那样运行,观察输出结果

八、javassit 介绍与使用

在上面的java agent的类加载原理图中,其最终在编辑字节码文件时,具体的技术实现可以有多种,比如cglib,asm或javassist等;

javassit介绍

javassit是一个开源的分析、编辑和创建Java字节码的类库,主要优点是简单且快速,直接使用java编码的形式,不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成字节码,相对于asm技术,学习和使用成本要低很多;

javassit依赖

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version>
        </dependency>

javassit 案例使用

1、打印输出方法耗时

通常为了输出一个方法的执行耗时,最简单的办法就是在方法头和方法尾部获取当前的执行时间最后再相减,即可得到方法的执行时长;

如果是在生产系统中来做这个事情,一个工程中随便几十几百个类,方法更多,这样加起来就相当费事了,就可以考虑使用java agent的方式来做,下面来看看使用java agent的实现过程;

提供一个业务类

在该类中有一个普通的方法,为了模拟执行业务,休眠1秒

public class UserService {

    public void sayHello(String name){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sayHello()");
    }

}

提供premain方法

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class AgentMain {

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        final ClassPool pool = new ClassPool();
        pool.appendSystemPath();

        //基于工具,在运行的时候修改class字节码,即动态插装
        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                if (!"com/congge/agent/UserService".equals(className)) {
                    return null;
                }
                try {

                    CtClass ctClass = pool.get("com.congge.agent.UserService");
                    CtMethod sayHello = ctClass.getDeclaredMethod("sayHello");

                    // 打印方法耗时 ================
                    //FIXME 1、复制一个方法
                    CtMethod copy = CtNewMethod.copy(sayHello, ctClass, null);
                    copy.setName("sayHelloCopy");
                    ctClass.addMethod(copy);

                    //2、 改变原有的方法
                    sayHello.setBody("{long begin = System.currentTimeMillis();\n" +
                            "                    sayHelloCopy($1);\n" +
                            "                    System.out.println(System.currentTimeMillis()-begin);}");

                    //打印方法耗时 ================

                    //sayHello.insertBefore("System.out.println(System.currentTimeMillis());");
                    return ctClass.toBytecode();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }

}

在该方法中,需要说明的是,通过javassit的编码方式,可以通过下面这种方式将一段程序以字符串的形式动态植入,像其中的 $1 就属于javassit的解析参数的一种指令,有兴趣的同学可以深入学习javassit做进一步的了解;

测试类

public class AgentTest {

    public static void main(String[] args) {
        new UserService().sayHello("hell agent");
    }

}

然后像上面的操作那样,将premain所在的类配置到pom打包,然后运行测试类,控制台就可以输出本次方法的执行耗时;

2、打印输出方法耗时改进

上面的案例使用javassit实现了对某个方法的执行耗时的监控,现在进一步思考下,如果希望这个功能做的更加通用些,比如说在实际业务中,我们不仅仅是输出某个方法的执行耗时,而是输出某一个包下的类的方法执行耗时,或者某一些特定类的方法的执行耗时又该怎么做呢?接下来,基于上面的案例做进一步的优化改进,其目的是为了尽可能做到通用性;

改进premain方法

该方法的改进点涉及下面几点:

  • 加载java agent的jar包时,可由外部传递参数,即外部传入要执行方法耗时的规则;
  • 可以对类中的所有方法进行插桩;
  • 对有返回值和无返回值的方法均可适用;

import javassist.*;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class AgentMain1 {

    public static void premain(String agentArgs, Instrumentation instrumentation) {

        final String config = agentArgs;

        final ClassPool pool = new ClassPool();
        pool.appendSystemPath();

        //基于工具,在运行的时候修改class字节码,即动态插装
        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                if (className == null || !className.replaceAll("/", ".").startsWith(config)) {
                    return null;
                }
                try {
                    className = className.replaceAll("/", ".");
                    CtClass ctClass = pool.getCtClass(className);
                    for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
                        newMethod(ctMethod);
                    }
                    return ctClass.toBytecode();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }

    private static CtMethod newMethod(CtMethod oldMethod) throws CannotCompileException, NotFoundException {
        CtMethod copy = CtNewMethod.copy(oldMethod, oldMethod.getDeclaringClass(), null);
        copy.setName(oldMethod.getName() + "$agent");
        oldMethod.getDeclaringClass().addMethod(copy);

        if (oldMethod.getReturnType().equals(CtClass.voidType)) {
            oldMethod.setBody(String.format(voidSource, oldMethod.getName()));
        } else {
            oldMethod.setBody(String.format(source, oldMethod.getName()));
        }
        return copy;
    }

    final static String source = "{\n" +
            "long begin = System.currentTimeMillis();\n" +
            "        Object result;\n" +
            "        try {\n" +
            "            result = ($w)%s$agent($$);\n" +
            "        }finally {\n" +
            "            long end = System.currentTimeMillis();\n" +
            "            System.out.println(end-begin);\n" +
            "        }\n" +
            "        return ($r) result;\n" +
            "}\n";


    final static String voidSource = "{\n" +
            "long begin = System.currentTimeMillis();\n" +
            "        try {\n" +
            "            %s$agent($$);\n" +
            "        }finally {\n" +
            "            long end = System.currentTimeMillis();\n" +
            "            System.out.println(end-begin);\n" +
            "        }\n" +
            "}\n";

}

UserService 类再追加一个方法

public String sayHello1(String name){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sayHello() " + name);
        return name;
    }

启动类

public class AgentTest {

    public static void main(String[] args) {
        new UserService().sayHello("hell agent");
        new UserService().sayHello1("hell agent");
    }

}

按照上面案例的步骤再次运行,不过这一次启动类中需要添加如下参数,表示以 com.congge.agent.User 这个为前置的类才会被监控执行方法的耗时;

-javaagent:E:\code-self\spi\java-agent\target\java-agent-1.0-SNAPSHOT.jar=com.congge.agent.User

输出控制台结果如下,

通过这种方式,就可以让agent的jar具备了更多的灵活性和一定的通用性,即只要符合匹配规则的类都可以适用这个jar来得到方法的执行耗时;

未完待续 ... 感谢您的观看!

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

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

相关文章

Mac docker-desktop 安装单机版k8s

文章目录01 引言02 下载安装docker desktop03 安装k8s04 安装k8s控制台&#xff08;k8s dashboard&#xff09;05 更方遍的方式安装dashboard01 引言 本文主要讲解在Mac下使用docker-desktop来安装k8s。 02 下载安装docker desktop 下载地址&#xff1a;https://www.docker.…

实例方法定义语法(四)

那么什么是方法呢&#xff1f; Java方法是语句的集合&#xff0c;它们在一起执行一个功能。 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建&#xff0c;在其他地方被引用 1.方法的定义 类的方法定义类的某种行为或者功能。 方法定义的语法…

Allegro如何设置丝印位号优先显示操作指导

Allegro如何设置丝印位号优先显示操作指导 Allegro支持让丝印位号优先显示,可以让视图更加的清晰明了,按照需要的方式显示,具体操作如下 以下图为例 丝印位号被器件的外形盖住了,需要显示的效果为,优先显示丝印位号,器件外形次优先 选择display-layer Priority 出现Di…

MySQL日志系统

MySQL相关文章 慢sql搜集分析工具搭建 前言 日志系统可谓是MySQL中的重中之重&#xff0c;一些MySQL的特性也通过依赖于日志实现的。 本篇文章过一遍日志相关的东西&#xff0c;方便日后复习。 binlog 概念 二进制日志文件&#xff0c;记录了所有的DDL&#xff08;数据库…

tensorflow.keras常用模块介绍

目录前言一、基础层1-0、Input层1-1、Dense层1-2、Activation层&#xff08;激活层&#xff09;、Dropout层1-3、Lambda层1-4、Flatten层二、嵌入层2-1、Embedding层三、池化层3-1、MaxPooling1D层3-2、MaxPooling2D层3-3、AveragePooling1D层3-4、AveragePooling2D层3-5、Glob…

Android 组件化架构设计从原理到实战

为什么需要组件化 小项目是不需要组件化的。当一个项目有数十个人开发&#xff0c;编译项目要花费10分钟&#xff0c;修改一个bug就可能会影响到其他业务&#xff0c;小小的改动就需要进行回归测试&#xff0c;如果是这种项目&#xff0c;那么我们需要进行组件化了 组件化和模…

有效的渗透测试才能确保Web应用安全

应用程序的安全性和快速交付之间存在矛盾&#xff0c;但由于应用程序代码缺陷和安全漏洞&#xff0c;我们正在目睹或经历越来越多的攻击。据调查&#xff0c;软件安全漏洞占了大约47%的安全事故。 与任何软件一样&#xff0c;Web应用程序也包含缺陷和错误。这种安全风险的一个…

[附源码]Python计算机毕业设计Django物品捎带系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

SAP PS 第9节 合并采购申请、组合WBS之详解

SAP PS 第9节 合并采购申请、组合WBS之影响1 合并采购申请1.1 合并采购申请后台配置1.2 合并采购申请效果如下2 组合WBS2.1 后台配置2.1.1 激活需要分组的MRP组2.1.2 项目必须为有库存模式&#xff0c;无论估价或者未估价都可以2.1.3 物料必须允许项目库存&#xff08;允许独立…

Azkaban登录分析

分析意义:目前azkaban采用的是azkaban-users.xml配置文件的方式,配置登录用户。如果公司需要二次开发,增加安全性和便捷性,想从数据库取值呢,该如何着手开发呢?本文分析登录过程,便于进行azkaban的二次登录开发。 1、登录请求地址,请求方式和参数 请求地址:http://x…

mysql创建子账户

1.首先登陆root账户 [roothecs-219255 ~]# mysql -uroot -p首先在Xshell客户端登陆linux&#xff0c;然后输入mysql命令进行登陆。 2.创建子账户 创建一个用户名为test1&#xff0c;密码为123456的子账号&#xff0c;有两种方法&#xff0c;分别为&#xff1a; CREATE USER …

【多目标进化优化】NSGAII 算法原理与代码实现

前言 Gitee 代码地址&#xff1a;https://gitee.com/futurelqh/Multi-objective-evolutionary-optimization 理论分析 \quad\quadSrinivas 和 Deb 于 1993 年提出 了 NSGA (non-dominated sorting in genetic algorithm) (Srinivas et al, 1994)。NSGA 主要有三个方面的不足&…

Docker:数据卷(Data Volumes)dockerfile

目录 一、宿主机与容器之间的文件拷贝 从容器中拷贝文件到宿主机 从宿主机拷贝文件到容器 二、数据卷 三、数据卷容器 四、Dockerfile 1、自定义centos&#xff0c;具备vim及ifconfig作用 2、自定义tomcat8 一、宿主机与容器之间的文件拷贝 备份我们已经装好的docker 等备…

隔离式DC/DC高压模块5V12V24V转50V110V250V300V380V600V1100V短路保护直流升压可调开关控制电源模块

特点 ● 效率高达 70%以上 ● 1*2英寸标准封装 ● 单电压输出 ● 价格低 ● 稳压输出 ● 工作温度: -40℃~85℃ ● 阻燃封装&#xff0c;满足UL94-V0 要求 ● 温度特性好 ● 可直接焊在PCB 上 应用 HRB W2~25W 系列模块电源是一种DC-DC升压变换器。该模块电源的输入电压分为&a…

Android databinding之数据单向与双向绑定详解与使用(三)

一、介绍 通过前面两篇文档&#xff0c;我们大概了解了databinding的工作方式&#xff0c;view的初始化&#xff0c;recycleview的使用。但是这些UI都离不开数据的填充&#xff0c;数据的修饰。 在说到数据绑定&#xff0c;好多开发者平时在工作中也经常听到databinding的数据…

https服务部署指南

1.概念 https服务的证书分布如下图&#xff1a; 客户端&#xff1a;CA证书 服务器&#xff1a;服务器证书&#xff0c;服务器密钥 2.证书生成&验证 2.1 证书生成 假设我们的域名为&#xff1a; www.contoso.com 创建CA私钥 openssl ecparam -out contoso.key -name p…

JMH基准测试工具 (一):介绍

在日常开发中&#xff0c;我们对一些代码的调用或者工具的使用会存在多种选择方式&#xff0c;在不确定他们性能的时候&#xff0c;我们首先想要做的就是去测量它。大多数时候&#xff0c;我们会简单的采用多次计数的方式来测量&#xff0c;来看这个方法的总耗时。 但是&#x…

串口通信扩展知识

在Android工控系统上&#xff0c;Android与硬件的通讯交互随处可见&#xff0c;其中串口通讯是最常用的通讯方式之一。 串口通信(Serial Communication)&#xff0c; 是指外设和计算机间&#xff0c;通过数据信号线 、地线、控制线等&#xff0c;按位进行传输数据的一种通讯方式…

扩散模型(Diffusion Model)原理与代码解析(一)

一、模型概览 扩散模型的灵感来自于非平衡热力学。定义了一个扩散步骤的马尔可夫链&#xff08;当前状态只与上一时刻的状态有关&#xff09;&#xff0c;慢慢地向真实数据中添加随机噪声&#xff08;前向过程&#xff09;&#xff0c;然后学习反向扩散过程&#xff08;逆扩散…

python 图形界面“诈金花”游戏,更新了!附完整代码

旧版本的代码请见上一篇博文&#xff1a; python 从一道作业题到制作一个图形界面的“诈金花”游戏_Hann Yang的博客-CSDN博客Player1: (♥Q, ♣2, ♣8) - 单张Player2: (♦10, ♥7, ♠6) - 单张Player3: (♣4, ♠4, ♦2) - 对子Player4: (♠5, ♠9, ♥6) - 单张Player5: (♠…