字节码进阶之java Instrumentation原理详解

news2024/11/15 15:41:39

文章目录

  • 0. 前言
  • 1. 基础
  • 2. Java Instrumentation API
    • 使用示例
  • 3. Java Agent
  • 4. 字节码操作库
  • 5. 实际应用
  • 6. 注意事项和最佳实践

在这里插入图片描述

0. 前言

Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操作,例如性能监控、代码覆盖率分析等。

通过Java提供的Instrumentation接口,我们能够在运行时改变和监控Java程序,这种能力在许多场景中都非常有用,比如性能调优、故障排查、代码覆盖率分析等。然而,Instrumentation接口的使用和字节码操作本身都是高级主题,需要深入理解JVM和字节码的工作原理。

本文我们一起深入讲解Java Instrumentation的原理,并通过实际的例子展示如何使用字节码操作库来实现类的修改。

在字节码的世界里,一切皆可能。让我们开搞!

1. 基础

Java Instrumentation的概念和用途

Java Instrumentation是Java编程语言的一个特性,它允许开发者在Java程序运行时检查和修改应用程序的行为,特别是类和对象的行为。Java提供了一个名为java.lang.instrument的包,其中包含API用于在运行时更改和监控Java类。

Java Instrumentation的主要功能包括:

  1. 在类文件加载到JVM之前,改变类文件的字节码。
  2. 在运行时计算应用程序中对象的大小。
  3. 在运行时更改类的定义。
  4. 为JVM提供一种获取加载到Java应用程序的类文件的方式。

Java Instrumentation的主要用途包括性能监控(例如,计算方法调用的时间)、故障排查、代码覆盖率分析、内存分析、线程分析等。

Java Agent的介绍

Java Agent是Java Instrumentation的一种应用,是一种特殊的Java程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。

Java Agent主要有两种类型:静态agent和动态agent。静态agent在JVM启动时通过命令行参数指定,并在主程序启动之前运行。动态agent则可以在JVM运行时随时加载。

Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。一些常见的Java诊断和监控工具(如JProfiler、VisualVM等)就是通过Java Agent实现的。

2. Java Instrumentation API

Java Instrumentation API

Java Instrumentation API位于java.lang.instrument包内,这个包提供了类和接口供开发者进行字节码操作。

java.lang.instrument包中的关键类和接口

  • Instrumentation:此接口提供了用于实施字节码转换和获取对象的相关信息的方法。

  • ClassFileTransformer:这是一个接口,其中定义了一个transform方法,允许我们在类加载到JVM之前对其进行转换。

  • UnmodifiableClassException:这是一个异常,会在尝试修改或重定义它的状态时,由Instrumentation.retransformClasses方法和Instrumentation.redefineClasses方法抛出。

Instrumentation接口及其方法

Instrumentation接口提供了许多方法,这些方法可以用来改变和检查应用程序的行为,包括:

  • addTransformer(ClassFileTransformer transformer, boolean canRetransform): 添加一个类文件转换器。
  • removeTransformer(ClassFileTransformer transformer): 移除一个类文件转换器。
  • redefineClasses(ClassDefinition... definitions): 重新定义类。
  • retransformClasses(Class<?>... classes): 改变已经加载到JVM的类。
  • getObjectSize(Object objectToSize): 返回对象的大小(以字节为单位)。

ClassFileTransformer接口

ClassFileTransformer接口是Java Instrumentation API中的重要组成部分,它让我们在类加载到虚拟机之前,可以修改类的字节码。

  • transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer): 这个方法会在类被加载到JVM之前被调用,它可以修改类的字节码。

使用示例

假设我们想要跟踪每个方法调用的执行时间,我们可以使用Java Instrumentation和ASM(一个常用的Java字节码操作库)来实现。以下是一个如何实现的例子:

  1. 需要一个ClassFileTransformer来转换类文件:
import org.objectweb.asm.*;

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

public class ProfilingTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.startsWith("my/package/")) { // only transform classes in my package
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, 0);
            ClassVisitor cv = new ProfilingClassAdapter(cw);
            cr.accept(cv, 0);
            return cw.toByteArray();
        } else {
            return classfileBuffer;
        }
    }
}
  1. 需要一个ClassVisitor来开始转换类,并一个MethodVisitor来插入我们的代码
import org.objectweb.asm.*;

