字节码调教的入口 —— JVM 的寄生插件 javaagent 那些事

字节码增强技术之 Java Agent 入门 前言 分布式链路追踪中为了获取服务之间调用链信息,采集器通常需要在方法的前后做埋点。在 Java 生态中,常见的埋点方式有两种: 依赖 SDK 手动埋点; 利用 Java Agent 技术来做无侵入埋点。 我们所熟知的分布式监控系统,是 Zipkin 开始的,最经典的是搞懂 X-B3 Ttrace 协议,使用 Brave SDK,手动埋点生成 Trace。但是 SDK 埋点的方式,对业务代码存在侵入性,当升级埋点时,必须要做代码的变更。 那么如何和业务逻辑解绑呢? Java 还提供了另外一种方式: 阅读详情

Java Instrumentation 包

Java Instrumentation 概述

Java Instrumentation 这个技术看起来非常神秘,很少有书会详细介绍。但是有很多工具是基于 Instrumentation 来实现的:

  • APM 产品: pinpoint、skywalking、newrelic、听云的 APM 产品等都基于 Instrumentation 实现
  • 热部署工具:Intellij idea 的 HotSwap、Jrebel 等
  • Java 诊断工具:Arthas、Btrace 等

由于对字节码修改功能的巨大需求,JDK 从 JDK5 版本开始引入了java.lang.instrument 包。它可以通过 addTransformer 方法设置一个 ClassFileTransformer,可以在这个 ClassFileTransformer 实现类的转换。

JDK 1.5 支持静态 Instrumentation,基本的思路是在 JVM 启动的时候添加一个代理(javaagent),每个代理是一个 jar 包,其 MANIFEST.MF 文件里指定了代理类,这个代理类包含一个 premain 方法。JVM 在类加载时候会先执行代理类的 premain 方法,再执行 Java 程序本身的 main 方法,这就是 premain 名字的来源。在 premain 方法中可以对加载前的 class 文件进行修改。这种机制可以认为是虚拟机级别的 AOP,无需对原有应用做任何修改,就可以实现类的动态修改和增强。

从 JDK 1.6 开始支持更加强大的动态 Instrument,在JVM 启动后通过 Attach API 远程加载,后面会详细介绍。

本文会分为 javaagent 和动态 Attach 两个部分来介绍

Java Instrumentation 核心方法

Instrumentation 是 java.lang.instrument 包下的一个接口,这个接口的方法提供了注册类文件转换器、获取所有已加载的类等功能,允许我们在对已加载和未加载的类进行修改,实现 AOP、性能监控等功能。

常用的方法如下:

/**
 * 为 Instrumentation 注册一个类文件转换器,可以修改读取类文件字节码
 */
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

/**
 * 对JVM已经加载的类重新触发类加载
 */
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

/**
 * 获取当前 JVM 加载的所有类对象
 */
Class[] getAllLoadedClasses()

它的 addTransformer 给 Instrumentation 注册一个 transformer,transformer 是 ClassFileTransformer 接口的实例,这个接口就只有一个 transform 方法,调用 addTransformer 设置 transformer 以后,后续JVM 加载所有类之前都会被这个 transform 方法拦截,这个方法接收原类文件的字节数组,返回转换过的字节数组,在这个方法中可以做任意的类文件改写。

下面是一个空的 ClassFileTransformer 的实现:

public class MyClassTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException {
        // 在这里读取、转换类文件
        return classBytes;
    }
}

接下来我们来介绍本文的主角之一 javaagent。

Javaagent 介绍

Javaagent 是一个特殊的 jar 包,它并不能单独启动的,而必须依附于一个 JVM 进程,可以看作是 JVM 的一个寄生插件,使用 Instrumentation 的 API 用来读取和改写当前 JVM 的类文件。

Agent 的两种使用方式

