【微服务】Java agent 使用详解

news2024/9/22 9:51:49

一、前言

于一个即将上线的应用来说,系统监控是必不可少的,为什么需要监控呢?应用是跑在服务器上的,应用在运行过程中会发生各自意想不到的问题,像大家熟知的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/57833.html

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

相关文章

cubeIDE开发,基于已有的STM32CubeMX (.ioc)创建工程文件

一、STM32Cube 生态系统 可以在其官网查看&#xff0c;支持中文。 STM32Cube - Discover the STM32Cube Ecosystem - STMicroelectronics ​ 截取官网的STM32Cube家族的软件工具描述&#xff1a; 【1】STM32CubeMX, 面向任意STM32设备的配置工具。这款简单易用的图形用户界面为…

ubuntu根目录清理

0.防范于未然&#xff08;就像给window电脑清理垃圾&#xff09; 清理ubuntu用不上的东西的常用命令 # 系统自带清理命令 sudo apt-get autoclean sudo apt-get clean sudo apt-get autoremove# 查看目录占用空间 sudo du -cks * | sort -rn | head -10 sudo du --max-depth1…

[附源码]计算机毕业设计JAVA血库管理系统

[附源码]计算机毕业设计JAVA血库管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Ma…

Effective C++条款24:若所有参数皆需类型转换,请为此采用non-member函数

Effective C条款24&#xff1a;若所有参数皆需类型转换&#xff0c;请为此采用non-member函数&#xff08;Declare non-member functions when type conversions should apply to all parameters&#xff09;条款24&#xff1a;若所有参数皆需类型转换&#xff0c;请为此采用no…

Swift基础语法 - 枚举

枚举的基本用法 定义&#xff1a;枚举简单的说也是一种数据类型&#xff0c;只不过是这种数据类型只包含自定义的特定数据&#xff0c;它是一组有共同特性的数据的集合。 enum Direction {case northcase southcase eastcase west }enum Direction {case north,south,east,we…

数理统计笔记10:回归分析

引言 数理统计笔记的第10篇介绍了回归分析&#xff0c;从相关关系开始介绍&#xff0c;然后介绍回归分析&#xff0c;主要介绍了一元回归模型和多元回归模型&#xff0c;并对其中的原理和检验进行了叙述&#xff0c;最后简单介绍了一下可以化为线性回归模型的非线性回归模型。 …

【Gradle-5】Gradle常用命令与参数

1、前言 Gradle的命令有很多&#xff0c;熟悉常用命令之后&#xff0c;在日常开发中&#xff0c;不仅可以提升效率&#xff0c;也可以辅助我们快速定位并解决编译问题&#xff1b;而且某些情况下命令行(CLI)与按钮执行的编译结果是不一样的&#xff0c;比如构建时要传参(-P)&a…

大学生环保主题网页制作 环境网页设计模板 学生静态网页作业成品 dreamweaver保护地球环境HTML网站制作

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【电商项目实战】上传头像(详细篇)

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;SpringBoot电商项目实战 ✨学习社区&#xff1a; &#x1f449;不会压弯的小飞侠 ✨知足上进&#x…

【Spring源码系列】Bean生命周期-实例化前

这里写目录标题前言一、实例化前 - InstantiationAwareBeanPostProcessor介绍InstantiationAwareBeanPostProcessor实例化前作用InstantiationAwareBeanPostProcessor实例化前代码案例二、实例化前 - 源码分析声明关键点源代码解读前言 在Bean的生命周期中&#xff0c;‘实例化…

Python还是很迷茫的小伙伴进来,教你用图秒懂Python

哈喽&#xff0c;大家好呀&#xff01;今天为大家带来12张图解python&#xff0c;让你们轻松学会了解python。 1.Python 解释器&#xff1a; Python数据结构&#xff1a;变量与运算符&#xff1a;Python 流程控制&#xff1a;Python 文件处理&#xff1a;python 输入输出&…

(三)Vue之模板语法

文章目录插值语法指令语法Vue学习目录上一篇&#xff1a;&#xff08;二&#xff09;初识Vue 下一篇&#xff1a;&#xff08;四&#xff09;Vue之数据绑定 Vue模板语法有2大类&#xff1a; 1.插值语法2.指令语法 插值语法 功能&#xff1a;用于解析标签体内容。 写法&…

lombok入门

目录 lombok概述 lombok安装 Getter、Setter ToString EqualsAndHashCode NotNull 生成构造方法相关注解 Data、Builder Log Cleanup、SneakyThrows lombok概述 以前的Java项目中&#xff0c;充斥着太多不友好的代码&#xff1a;POJO的getter/setter/toString/构造方…

Python迭代器和生成器

在Python中&#xff0c;很多对象都是可以通过for语句来直接遍历的&#xff0c;例如list、string、dict等等&#xff0c;这些对象都可以被称为可迭代对象。至于说哪些对象是可以被迭代访问的&#xff0c;就要了解一下迭代器相关的知识了。 迭代器 迭代器对象要求支持迭代器协议…

cpu设计和实现(总结篇)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 学习cpu&#xff0c;主要还是因为自己对它的原理和实现还有很多不明白、不清楚的地方&#xff0c;本着追根溯源的精神&#xff0c;正好借助于veril…

项目接入腾讯云短信服务SMS实现向用户发送手机验证码

1、自述 早在18年的时候&#xff0c;我就在项目中使用过阿里云的短信服务&#xff0c;现在我上阿里云短信控制台看&#xff0c;还能看到当时创建的短信签名&#xff0c;如下图所示。 出于某种原因&#xff0c;我现在想重新申请一个新的签名&#xff0c;却审批失败了&#xf…

HashMap和Hashtable的详细区别

HashMap和Hashtable的详细区别 一、简述&#xff1a; 1.安全性 Hashtable是线程安全&#xff0c;HashMap是非线程安全。HashMap的性能会高于Hashtable&#xff0c;我们平时使用时若无特殊需求建议使用HashMap&#xff0c;在多线程环境下若使用HashMap需要使用Collections.sy…

MyBatisPlus的使用入门

一、简介 官网&#xff1a;http://mp.baomidou.com/ 参考教程&#xff1a;http://mp.baomidou.com/guide/ MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 二、…

(ICLR-2019)DARTS:可微分架构搜索

DARTS&#xff1a;可微分架构搜索 paper题目&#xff1a;DARTS: DIFFERENTIABLE ARCHITECTURE SEARCH paper是CMU发表在ICLR 2019的工作 paper链接&#xff1a;地址 ABSTRACT 本文通过以可微分的方式制定任务来解决架构搜索的可扩展性挑战。与传统的在离散的、不可微分的搜索空…

【Android App】实战项目之使用OpenCV人脸识别实现找人功能(附源码和演示 超详细)

需要全部代码请点赞关注收藏后评论区留言私信~~~ 人脸识别自古有之&#xff0c;每当官府要捉拿某人时&#xff0c;便在城墙贴出通缉告示并附上那人的肖像。只是该办法依赖人们的回忆与主观判断&#xff0c;指认结果多有出入&#xff0c;算不上什么先进。 如今利用监控摄像头结合…