public class ProfilingClassAdapter extends ClassVisitor {
    public ProfilingClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        return new ProfilingMethodAdapter(mv, access, name, desc);
    }
}

public class ProfilingMethodAdapter extends MethodVisitor {
    private String methodName;

    public ProfilingMethodAdapter(MethodVisitor mv, int access, String name, String desc) {
        super(Opcodes.ASM5, mv);
        this.methodName = name;
    }

    @Override
    public void visitCode() {
        mv.visitCode();
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
        mv.visitVarInsn(Opcodes.LSTORE, 1); // store start time to local variable
    }

    @Override
    public void visitInsn(int opcode) {
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
            mv.visitVarInsn(Opcodes.LLOAD, 1); // load start time
            mv.visitInsn(Opcodes.LSUB); // get elapsed time by subtracting start time from current time
            mv.visitVarInsn(Opcodes.LSTORE, 3); // store elapsed time to local variable
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("execution time of " + methodName + ": ");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitVarInsn(Opcodes.LLOAD, 3); // load elapsed time
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false); 
        }
        mv.visitInsn(opcode);
    }
}
  1. 需要创建一个Java Agent来使用这个ClassFileTransformer
import java.lang.instrument.Instrumentation;

public class ProfilingAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ProfilingTransformer());
    }
}

以上代码在每个方法的开始和结束时,插入了代码来获取当前时间,计算出方法的执行时间,并将执行时间打印出来。此代码编译后,需要打包为JAR文件,并在JAR的MANIFEST.MF文件中指定Premain-Class属性为ProfilingAgent,然后就可以使用-javaagent:ProfilingAgent.jar参数启动JVM了。

3. Java Agent

Java Agent的概念

Java Agent是一种特殊的Java应用程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。

如何创建Java Agent

要创建Java Agent,你需要创建一个Java类,并实现premain方法。这个方法在主程序的main方法之前执行,它的签名如下:

public static void premain(String agentArgs, Instrumentation inst)

你可以使用Instrumentation参数来添加你的ClassFileTransformer

创建Agent的步骤:实现premain方法、创建MANIFEST.MF文件、打包为JAR

  1. 实现premain方法:在你的Java Agent类中,实现premain方法。
  2. 创建MANIFEST.MF文件:在MANIFEST.MF文件中,设置Premain-Class属性为你的Java Agent类的全名。
  3. 打包为JAR:将你的类和MANIFEST.MF文件打包为一个JAR文件。

在JVM启动时附加Java Agent

当启动JVM时,你可以使用-javaagent参数来指定你的Java Agent JAR文件,如下:

java -javaagent:myAgent.jar -jar myApplication.jar

在运行时动态附加Java Agent(Attach API)

从Java 6开始,你也可以在运行时动态地附加Java Agent。这需要你再实现一个agentmain方法,这个方法在动态附加Java Agent时会被调用。其签名如下:

public static void agentmain(String agentArgs, Instrumentation inst)

要动态附加Java Agent,你需要使用Java的Attach API,例如:

VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarFilePath, agentArgs);
vm.detach();

上述代码会将Java Agent附加到运行中的JVM中,其中pid是你要附加的JVM的进程ID,agentJarFilePath是你的Java Agent JAR文件的路径,agentArgs是传递给agentmain方法的参数。

4. 字节码操作库

使用字节码操作库实现类的修改

字节码操作库能够帮助我们操作和修改Java类的字节码。这些库提供了一种在运行时修改Java类的方法,例如添加、修改或删除类的字段和方法,改变类的继承结构等。这些操作是通过直接修改类的字节码来实现的。

ASM

ASM是一个通用的Java字节码操纵和分析框架。它可以用来修改现有类或者动态生成新的类。ASM提供了一些核心API用于直接操作字节码,这使得ASM非常强大,但也意味着使用ASM需要了解Java字节码的详细知识。

ByteBuddy

Byte Buddy是一个新的库,用于创建和修改Java类。Byte Buddy的API设计得非常友好,适合那些不熟悉Java字节码的开发者使用。Byte Buddy也提供了一些高级特性,如方法调用代理和Java Agent的支持。

CGLIB

CGLIB(Code Generation Library)是一个开源的项目,它提供了一些强大的高级功能,如方法拦截和创建代理类。CGLIB通过使用ASM框架在运行时动态生成和加载新的Java类。CGLIB常常被用在许多流行的开源项目中,如Spring和Hibernate。

Javassist