它有两种使用方式:

  • 在 JVM 启动的时候加载,通过 javaagent 启动参数 java -javaagent:myagent.jar MyMain,这种方式在程序 main 方法执行之前执行 agent 中的 premain 方法
  • 在 JVM 启动后 Attach,通过 Attach API 进行加载,这种方式会在 agent 加载以后执行 agentmain 方法 premain 和 agentmain 方法签名如下:
public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception

public static void agentmain(String agentArgument, Instrumentation instrumentation) throws Exception

这两个方法都有两个参数

  • 第一个 agentArgument 是 agent 的启动参数,可以在 JVM 启动命令行中设置,比如java -javaagent:<jarfile>=appId:agent-demo,agentType:singleJar test.jar的情况下 agentArgument 的值为 "appId:agent-demo,agentType:singleJar"。

  • 第二个 instrumentation 是 java.lang.instrument.Instrumentation 的实例,可以通过 addTransformer 方法设置一个 ClassFileTransformer。

第一种 premain 方式的加载时序如下:

alt

Agent 打包

为了能够以 javaagent 的方式运行 premain 和 agentmain 方法,我们需要将其打包成 jar 包,并在其中的 MANIFEST.MF 配置文件中,指定 Premain-class 等信息,一个典型的生成好的 MANIFEST.MF 内容如下

为了能够以 javaagent 的方式运行 premain 和 agentmain 方法,我们需要将其打包成 jar 包,并在其中的 MANIFEST.MF 配置文件中,指定 Premain-class 等信息,一个典型的生成好的 MANIFEST.MF 内容如下

下面是一个可以帮助生成上面 MANIFEST.MF 的 maven 配置

<build>
  <finalName>my-javaagent</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifestEntries>
            <Agent-Class>me.geek01.javaagent.AgentMain</Agent-Class>
            <Premain-Class>me.geek01.javaagent.AgentMain</Premain-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
          </manifestEntries>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>

Agent 使用方式一:JVM 启动参数

下面使用 javaagent 实现简单的函数调用栈跟踪,以下面的代码为例:

public class MyTest {
    public static void main(String[] args) {
        new MyTest().foo();
    }
    public void foo() {
        bar1();
        bar2();
    }

    public void bar1() {
    }

    public void bar2() {
    }
}

通过 javaagent 启动参数的方式在每个函数进入和结束时都打印一行日志,实现调用过程的追踪的效果。

核心的方法 instrument 的逻辑如下:

public static class MyMethodVisitor extends AdviceAdapter {

    @Override
    protected void onMethodEnter() {
        // 在方法开始处插入 <<<enter xxx
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn("<<<enter " + this.getName());
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        super.onMethodEnter();
    }

    @Override
    protected void onMethodExit(int opcode) {
        super.onMethodExit(opcode);
        // 在方法结束处插入 <<<exit xxx
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn(">>>exit " + this.getName());
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }
}

把 agent 打包生成 my-trace-agent.jar,添加 agent 启动 MyTest 类

java -javaagent:/path_to/my-trace-agent.jar MyTest

可以看到输出结果如下:

<<<enter main
<<<enter foo
<<<enter bar1
>>>exit bar1
<<<enter bar2
>>>exit bar2
>>>exit foo
>>>exit main

通过上面的方式,我们在不修改 MyTest 类源码的情况下实现了调用链跟踪的效果。更加健壮和完善的调用链跟踪实现会在后面的 APM 章节详细介绍。

Agent 使用方式二:Attach API 使用

在 JDK5 中,开发者只能 JVM 启动时指定一个 javaagent 在 premain 中操作字节码,Instrumentation 也仅限于 main 函数执行前,这样的方式存在一定的局限性。从 JDK6 开始引入了动态 Attach Agent 的方案,除了在命令行中指定 javaagent,现在可以通过 Attach API 远程加载。我们常用的 jstack、arthas 等工具都是通过 Attach 机制实现的。

接下来我们会结合跨进程通信中的信号和 Unix 域套接字来看 JVM Attach API 的实现原理

JVM Attach API 基本使用

