mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
722 lines
25 KiB
HTML
722 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/00 开篇词:JVM,一块难啃的骨头.md.html">00 开篇词:JVM,一块难啃的骨头.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/01 一探究竟:为什么需要 JVM?它处在什么位置?.md.html">01 一探究竟:为什么需要 JVM?它处在什么位置?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/02 大厂面试题:你不得不掌握的 JVM 内存管理.md.html">02 大厂面试题:你不得不掌握的 JVM 内存管理.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制.md.html">03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的.md.html">04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/05 大厂面试题:得心应手应对 OOM 的疑难杂症.md.html">05 大厂面试题:得心应手应对 OOM 的疑难杂症.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/06 深入剖析:垃圾回收你真的了解吗?(上).md.html">06 深入剖析:垃圾回收你真的了解吗?(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/07 深入剖析:垃圾回收你真的了解吗?(下).md.html">07 深入剖析:垃圾回收你真的了解吗?(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html">08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/09 案例实战:亿级流量高并发下如何进行估算和调优.md.html">09 案例实战:亿级流量高并发下如何进行估算和调优.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/10 第09讲:案例实战:面对突如其来的 GC 问题如何下手解决.md.html">10 第09讲:案例实战:面对突如其来的 GC 问题如何下手解决.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/11 第10讲:动手实践:自己模拟 JVM 内存溢出场景.md.html">11 第10讲:动手实践:自己模拟 JVM 内存溢出场景.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/12 第11讲:动手实践:遇到问题不要慌,轻松搞定内存泄漏.md.html">12 第11讲:动手实践:遇到问题不要慌,轻松搞定内存泄漏.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/13 工具进阶:如何利用 MAT 找到问题发生的根本原因.md.html">13 工具进阶:如何利用 MAT 找到问题发生的根本原因.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/14 动手实践:让面试官刮目相看的堆外内存排查.md.html">14 动手实践:让面试官刮目相看的堆外内存排查.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/15 预警与解决:深入浅出 GC 监控与调优.md.html">15 预警与解决:深入浅出 GC 监控与调优.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/16 案例分析:一个高死亡率的报表系统的优化之路.md.html">16 案例分析:一个高死亡率的报表系统的优化之路.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/17 案例分析:分库分表后,我的应用崩溃了.md.html">17 案例分析:分库分表后,我的应用崩溃了.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/18 动手实践:从字节码看方法调用的底层实现.md.html">18 动手实践:从字节码看方法调用的底层实现.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/19 大厂面试题:不要搞混 JMM 与 JVM.md.html">19 大厂面试题:不要搞混 JMM 与 JVM.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/20 动手实践:从字节码看并发编程的底层实现.md.html">20 动手实践:从字节码看并发编程的底层实现.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/21 动手实践:不为人熟知的字节码指令.md.html">21 动手实践:不为人熟知的字节码指令.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/深入浅出 Java 虚拟机-完/22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html">22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/23 动手实践:JIT 参数配置如何影响程序运行?.md.html">23 动手实践:JIT 参数配置如何影响程序运行?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/24 案例分析:大型项目如何进行性能瓶颈调优?.md.html">24 案例分析:大型项目如何进行性能瓶颈调优?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/25 未来:JVM 的历史与展望.md.html">25 未来:JVM 的历史与展望.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/26 福利:常见 JVM 面试题补充.md.html">26 福利:常见 JVM 面试题补充.md.html</a>
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>22 深入剖析:如何使用 Java Agent 技术对字节码进行修改</h1>
|
||
|
||
<p>本课时我们主要分析如何使用 Java Agent 技术对字节码进行修改。</p>
|
||
|
||
<p>Java 5 版本以后,JDK 有一个包叫做 instrument ,能够实现一些<strong>非常酷</strong>的功能,市面上一些 APM 工具,就是通过它来进行的增强,这个功能对于业务开发者来说,是比较偏门的。但你可能在无意中已经用到它了,比如 <strong>Jrebel</strong> 酷炫的热部署功能(这个工具能够显著增加开发效率)。</p>
|
||
|
||
<p>本课时将以一个例子来看一下具体的应用场景,然后介绍一个在线上常用的问题排查工具:Arthas。</p>
|
||
|
||
<h1>Java Agent 介绍</h1>
|
||
|
||
<p>我们上面说的这些工具的基础,就是 Java Agent 技术,可以利用它来构建一个附加的代理程序,用来协助检测性能,还可以替换一些现有功能,甚至 JDK 的一些类我们也能修改,有点像 JVM 级别的 AOP 功能。</p>
|
||
|
||
<p>通常,Java 入口是一个 main 方法,这是毋庸置疑的,而 Java Agent 的入口方法叫做 premain,表明是在 main 运行之前的一些操作。Java Agent 就是这样的一个 jar 包,定义了一个标准的入口方法,它并不需要继承或者实现任何其他的类,属于无侵入的一种开发模式。</p>
|
||
|
||
<blockquote>
|
||
|
||
<p>为什么叫 premain?这是一个约定,并没有什么其他的理由,这个方法,无论是第一次加载,还是每次新的 ClassLoader 加载,都会执行。</p>
|
||
|
||
</blockquote>
|
||
|
||
<p>我们可以在这个前置的方法里,对字节码进行一些修改,来增加功能或者改变代码的行为,这种方法没有侵入性,只需要在启动命令中加上 -javaagent 参数就可以了。Java 6 以后,甚至可以通过 attach 的方式,动态的给运行中的程序设置加载代理类。</p>
|
||
|
||
<p>其实,instrument 一共有两个 main 方法,一个是 premain,另一个是 agentmain,但在一个 JVM 中,只会调用一个;前者是 main 执行之前的修改,后者是控制类运行时的行为。它们还是有一些区别的,agentmain 因为能够动态修改大部分代码,比较危险,限制会更大一些。</p>
|
||
|
||
<h1>有什么用</h1>
|
||
|
||
<h2>获取统计信息</h2>
|
||
|
||
<p>在许多 APM 产品里,比如 Pinpoint、SkyWalking 等,就是使用 Java Agent 对代码进行的增强。通过在方法执行前后动态加入的统计代码,来进行监控信息的收集;通过兼容 OpenTracing 协议,可以实现分布式链路追踪的功能。</p>
|
||
|
||
<p>它的原理类似于 AOP,最终以字节码的形式存在,性能损失取决于你的代码逻辑。</p>
|
||
|
||
<h2>热部署</h2>
|
||
|
||
<p>通过自定义的 ClassLoader,可以实现代码的热替换。使用 agentmain,实现热部署功能会更加便捷,通过 agentmain 获取到 Instrumentation 以后,就可以对类进行动态重定义了。</p>
|
||
|
||
<h2>诊断</h2>
|
||
|
||
<p>配合 JVMTI 技术,可以 attach 到某个进程进行运行时的统计和调试,比较流行的 btrace 和 arthas ,其底层就是这种技术。</p>
|
||
|
||
<h1>代码示例</h1>
|
||
|
||
<p>要构建一个 agent 程序,大体可分为以下步骤:</p>
|
||
|
||
<ul>
|
||
|
||
<li>使用字节码增强工具,编写增强代码;</li>
|
||
|
||
<li>在 manifest 中指定 Premain-Class/Agent-Class 属性;</li>
|
||
|
||
<li>使用参数加载或者使用 attach 方式。</li>
|
||
|
||
</ul>
|
||
|
||
<p>我们来详细介绍一下这个过程。</p>
|
||
|
||
<h2>编写 Agent</h2>
|
||
|
||
<p>Java Agent 最终的体现方式是一个 jar 包,使用 IDEA 创建一个默认的 maven 工程即可。</p>
|
||
|
||
<p>创建一个普通的 Java 类,添加 premain 或者 agentmain 方法,它们的参数完全一样。</p>
|
||
|
||
<p><img src="assets/CgpOIF50j_qAf_dJAACCGgHeHyw573.jpg" alt="img" /></p>
|
||
|
||
<h2>编写 Transformer</h2>
|
||
|
||
<p>实际的代码逻辑需要实现 ClassFileTransformer 接口。假如我们要统计某个方法的执行时间,使用 JavaAssist 工具来增强字节码,则可以通过以下代码来实现:</p>
|
||
|
||
<ul>
|
||
|
||
<li>获取 MainRun 类的字节码实例;</li>
|
||
|
||
<li>获取 hello 方法的字节码实例;</li>
|
||
|
||
<li>在方法前后,加入时间统计,首先定义变量 _begin,然后追加要写的代码。</li>
|
||
|
||
</ul>
|
||
|
||
<p>别忘了加入 maven 依赖,我们借用 javassist 完成字节码增强:</p>
|
||
|
||
<pre><code><dependency>
|
||
|
||
<groupId>org.javassist</groupId>
|
||
|
||
<artifactId>javassist</artifactId>
|
||
|
||
<version>3.24.1-GA</version>
|
||
|
||
</dependency>
|
||
|
||
</code></pre>
|
||
|
||
<p><img src="assets/Cgq2xl50j_qAFT4LAAEbr39G4og828.jpg" alt="img" /></p>
|
||
|
||
<p>字节码增强也可以使用 Cglib、ASM 等其他工具。</p>
|
||
|
||
<h2>MANIFEST.MF 文件</h2>
|
||
|
||
<p>那么我们编写的代码是如何让外界知晓的呢?那就是依靠 MANIFEST.MF 文件,具体路径在</p>
|
||
|
||
<p>src/main/resources/META-INF/MANIFEST.MF:</p>
|
||
|
||
<pre><code>Manifest-Version: 1.0
|
||
|
||
premain-class: com.sayhiai.example.javaagent.AgentApp
|
||
|
||
</code></pre>
|
||
|
||
<p>一般的,maven 打包会覆盖这个文件,所以我们需要为它指定一个。</p>
|
||
|
||
<pre><code><build><plugins><plugin>
|
||
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
|
||
<artifactId>maven-jar-plugin</artifactId>
|
||
|
||
<configuration>
|
||
|
||
<archive>
|
||
|
||
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
|
||
|
||
</archive>
|
||
|
||
</configuration></plugin></plugins></build>
|
||
|
||
</code></pre>
|
||
|
||
<p>然后,在命令行,执行 mvn install 安装到本地代码库,或者使用 mvn deploy 发布到私服上。</p>
|
||
|
||
<p>附 MANIFEST.MF 参数清单:</p>
|
||
|
||
<pre><code>Premain-Class
|
||
|
||
Agent-Class
|
||
|
||
Boot-Class-Path
|
||
|
||
Can-Redefine-Classes
|
||
|
||
Can-Retransform-Classes
|
||
|
||
Can-Set-Native-Method-Prefix
|
||
|
||
</code></pre>
|
||
|
||
<h2>使用</h2>
|
||
|
||
<p>使用方式取决于你使用的 premain 还是 agentmain,它们之间有一些区别,具体如下。</p>
|
||
|
||
<h3>premain</h3>
|
||
|
||
<p>在我们的例子中,直接在启动命令行中加入参数即可,在 jvm 启动时启用代理。</p>
|
||
|
||
<pre><code>java -javaagent:agent.jar MainRun
|
||
|
||
</code></pre>
|
||
|
||
<p>在 IDEA 中,可以将参数附着在 jvm options 里。</p>
|
||
|
||
<p><img src="assets/CgpOIF50j_uATNQIAACvhINEUI4723.jpg" alt="img" /></p>
|
||
|
||
<p>接下来看一下测试代码。</p>
|
||
|
||
<p><img src="assets/Cgq2xl50j_uAJ7_SAABNVSit7MU387.jpg" alt="img" /></p>
|
||
|
||
<p>这是我们的执行类,执行后,直接输出 hello world。通过增强以后,还额外的输出了执行时间,以及一些 debug 信息。其中,debug 信息在 main 方法执行之前输出。</p>
|
||
|
||
<p><img src="assets/CgpOIF50j_uAMC5CAAAyERGVQpg151.jpg" alt="img" /></p>
|
||
|
||
<h3>agentmain</h3>
|
||
|
||
<p>这种模式一般用在一些诊断工具上。使用 <strong>jdk/lib/tools.jar</strong> 中的工具类,可以动态的为运行中的程序加入一些功能。它的主要运行步骤如下:</p>
|
||
|
||
<ul>
|
||
|
||
<li>获取机器上运行的所有 JVM 进程 ID;</li>
|
||
|
||
<li>选择要诊断的 jvm;</li>
|
||
|
||
<li>将 jvm 使用 attach 函数链接上;</li>
|
||
|
||
<li>使用 loadAgent 函数加载 agent,动态修改字节码;</li>
|
||
|
||
<li>卸载 jvm。</li>
|
||
|
||
</ul>
|
||
|
||
<p>代码样例如下:</p>
|
||
|
||
<pre><code>import com.sun.tools.attach.VirtualMachine;
|
||
|
||
import com.sun.tools.attach.VirtualMachineDescriptor;
|
||
import java.util.List;
|
||
public class JvmAttach {
|
||
public static void main(String[] args)
|
||
|
||
throws Exception {
|
||
|
||
List<VirtualMachineDescriptor> list = VirtualMachine.list();
|
||
|
||
for (VirtualMachineDescriptor vmd : list) {
|
||
|
||
if (vmd.displayName().endsWith("MainRun")) {
|
||
|
||
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
|
||
|
||
virtualMachine.loadAgent("test.jar ", "...");
|
||
|
||
//.....
|
||
|
||
virtualMachine.detach();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这些代码功能虽然强大,但都是比较危险的,这就是为什么 Btrace 说了这么多年,还是只在小范围内被小心的使用。相对来说,Arthas 显的友好而且安全的多。</p>
|
||
|
||
<h1>使用注意点</h1>
|
||
|
||
<p><strong>(1)jar 包依赖方式</strong></p>
|
||
|
||
<p>一般,Agent 的 jar 包会以 fatjar 的方式提供,即将所有的依赖打包到一个大的 jar 包中。如果你的功能复杂、依赖多,那么这个 jar 包将会特别的大。</p>
|
||
|
||
<p>使用独立的 <strong>bom</strong> 文件维护这些依赖是另外一种方法。使用方自行管理依赖问题,但这通常会发生一些找不到 jar 包的错误,更糟糕的是,大多数在运行时才发现。</p>
|
||
|
||
<p><strong>(2)类名称重复</strong></p>
|
||
|
||
<p>不要使用和 jdk 及 instrument 包中相同的类名(包括包名),有时候你能够侥幸过关,但也会陷入无法控制的异常中。</p>
|
||
|
||
<p><strong>(3)做有限的功能</strong></p>
|
||
|
||
<p>可以看到,给系统动态的增加功能是非常酷的,但大多数情况下非常耗费性能。你会发现,一些简单的诊断工具,会占用你 1 核的 CPU,这是很平常的事情。</p>
|
||
|
||
<p><strong>(4)ClassLoader</strong></p>
|
||
|
||
<p>如果你用的 JVM 比较旧,频繁地生成大量的代理类,会造成元空间的膨胀,容易发生内存占用问题。</p>
|
||
|
||
<p>ClassLoader 有双亲委派机制,如果你想要替换相应的类,一定要搞清楚它的类加载器应该用哪个,否则替换的类,是不生效的。</p>
|
||
|
||
<p>具体的调试方法,可以在 Java 进程启动时,加入 -verbose:class 参数,用来监视引用程序对类的加载。</p>
|
||
|
||
<h1>Arthas</h1>
|
||
|
||
<p>我们来回顾一下在故障排查时所做的一些准备和工具支持。</p>
|
||
|
||
<p>在第 09 课时,我们了解了 jstat 工具,还有 jmap 等查看内存状态的工具;第 11 课时,介绍了超过 20 个工具的使用,这需要开发和分析的人员具有较高的水平;第 15 课时,还介绍了 jstack 的一些典型状态。对于这种瞬时态问题的分析,需要综合很多工具,对刚进入这个行业的人来说,很不友好。</p>
|
||
|
||
<p>Arthas 就是使用 Java Agent 技术编写的一个工具,具体采用的方式,就是我们上面提到的 attach 方式,它会无侵入的 attach 到具体的执行进程上,方便进行问题分析。</p>
|
||
|
||
<p>你甚至可以像 debug 本地的 Java 代码一样,观测到方法执行的参数值,甚至做一些统计分析。这通常可以解决下面的问题:</p>
|
||
|
||
<ul>
|
||
|
||
<li>哪个线程使用了最多的 CPU</li>
|
||
|
||
<li>运行中是否有死锁,是否有阻塞</li>
|
||
|
||
<li>如何监测一个方法哪里耗时最高</li>
|
||
|
||
<li>追加打印一些 debug 信息</li>
|
||
|
||
<li>监测 JVM 的实时运行状态</li>
|
||
|
||
</ul>
|
||
|
||
<p><a href="https://alibaba.github.io/arthas">Arthas 官方文档十分详细,也可以点击这里参考</a>。</p>
|
||
|
||
<p>但无论工具如何强大,一些基础知识是需要牢固掌握的,否则,工具中出现的那些术语,也会让人一头雾水。</p>
|
||
|
||
<p>工具常变,但基础更加重要。如果你想要一个适应性更强的技术栈,还是要多花点时间在原始的排查方法上。</p>
|
||
|
||
<h1>小结</h1>
|
||
|
||
<p>本课时介绍了开发人员极少接触的 Java Agent 技术,但在平常的工作中你可能不知不觉就用到它了。在平常的面试中,一些面试官也会经常问一些相关的问题,以此来判断你对整个 Java 体系的掌握程度,如果你能回答上来,说明你已经脱颖而出了。</p>
|
||
|
||
<p>值得注意的是,这个知识点,对于做基础架构(比如中间件研发)的人来说,是必备技能,如果不了解,那面试可能就要凉了。</p>
|
||
|
||
<p>从实用角度来说,阿里开源的 Arthas 工具,是非常好用的,如果你有线上的运维权限,不妨尝试一下。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/21 动手实践:不为人熟知的字节码指令.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/23 动手实践:JIT 参数配置如何影响程序运行?.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997a772a393cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
</script>
|
||
</html>
|
||
|