Javassist(Java Programming Assistant)使Java字节码的编辑变得简单。它是一个类库,为修改字节码提供了两级的接口:源码级和字节码级。源码级接口允许以源码形式(如字符串)修改类的字段和方法,字节码级接口允许直接修改字节码。

对比和选择适合的库

库名称描述优点缺点
ASM一个通用的Java字节码操纵和分析框架提供了一些核心API用于直接操作字节码,非常强大使用ASM需要了解Java字节码的详细知识
ByteBuddy用于创建和修改Java类的库API设计得非常友好,适合那些不熟悉Java字节码的开发者使用,提供了一些高级特性相比于ASM,可能无法进行一些更底层的操作
CGLIB提供了一些强大的高级功能,如方法拦截和创建代理类常常被用在许多流行的开源项目中,如Spring和Hibernate使用ASM框架,需要一定的字节码知识
Javassist使Java字节码的编辑变得简单提供了源码级和字节码级接口,使得操作更直观虽然方便,但在性能和灵活性上可能不如ASM

这只是一个大概的比较,具体使用哪个库还是要根据个人的实际需求和偏好来决定。每个库都有各自的优点和特色,了解它们的特性和优缺点,可以帮助你做出更好的决定。

  • 如果你需要最大的灵活性,并且不介意处理底层的字节码细节,那么ASM可能是最好的选择。
  • 如果你希望有一个简单、易用的API,并且需要一些高级特性,如方法调用代理,那么Byte Buddy可能是最好的选择。
  • 如果你需要生成代理类,或者你正在使用依赖于CGLIB的库(如Spring和Hibernate),那么CGLIB可能是最好的选择。
  • 如果你希望能够以源代码形式修改类,那么Javassist可能是最好的选择。

5. 实际应用

如何使用Java Instrumentation实现性能监控

为了使用Java Instrumentation实现性能监控,可以创建一个Java Agent,这个Agent会添加代码来跟踪每个方法的执行时间。

如何使用Java Instrumentation实现代码覆盖率分析

代码覆盖率分析是检查你的测试用例覆盖了多少代码的一种方法。可以使用Java Agent来修改类的字节码,添加代码来跟踪每个方法和代码块的执行情况。

public class CodeCoverageMethodAdapter extends MethodVisitor {
    private String className;
    private String methodName;

    public CodeCoverageMethodAdapter(MethodVisitor mv, String className, String methodName) {
        super(Opcodes.ASM5, mv);
        this.className = className;
        this.methodName = methodName;
    }

    // 添加代码来跟踪方法的执行情况
    @Override
    public void visitCode() {
        mv.visitCode();
        mv.visitFieldInsn(Opcodes.GETSTATIC, "my/package/CodeCoverage", "executedMethods", "Ljava/util/Set;");
        mv.visitLdcInsn(className + "." + methodName);
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true);
        mv.visitInsn(Opcodes.POP);
    }
}

CodeCoverage是一个你自己创建的类,它有一个Set类型的静态字段executedMethods,用于存储已经执行过的方法。然后,你需要在每个方法的开始处添加代码,将当前方法添加到executedMethods中。

如何使用Java Instrumentation实现故障排查和诊断工具

故障排查和诊断工具通常需要获取一些低级别的信息,例如对象的创建和销毁,方法的调用情况等。可以使用Java Agent来收集这些信息。下面是一个跟踪对象创建的例子:

public class ObjectCreationClassAdapter extends ClassVisitor {
    public ObjectCreationClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if ("<init>".equals(name)) { // 如果是构造方法
            mv = new ObjectCreationMethodAdapter(mv);
        }
        return mv;
    }
}

public class ObjectCreationMethodAdapter extends MethodVisitor {
    public ObjectCreationMethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    // 在对象创建后添加代码来跟踪对象的创建
    @Override
    public void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "my/package/Tracker", "trackObjectCreation", "()V", false);
        }
        mv.visitInsn(opcode);
    }
}

<>Tracker是一个创建的类,它有一个静态方法trackObjectCreation,用于跟踪对象的创建。然后,你需要在每个构造方法的结束处添加代码,调用trackObjectCreation方法。

6. 注意事项和最佳实践

避免破坏类的结构和逻辑

字节码操作是一项强大的功能,但如果使用不当,可能会导致一些意想不到的问题。在进行字节码操作时,你需要确保不要破坏类的结构和逻辑。例如,不要删除或更改类的重要方法,不要添加破坏类行为的代码等。