下面以一个实际的例子来演示动态 Attach API 的使用,代码中有一个 main 方法,每隔 3s 输出 foo 方法的返回值 100,接下来动态 Attach 上 MyTestMain 进程,修改 foo 的字节码,让 foo 方法返回 50。

public class MyTestMain {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(foo());
            TimeUnit.SECONDS.sleep(3);
        }
    }

    public static int foo() {
        return 100; // 修改后 return 50;
    }
}

步骤如下:

1、编写 Attach Agent,对 foo 方法做注入,完整的代码见:github.com/arthur-zhan…

动态 Attach 的 agent 与通过 JVM 启动 javaagent 参数指定的 agent jar 包的方式有所不同,动态 Attach 的 agent 会执行 agentmain 方法,而不是 premain 方法。

public class AgentMain {
    public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        System.out.println("agentmain called");
        inst.addTransformer(new MyClassFileTransformer(), true);
        Class classes[] = inst.getAllLoadedClasses();
        for (int i = 0; i < classes.length; i++) {
            if (classes[i].getName().equals("MyTestMain")) {
                System.out.println("Reloading: " + classes[i].getName());
                inst.retransformClasses(classes[i]);
                break;
            }
        }
    }
}

2、因为是跨进程通信,Attach 的发起端是一个独立的 java 程序,这个 java 程序会调用 VirtualMachine.attach 方法开始和目标 JVM 进行跨进程通信。

public class MyAttachMain {
    public static void main(String[] args) throws Exception {
        VirtualMachine vm = VirtualMachine.attach(args[0]);
        try {
            vm.loadAgent("/path/to/agent.jar");
        } finally {
            vm.detach();
        }
    }
}

使用 jps 查询到 MyTestMain 的进程 id,

java -cp /path/to/your/tools.jar:. MyAttachMain pid

可以看到 MyTestMain 的输出的 foo 方法已经返回了 50。

java -cp . MyTestMain

100
100
100
agentmain called
Reloading: MyTestMain
50
50
50

JVM Attach API 的底层原理

JVM Attach API 的实现主要基于信号和 Unix 域套接字,接下来详细介绍这两部分的内容。

信号是什么

信号是某事件发生时对进程的通知机制,也被称为“软件中断”。信号可以看做是一种非常轻量级的进程间通信,信号由一个进程发送给另外一个进程,只不过是经由内核作为一个中间人发出,信号最初的目的是用来指定杀死进程的不同方式。

每个信号都有一个名字,以 "SIG" 开头,最熟知的信号应该是 SIGINT,我们在终端执行某个应用程序的过程中按下 Ctrl+C 一般会终止正在执行的进程,正是因为按下 Ctrl+C 会发送 SIGINT 信号给目标程序。

每个信号都有一个唯一的数字标识,从 1 开始,下面是常见的信号量列表:

alt

在 Linux 中,一个前台进程可以使用 Ctrl+C 进行终止,对于后台进程需要使用 kill 加进程号的方式来终止,kill 命令是通过发送信号给目标进程来实现终止进程的功能。默认情况下,kill 命令发送的是编号为 15 的 SIGTERM 信号,这个信号可以被进程捕获,选择忽略或正常退出。目标进程如果没有自定义处理这个信号,就会被终止。对于那些忽略 SIGTERM 信号的进程,则需要编号为 9 的 SIGKILL 信号强行杀死进程,SIGKILL 信号不能被忽略也不能被捕获和自定义处理。

下面写了一段 C 代码,自定义处理了 SIGQUIT、SIGINT、SIGTERM 信号

signal.c

static void signal_handler(int signal_no) {
    if (signal_no == SIGQUIT) {
        printf("quit signal receive: %d\n", signal_no);
    } else if (signal_no == SIGTERM) {
        printf("term signal receive: %d\n", signal_no);
    } else if (signal_no == SIGINT) {
        printf("interrupt signal receive: %d\n", signal_no);
    }
}