优雅地处理异常

在字节码操作的过程中,可能会出现各种各样的异常。你需要确保在你的代码中妥善处理这些异常。一般来说,你应该尽量减少对异常的静默处理,而是应该将异常记录下来,便于后续的问题排查。

注意性能影响

虽然字节码操作可以实现强大的功能,但它同时也可能对应用程序的性能产生影响。你需要确保你的字节码操作不会对性能产生太大的影响。在进行性能敏感的字节码操作时,你可能需要对你的代码进行性能测试,以确保它不会成为性能瓶颈。

一些常见的性能影响因素包括:增加过多的方法调用,添加大量的同步代码,过度使用反射等。

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

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

相关文章

记录一次线下渗透电气照明系统(分析与实战)

项目地址:https://github.com/MartinxMax/S-Clustr 注意 本次行动未造成任何设备损坏,并在道德允许范围内测试 >ethical hacking< 发现过程 在路途中,发现一个未锁的配电柜,身为一个电工自然免不了好奇心(非专业人士请勿模仿,操作不当的话220V人就直了) 根据照片,简…

c++踩坑点,类型转换

std::string转换到PVOID std::string转换到PVOID的方式如下 这样的话成功转换 “const char *” 类型的实参与 “WCHAR *” “const char *” 类型的实参与 “WCHAR *” 类型的形参不兼容 可以看到这种报错&#xff0c;可以直接强转如下&#xff1a; 但是在我们这里不适…

论文总结:EXPRESSIVE SPEECH-DRIVEN FACIAL ANIMATION WITH CONTROLLABLE EMOTIONS

存在的问题:现有的语音驱动面部动画方法可以产生令人满意的嘴部运动和嘴唇同步,但在情感表达和情感控制方面存在不足。 作者使用wav2vec2.0和transformer encoder来获取文本向量和全局风格向量。将其拼接起来通过Auido2FLAME模块来预测flame的参数,Auido2FLAME由多层CNN组成…

大厂秋招真题【贪心】大疆20230813秋招T1-矩形田地

题目描述与示例 题目描述 给定一个矩形田地&#xff0c;其高度为 h 且宽度为 w。同时&#xff0c;你将获得两个整数数组 horizontalCutting 和 verticalCutting&#xff0c;其中 horizontalCutting[i] 表示从矩形田地顶部到第 i 个水平切口的距离&#xff0c;verticalCutting…

【二维差分】ICPC南京 A

https://codeforces.com/gym/104128/problem/A 题意 思路 二维差分经典模型 考虑如果没有洞那么经历操作之后会剩下什么样子的袋鼠。发现上下左右移动可以看成是边界在移动&#xff0c;边界一直保持一个原初的矩形形状&#xff0c;而且上下移动和左右移动没有任何关系。一旦…

自然语言处理---Transformer机制详解之GPT模型介绍

1 GPT介绍 GPT是OpenAI公司提出的一种语言预训练模型.OpenAI在论文<< Improving Language Understanding by Generative Pre-Training >>中提出GPT模型.OpenAI后续又在论文<< Language Models are Unsupervised Multitask Learners >>中提出GPT2模型.…

自然语言处理---RNN、LSTM、GRU模型

RNN模型 RNN模型概述 RNN(Recurrent Neural Network)&#xff0c;中文称作循环神经网络&#xff0c;它一般以序列数据为输入&#xff0c;通过网络内部的结构设计有效捕捉序列之间的关系特征&#xff0c;一般也是以序列形式进行输出。RNN的循环机制使模型隐层上一时间步产生的…

MIPS指令集摘要

目录 MIPS指令R I J三种格式 MIPS五种寻址方式 立即数寻址 寄存器寻址 基址寻址 PC相对寻址 伪直接寻址 WinMIPS64汇编指令 助记 从内存中加载数据 lb lbu lh lhu lw lwu ld l.d lui 存储数据到内存 sb sh sw sd s.d 算术运算 daddi daddui dadd…

自然语言处理---Transformer机制详解之GPT2模型介绍

1 GPT2的架构 从模型架构上看, GPT2并没有特别新颖的架构, 它和只带有解码器模块的Transformer很像. 所谓语言模型, 作用就是根据已有句子的一部分, 来预测下一个单词会是什么. 现实应用中大家最熟悉的一个语言模型应用, 就是智能手机上的输入法, 它可以根据当前输入的内容智…