int main() {
    signal(SIGQUIT, signal_handler);
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    for (int i = 0;; i++) {
        printf("%d\n", i);
        sleep(3);
    }
}

编译运行上面的 signal.c 文件

gcc signal.c -o signal
./signal

这种情况下,在终端中Ctrl+C,kill -3,kill -15都没有办法杀掉这个进程,只能用kill -9

0
^Cinterrupt signal receive: 2     // Ctrl+C
1
2
term signal receive: 15           // kill pid
3
4
5
quit signal receive: 3             // kill -3 
6
7
8
[1]    46831 killed     ./signal  // kill -9 成功杀死进程

JVM 对 SIGQUIT 的默认行为是打印所有运行线程的堆栈信息,在类 Unix 系统中,可以通过使用命令 kill -3 pid 来发送 SIGQUIT 信号。运行上面的 MyTestMain,使用 jps 找到整个 JVM 的进程 id,执行 kill -3 pid,在终端就可以看到打印了所有的线程的调用栈信息:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.51-b03 mixed mode):

"Service Thread" #8 daemon prio=9 os_prio=31 tid=0x00007fe060821000 nid=0x4403 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
...
"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fe061008800 nid=0x3403 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"main" #1 prio=5 os_prio=31 tid=0x00007fe060003800 nid=0x1003 waiting on condition [0x000070000d203000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
 at java.lang.Thread.sleep(Native Method)
 at java.lang.Thread.sleep(Thread.java:340)
 at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
 at MyTestMain.main(MyTestMain.java:10)

Unix 域套接字(Unix Domain Socket)

使用 TCP 和 UDP 进行 socket 通信是一种广为人知的 socket 使用方式,除了这种方式还有一种称为 Unix 域套接字的方式,可以实现同一主机上的进程间通信。虽然使用 127.0.0.1 环回地址也可以通过网络实现同一主机的进程间通信,但 Unix 域套接字更可靠、效率更高。Docker 守护进程(Docker daemon)使用了 Unix 域套接字,容器中的进程可以通过它与Docker 守护进程进行通信。MySQL 同样提供了域套接字进行访问的方式。

Unix 域套接字是什么?

Unix 域套接字是一个文件,通过 ls 命令可以看到

ls -l
srwxrwxr-x. 1 ya ya        0 9月   8 00:26 tmp.sock

两个进程通过读写这个文件就实现了进程间的信息传递。文件的拥有者和权限决定了谁可以读写这个套接字。

与普通套接字的区别是什么?
  • Unix 域套接字更加高效,Unix 套接字不用进行协议处理,不需要计算序列号,也不需要发送确认报文,只需要复制数据即可
  • Unix 域套接字是可靠的,不会丢失报文,普通套接字是为不可靠通信设计的
  • Unix 域套接字的代码可以非常简单的修改转为普通套接字

下面是一个简单的 C 实现的域套接字的例子

.
├── client.c
└── server.c

server.c 充当 Unix 域套接字服务器,启动后会在当前目录生成一个名为 tmp.sock 的 Unix 域套接字文件,它读取客户端写入的内容并输出。

server.c

int main() {
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "tmp.sock");
    int ret = bind(fd, (struct sockaddr *) &addr, sizeof(addr));
    listen(fd, 5)
    
    int accept_fd;
    char buf[100];
    while (1) {
        accept_fd = accept(fd, NULL, NULL)) == -1);
        while ((ret = read(accept_fd, buf, sizeof(buf))) > 0) {
            // 输出客户端传过来的数据
            printf("receive %u bytes: %s\n", ret, buf);
        }
}

客户端的代码如下:

client.c
int main() {
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "tmp.sock");

    connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1
    
    int rc;
    char buf[100];
    // 读取终端标准输入的内容,写入到 Unix 域套接字文件中
    while ((rc = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
        write(fd, buf, rc);
    }
}

在命令行中进行编译和执行

gcc server.c -o server
gcc client.c -o client