hdlbits系列verilog解答(向量)-11

文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 向量用于使用一个名称对相关信号进行分组,以使其更易于操作。例如,声明一个名为 8 位向量, wire [7:0] w; 该向量在 w 功能上等效于具有 8 个单独的线网(wire w0, w1, w2, w3, w4, w5, w6, w7)。 与C语言…

【Ubuntu系统搭建STM32开发环境(国内镜像全程快速配置)】

源于本人失败的经历苦心研究 虚拟机安装ubuntu换源VScode安装安装Java环境安装cubemx安装 arm-Linux-gcc安装gdb server安装OpenOCD 虚拟机安装ubuntu 系统镜像可以在阿里云镜像站且下载速度很快。 选择安装的版本。 我选择的是&#xff1a;ubuntu-22.10-desktop-amd64.iso。…

Hadoop3教程(三十四):(生产调优篇)MapReduce生产经验汇总

文章目录 &#xff08;164&#xff09;MR跑得慢的原因&#xff08;165&#xff09;MR常用调优参数Map阶段Reduce阶段 &#xff08;166&#xff09;MR数据倾斜问题参考文献 &#xff08;164&#xff09;MR跑得慢的原因 MR程序执行效率的瓶颈&#xff0c;或者说当你觉得你的MR程…

json-server工具准备后端接口服务环境

1.安装全局工具json-server&#xff08;全局工具仅需要安装一次&#xff09; 官网&#xff1a;json-server - npm 点击Getting started可以查看使用方法 在终端中输入yarn global add json-server或npm i json-server -g 2.代码根目录新建一个db目录 3.在db目录下创建index…

061:mapboxGL利用fitBounds同时将多个点放在可视范围内

第061个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载geojson数据,利用fitBounds同时将多个点放在可视范围内。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共134行)相关API参考:专栏目标示例…

linux性能分析(四)如何学习linux性能优化

一 如何学习linux性能优化 强调&#xff1a; 由于知识记忆曲线以及某些知识点不常用,所以一定要注重复习思考&#xff1a; 如何进行能力转义以及能力嫁接? --> 真正站在巨人的肩膀上性能调优的目的&#xff1a; 不影响系统稳定性的资源最大利用化补充&#xff1a; 性能…

【Spring Cloud】如何确定微服务项目的Spring Boot、Spring Cloud、Spring Cloud Alibaba的版本

文章目录 1. 版本选择2. 用脚手架快速生成微服务的pom.xml3. 创建一个父工程4. 代码地址 本文描述如何确定微服务项目的Spring Boot、Spring Cloud、Spring Cloud Alibaba的版本。 1. 版本选择 我们知道Spring Boot、Spring Cloud、Spring Cloud Alibaba的版本选择一致性非常重…

CSS常见的预处理器有哪些?

CSS常见的预处理器有以下几种&#xff1a; 1&#xff1a;Sass&#xff08;Syntactically Awesome Style Sheets&#xff09;&#xff1a;Sass是一种成熟且广泛使用的CSS预处理器。它提供了许多功能&#xff0c;如变量、嵌套规则、混合&#xff08;Mixins&#xff09;、继承&am…

RT-Thread学习笔记(四):RT-Thread Studio工具使用

RT-Thread Studio工具使用 官网详细资料实用操作1. 查看 RT-Thread RTOS API 文档2.打开已创建的工程3.添加头文件路径4. 如何设置生成hex文件5.新建工程 官网详细资料 RT-Thread Studio 用户手册 实用操作 1. 查看 RT-Thread RTOS API 文档 2.打开已创建的工程 如果打开项目…

库的操作【MySQL】

文章目录 创建数据库字符集和校验规则概念分类例子 查看数据库显示创建语句修改数据库删除数据库备份和恢复备份恢复 创建数据库 SQL: CREATE DATABASE [IF NOT EXISTS] db_name [[DEFAULT] CHARSETcharset_name] [[DEFAULT] COLLATEcollation_name];其中&#xff0c;大写的单…

数据结构与算法设计分析——动态规划

目录 一、动态规划的定义二、动态规划的基本要素和主要步骤&#xff08;一&#xff09;最优子结构&#xff08;二&#xff09;重叠子问题 三、贪心法、分治法和动态规划的对比&#xff08;一&#xff09;贪心法&#xff08;二&#xff09;分治法&#xff08;三&#xff09;动态…