启动两个终端,一个启动 server 端,一个启动 client 端

./server
./client

可以看到当前目录生成了一个 "tmp.sock" 文件

ls -l

srwxrwxr-x. 1 ya ya    0 9月   8 00:08 tmp.sock

在 client 输入 hello,在 server 的终端就可以看到

./server
receive 6 bytes: hello

JVM Attach 过程分析

执行 MyAttachMain,当指定一个不存在的 JVM 进程时,会出现如下的错误:

java -cp /path/to/your/tools.jar:. MyAttachMain 1234
Exception in thread "main" java.io.IOException: No such process
 at sun.tools.attach.LinuxVirtualMachine.sendQuitTo(Native Method)
 at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:91)
 at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63)
 at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208)
 at MyAttachMain.main(MyAttachMain.java:8)

可以看到 VirtualMachine.attach 最终调用了 sendQuitTo 方法,这是一个 native 的方法,底层就是发送了 SIGQUIT 信号给目标 JVM 进程。

前面信号部分我们介绍过,JVM 对 SIGQUIT 的默认行为是 dump 当前的线程堆栈,那为什么调用 VirtualMachine.attach 没有输出调用栈堆栈呢?

对于 Attach 的发起方,假设目标进程为 12345,这部分的详细的过程如下:

1、Attach 端检查临时文件目录是否有 .java_pid12345 文件

这个文件是一个 UNIX 域套接字文件,由 Attach 成功以后的目标 JVM 进程生成。如果这个文件存在,说明正在 Attach 中,可以用这个 socket 进行下一步的通信。如果这个文件不存在则创建一个 .attach_pid12345 文件,这部分的伪代码如下:

String tmpdir = "/tmp";
File socketFile = new File(tmpdir,  ".java_pid" + pid);
if (socketFile.exists()) {
    File attachFile = new File(tmpdir, ".attach_pid" + pid);
    createAttachFile(attachFile.getPath());
}

2、Attach 端检查如果没有 .java_pid12345 文件,创建完 .attach_pid12345 文件以后发送 SIGQUIT 信号给目标 JVM。然后每隔 200ms 检查一次 socket 文件是否已经生成,5s 以后还没有生成则退出,如果有生成则进行 socket 通信

3、对于目标 JVM 进程而言,它的 Signal Dispatcher 线程收到 SIGQUIT 信号以后,会检查 .attach_pid12345 文件是否存在。

  • 目标 JVM 如果发现 .attach_pid12345 不存在,则认为这不是一个 attach 操作,执行默认行为,输出当前所有线程的堆栈
  • 目标 JVM 如果发现 .attach_pid12345 存在,则认为这是一个 attach 操作,会启动 Attach Listener 线程,负责处理 Attach 请求,同时创建名为 .java_pid12345 的 socket 文件,监听 socket。 源码中 /hotspot/src/share/vm/runtime/os.cpp 这一部分处理的逻辑如下:
#define SIGBREAK SIGQUIT

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  while (true) {
    int sig;
    {
    switch (sig) {
      case SIGBREAK: { 
        // Check if the signal is a trigger to start the Attach Listener - in that
        // case don't print stack traces.
        if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
          continue;
        }
        ...
        // Print stack traces
    }
}

AttachListener 的 is_init_trigger 在 .attach_pid12345 文件存在的情况下会新建 .java_pid12345 套接字文件,同时监听此套接字,准备 Attach 端发送数据。

那 Attach 端和目标进程用 socket 传递了什么信息呢?可以通过 strace 的方式看到 Attach 端究竟往 socket 里面写了什么:

sudo strace -f java -cp /usr/local/jdk/lib/tools.jar:. MyAttachMain 12345  2> strace.out

...
5841 [pid  3869] socket(AF_LOCAL, SOCK_STREAM, 0) = 5
5842 [pid  3869] connect(5, {sa_family=AF_LOCAL, sun_path="/tmp/.java_pid12345"}, 110)      = 0
5843 [pid  3869] write(5, "1", 1)            = 1
5844 [pid  3869] write(5, "\0", 1)           = 1
5845 [pid  3869] write(5, "load", 4)         = 4
5846 [pid  3869] write(5, "\0", 1)           = 1
5847 [pid  3869] write(5, "instrument", 10)  = 10
5848 [pid  3869] write(5, "\0", 1)           = 1
5849 [pid  3869] write(5, "false", 5)        = 5
5850 [pid  3869] write(5, "\0", 1)           = 1
5855 [pid  3869] write(5, "/home/ya/agent.jar"..., 18 <unfinished ...>

可以看到往 socket 写入的内容如下:

1
\0
load
\0
instrument
\0
false
\0
/home/ya/agent.jar
\0

数据之间用 \0 字符分隔,第一行的 1 表示协议版本,接下来是发送指令 "load instrument false /home/ya/agent.jar" 给目标 JVM,目标 JVM 收到这些数据以后就可以加载相应的 agent jar 包进行字节码的改写。

如果从 socket 的角度来看,VirtualMachine.attach 方法相当于三次握手建连,VirtualMachine.loadAgent 则是握手成功之后发送数据,VirtualMachine.detach 相当于四次挥手断开连接。

这个过程如下图所示:

alt

小结

本文讲解了 javaagent,一起来回顾一下要点:

  • 第一,javaagent 是一个使用 instrumentation 的 API 用来改写类文件的 jar 包,可以看作是 JVM 的一个寄生插件。
  • 第二,javaagent 有两个重要的入口类:Premain-Class 和 Agent-Class,分别对应入口函数 premain 和 agentmain,其中 agentmain 可以采用远程 attach API 的方式远程挂载另一个 JVM 进程。

本文由 mdnice 多平台发布

嵌入式 视频编码(H264)hi3518 这几天在编写视频录制模块,所以,闲暇之余,又粗粗的整理了一下,主要是API,以备不时之用   视频编码(H264)hi3518" />     摄像头获取的模拟信号通过经芯片处理(我们使用的是CX25825),将模拟信号转成数字信号,产生标准的ITU 656 YUV格式的数字信号以帧为单位送到编码卡上的DSP和内存中。分别供视频实时预览、移动侦测处理以及编码等使用。其中编码的作用是将编码卡内存中 阅读详情

相关推荐

Java agent 使用详解

Java agent 使用详解

congge 8982

SQL Server性能调教系列入口

SQL Server性能调教系列入口 http://www.cnblogs.com/changbluesky/archive/2010/07/08/1761780.html

我的Oracle DBA之路 1107

STM32串口通信实战:从CubeMX配置到printf重定向全解析

串口通信是嵌入式系统中最基础且广泛使用的数据交换方式,它基于异步串行通信原理,通过TX(发送)和RX(接收)两根信号线实现设备间的全双工通信。其技术价值在于协议简单、硬件成本低、可靠性高,是单片机与上位机调试、传感器数据采集、设备控制等场景的首选方案。在STM32等ARM Cortex-M系列MCU中,USART外设通过硬件自动处理时序,大大减轻了CPU负担。本文聚焦于嵌入式竞赛与开发中的高频需求,以STM32G431RBT6为例,详解通过CubeMX配置USART1、实现按键触发数据发送、并完成print

weixin_30603633的博客 545

Java 中的bind 绑定

概念 一个Java程序的执行要经过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言。当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法还是子类的重写方法呢,这应该是我们在初学Java时遇到的问题。这里我们将确定这种调用何种方法实现或者变量的操作叫做绑定。可以将绑定理解成Java多态特性的底层原理。 分类 在Java中存在两种绑定方式,一种为静态绑定,又称作早期绑定。另一种就是动态绑定,亦称为后期绑定。 区别 静态绑定发生在编译时期,动态绑定发生在运行时

weixin_45563088的博客 7951

华硕天选3 win11+ubuntu20.04双硬盘双系统配置(包括ubuntu重启后无法成功进入、没有wifi图标的设置)

1、制作系统盘 2、分区 3、重启电脑按F2

baidu_38431960的博客 1万+

Cocos Creator3.5.2使用Spine切换皮肤

3、然后获取当前spine中是否有皮肤数据,如果spine连皮肤数据都没有,接着写可能会error,这里的 skins[skin],是判断皮肤列表中是否有需要更换的皮肤。2、因为spine往往意味着异步加载,换肤时也经常出现频繁异步的情况,所以下一步我们要可以判断当前spine组件里是否已经加载好了spine文件。换肤前隐藏好需要换肤的节点,避免穿帮;官网文档的换肤流程太简化,对于实际使用来说会有各种各样的问题。1、首先获取对应的spine组件。这里的skin自然是外部传入的需要换肤的皮肤名。

tc654321的博客 1817

我的知识梳理-字符串拼接

以下内容基于jdk1.8 测试代码 public static void main(String[] args) { String a = "a"; for (int i = 0; i < 100; i++) { a += "a"; } } 字节码 public static main([Ljava...

dstears的博客 300

安卓光速虚拟机ADB调试实战:从密钥配置到设备连接

本文详细介绍了在安卓光速虚拟机中进行ADB调试的完整实战流程。核心在于通过密钥配置解决设备未授权问题,并启用网络调试与端口映射,最终实现电脑与虚拟机的稳定连接。文章提供了从原理理解、脚本修改到实战命令和疑难排查的全方位指南,帮助开发者和爱好者高效利用光速虚拟机作为移动测试沙盒。

CAT789的专栏 555

初探Java安全之JavaAgent

其实这一部分把注释翻译过来,有些类和某些方法依旧不理解是什么意思,也有些看懂了但不知道怎么用,先鸽着。

qq_53058639的博客 2978

Microsemi Libero系列教程(全网首发)

关于Libero IDE和Libero SoC Libero分为Libero IDE和Libero SoC,Libero IDE最新版本是v9.2 SP3(2016年4月4日)已经不再更新维护了,而Libero SoC是Libero IDE的升级版,官方推荐的开发工具,文中的Libero指的是Libero SoC。 Libero SoC Libero® SoC是Microsemi(美高森美)官方...

whik1194的博客 2万+

Linux 服务器上部署 OpenClaw完整教程

2. 全局安装 OpenClaw 3. 初始化配置 配置模型提供商 方式一:使用配置命令(推荐) 方式二:直接编辑配置文件 在 部分添加: 配置多个模型提供商 如果你使用 Antigravity 本地代理,可以同时配置多个模型: 配置飞书集成 1. 创建飞书应用 访问 飞书开放平台 创建企业自建应用 记录 App ID 和 App Secret 2. 配置应用权限 在飞书开放平台,为应用添加以下权限:重要: 必须使用 长连接(WebSocket) 模式,不要使用 HTTP 回调!在飞书开放

Maynor的博客 5247

Java Instrument (四) JPLISAgent

Java Instrument (四) JPLISAgent

沧海一粟 4476

关于 SAP UI5 应用 url 中的特殊符号 # 的作用介绍

符号在 SAP UI5 的 URL 中不仅仅是一个分隔符,更是动态路由、状态管理和深链接的关键所在。通过合理使用,开发者可以实现高效、流畅的用户体验,同时保持系统的可维护性和扩展性。通过 SAP UI5 提供的灵活路由机制,企业应用可以轻松实现复杂导航需求,同时为用户提供直观的交互体验。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 407

基于Java Agent的premain方式实现方法耗时监控(转),为了找到结论执行:premain在jvm启动的时候执行,所有方法前,会执行MyAgent的premain方法...

Java Agent是依附于java应用程序并能对其字节码做相关更改的一项技术,它也是一个Jar包,但并不能独立运行,有点像寄生虫的感觉。当今的许多开源工具尤其是监控和诊断工具,很多都是基于Java Agent来实现的,如最近阿里刚开源的Arthas。一个Java Agent既可以在程序运行前加载(premain方式), 又可以在程序运行...

as403045314的博客 556

麒麟系统无法进入单用户模式该怎么办?

🏆本文收录于《全栈Bug调优(实战版)》专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 备注:部分问题/疑难杂症搜集于互联网。

**My Coding Family** 1820

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent premain

基于 Instrumentation 来实现的有:APM 产品: pinpoint、skywalking、newrelic、听云的 APM 产品等都基于 Instrumentation 实现热部署工具:Intellij idea 的 HotSwap、Jrebel 等Java 诊断工具:Arthas、Btrace 等由于对字节码修改功能的巨大需求,JDK 从 JDK5 版本开始引入了java.lang.instrument 包。

971

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

JVM之类的热替换原理解读

这里终于派上用场了,TransformerManager的transform()方法会遍历它的注册数组,调用每个ClassFileTransformer对象的transform()方法,并将我们修改后的类字节码返回,返回后的字节码最终又回到了上面JVM层的transformClassFile()中,并最终交还给给class_file_load_hook 消息的发送方。retransformClasses()会让类重新加载,从而使得注册的类修改器能够重新修改类的字节码。这个方法的实现是个Native方法。

2501_90193656的博客 990

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

java instrument 初探

java在1.5引入java.lang.instrument,你可以由此实现一个java agent,通过此agent来修改类的字节码即改变一个类。本文中,会通过java instrument 实现一个简单的profiler。当然instrument并不限于profiler,instrument可以做很多事情,它类似一种更低级,更松耦合的AOP,可以从底层来改变一个类的行为,你可以由此产生无限

pwlazy的专栏 1万+

学习Java agent 内存马(看不懂别关闭,直接喷!!!)

java agent 是 Java 程序,可以通过在 Java 虚拟机(JVM)启动时提供参数来加载和运行。它可以通过 java.lang.instrument 包提供的 Java 标准接口进行代码插桩,从而在 Java 应用程序的类加载和运行期间改变 Java 字节码。而java agent内存马就是利用这个特性产生。

一剑开天门!的博客 3916

java 绑定_Java绑定

java程序绑定:【静态绑定+动态绑定】(一)首先:要知道java的编译 运行过程:①:java编译过程:java源文件(.java文件)-->字节码文件(.class文件--->JVM可执行代码)【这个过程中java不与内存打交道,编译器解析语法,语法不正确会报错】②:java运行过程:是JVM装载字节码文件--->解释器来解析执行。【这个过程才会创立内存布局,执行java程...

weixin_30746095的博客 1123

Java Instrument (五) Agent attach

首先先参考笔者前期的博客(http://blog.youkuaiyun.com/raintungli/article/details/7034005),先了解在jvm启动的过程中的两个线程Signal Dispatcher和Attach Listener 在博客中,已经探讨了在Attach Listener 的线程在linux环境中创建了socket的文件,接着我们的关注点讲成为客户端如何写这个文件。

沧海一粟 6679

入侵JVM?Java Agent原理浅析和实践(上)

声明:本文首发于京东零售技术公众号,为博主本人撰写投稿。 前言: 在平时的开发中,我们不可避免的会使用到Debug工具,JVM作为一个单独的进程,我们使用的Debug工具可以获取JVM运行时的相关的信息,查看变量值,甚至加入断点控制,还有我们平时使用JDK自带的JMAP、JSTACK等工具,可以在JVM运行时动态的dump内存、查询线程信息,甚至一些第三方的工具,比如说京东内部使用的JEX、pfinder,阿里巴巴的Arthas,优秀的开源的框架skywalking等等,也可以做到这些,那么这些工具究竟是通

CringKong的博客 1682
上一篇: docker安装clickhouse
下一篇: 10个必备的IntelliJ IDEA插件,提高开发效率
yue_stack
博客等级 码龄3年 6粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值