<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>横云断岭的专栏</title>
  <icon>https://www.gravatar.com/avatar/98cb0c8e8c8aac68e15d2a3145be3998</icon>
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://hengyunabc.github.io/"/>
  <updated>2020-03-02T17:57:00.255Z</updated>
  <id>http://hengyunabc.github.io/</id>
  
  <author>
    <name>横云断岭/hengyunabc</name>
    <email>hengyunabc#gmail.com</email>
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>深入Spring Boot系列</title>
    <link href="http://hengyunabc.github.io/spring-boot-inside/"/>
    <id>http://hengyunabc.github.io/spring-boot-inside/</id>
    <published>2118-07-22T16:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.255Z</updated>
    
    <content type="html"><![CDATA[<h2 id="原理-使用"><a href="#原理-使用" class="headerlink" title="原理/使用"></a>原理/使用</h2><ul><li><a href="/spring-boot-application-start-analysis">spring boot应用启动原理分析</a></li><li><a href="/spring-boot-executable-jar">spring boot executable jar/war 原理</a></li><li><a href="/spring-boot-classloader">深入Spring Boot：ClassLoader的继承关系和影响</a></li><li><a href="/spring-boot-context">深入Spring Boot：Spring Context的继承关系和影响</a></li><li><a href="/spring-boot-fat-jar-jsp-sample">深入Spring Boot：实现对Fat Jar jsp的支持</a></li><li><a href="/spring-boot-dubbo-hystrix/">深入Spring Boot：快速集成Dubbo + Hystrix</a></li><li><a href="/spring-scan-annotation/">正确实现用spring扫描自定义的annotation</a></li><li><a href="/spring-boot-starter-compatibility">深入Spring Boot：编写兼容Spring Boot1和Spring Boot2的Starter</a></li></ul><h2 id="排查问题"><a href="#排查问题" class="headerlink" title="排查问题"></a>排查问题</h2><ul><li><a href="/hibernate-validar-noclassdefounderror">深入JVM分析spring-boot应用hibernate-validator NoClassDefFoundError</a></li><li><a href="/spring-placeholder-inject-failed-cases">深入Spring Boot：那些注入不了的Spring占位符（${}表达式）</a></li><li><a href="/spring-boot-ArrayStoreException/">深入Spring Boot：怎样排查 java.lang.ArrayStoreException</a></li><li><a href="/spring-boot-database-type-none/">深入Spring Boot：排查 Cannot determine embedded database driver class for database type NONE</a></li><li><a href="/spring-boot-expected-single-but-found-2">深入Spring Boot：排查expected single matching bean but found 2的异常</a></li><li><a href="/spring-boot-transactional-nullpointerexception">深入Spring Boot：排查@Transactional引起的NullPointerException</a></li><li><a href="/spring-boot-enablewebmvc-static-404">深入Spring Boot：显式配置 @EnableWebMvc 导致静态资源访问失败</a></li><li><a href="/spring-boot-arthas-NoSuchMethodError">深入Spring Boot：利用Arthas排查NoSuchMethodError</a></li><li><a href="/arthas-spring-boot-404-401">Arthas实践–快速排查Spring Boot应用404/401问题</a></li><li><a href="/arthas-spring-context">Alibaba Arthas实践–获取到Spring Context，然后为所欲为</a></li><li><a href="/arthas-springmvc-controller">Alibaba Arthas实践–是哪个Controller处理了请求？</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;原理-使用&quot;&gt;&lt;a href=&quot;#原理-使用&quot; class=&quot;headerlink&quot; title=&quot;原理/使用&quot;&gt;&lt;/a&gt;原理/使用&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/spring-boot-application-start-analysis&quot;&gt;sp
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="spring-boot" scheme="http://hengyunabc.github.io/tags/spring-boot/"/>
    
      <category term="spring" scheme="http://hengyunabc.github.io/tags/spring/"/>
    
  </entry>
  
  <entry>
    <title>Alibaba Arthas 3.1.5版本支持火焰图，快速定位应用热点</title>
    <link href="http://hengyunabc.github.io/arthas-3.1.5/"/>
    <id>http://hengyunabc.github.io/arthas-3.1.5/</id>
    <published>2019-11-26T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://alibaba.github.io/arthas/_images/arthas.png" alt="Arthas"></p><p><code>Arthas</code>是Alibaba开源的Java诊断工具，深受开发者喜爱。</p><ul><li>Github： <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas" target="_blank" rel="noopener">https://alibaba.github.io/arthas</a></li></ul><p>Arthas 3.1.5版本带来下面全新的特性：</p><ul><li>开箱即用的Profiler/火焰图功能</li><li>grep命令支持更丰富的选项</li><li>monitor/tt/trace等命令提供更精确的时间统计</li><li>telnet/http协议共用3658端口</li></ul><h2 id="Profiler-Flame-Graph-火焰图"><a href="#Profiler-Flame-Graph-火焰图" class="headerlink" title="Profiler/Flame Graph/火焰图"></a>Profiler/Flame Graph/火焰图</h2><p>火焰图的威名相信大家都有所耳闻，但可能因为使用比较复杂，所以望而止步。</p><p>在新版本的Arthas里集成了<a href="https://github.com/jvm-profiling-tools/async-profiler" target="_blank" rel="noopener">async-profiler</a>，使用<code>profiler</code>命令就可以很方便地生成火焰图，并且可以在浏览器里直接查看。</p><ul><li>profiler命令wiki: <a href="https://alibaba.github.io/arthas/profiler.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/profiler.html</a></li></ul><p><code>profiler</code> 命令基本运行结构是 <code>profiler action [actionArg]</code>。 下面介绍如何使用。</p><h3 id="启动profiler"><a href="#启动profiler" class="headerlink" title="启动profiler"></a>启动profiler</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ profiler start</span><br><span class="line">Started [cpu] profiling</span><br></pre></td></tr></table></figure><blockquote><p>默认情况下，生成的是cpu的火焰图，即event为<code>cpu</code>。可以用<code>--event</code>参数来指定。</p></blockquote><h3 id="获取已采集的sample的数量"><a href="#获取已采集的sample的数量" class="headerlink" title="获取已采集的sample的数量"></a>获取已采集的sample的数量</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ profiler getSamples</span><br><span class="line">23</span><br></pre></td></tr></table></figure><h3 id="查看profiler状态"><a href="#查看profiler状态" class="headerlink" title="查看profiler状态"></a>查看profiler状态</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ profiler status</span><br><span class="line">[cpu] profiling is running <span class="keyword">for</span> 4 seconds</span><br></pre></td></tr></table></figure><p>可以查看当前profiler在采样哪种<code>event</code>和采样时间。</p><h3 id="生成svg格式结果"><a href="#生成svg格式结果" class="headerlink" title="生成svg格式结果"></a>生成svg格式结果</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ profiler stop</span><br><span class="line">profiler output file: /tmp/demo/arthas-output/20191125-135546.svg</span><br><span class="line">OK</span><br></pre></td></tr></table></figure><p>默认情况下，生成的结果保存到应用的<code>工作目录</code>下的<code>arthas-output</code>目录里。</p><h3 id="通过浏览器查看arthas-output下面的profiler结果"><a href="#通过浏览器查看arthas-output下面的profiler结果" class="headerlink" title="通过浏览器查看arthas-output下面的profiler结果"></a>通过浏览器查看arthas-output下面的profiler结果</h3><p>默认情况下，arthas使用3658端口，则可以打开： <a href="http://localhost:3658/arthas-output/" target="_blank" rel="noopener">http://localhost:3658/arthas-output/</a> 查看到<code>arthas-output</code>目录下面的profiler结果：</p><p><img src="https://alibaba.github.io/arthas/_images/arthas-output.jpg" alt></p><p>点击可以查看具体的结果：</p><p><img src="https://alibaba.github.io/arthas/_images/arthas-output-svg.jpg" alt></p><blockquote><p>如果是chrome浏览器，可能需要多次刷新。</p></blockquote><h2 id="grep命令支持更丰富的选项"><a href="#grep命令支持更丰富的选项" class="headerlink" title="grep命令支持更丰富的选项"></a>grep命令支持更丰富的选项</h2><p>标准的linux grep命令支持丰富的选项，可以很方便地定位结果的上下文等。</p><p>新版本的<code>grep</code>命令支持更多标准的选项，下面是一些例子:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sysprop | grep java</span><br><span class="line">sysprop | grep java -n</span><br><span class="line">sysenv | grep -v JAVA</span><br><span class="line">sysenv | grep -e <span class="string">"(?i)(JAVA|sun)"</span> -m 3  -C 2</span><br><span class="line">sysenv | grep JAVA -A2 -B3</span><br><span class="line">thread | grep -m 10 -e  <span class="string">"TIMED_WAITING|WAITING"</span></span><br></pre></td></tr></table></figure><p>感谢社区里 @qxo 的贡献。</p><h2 id="telnet-http协议共用3658端口"><a href="#telnet-http协议共用3658端口" class="headerlink" title="telnet/http协议共用3658端口"></a>telnet/http协议共用3658端口</h2><p>默认情况下，Arthas的Telnet端口是3658，HTTP端口是8563，这个常常让用户迷惑。在新版本里，在3658端口同时支持Telnet/HTTP协议。</p><p>在浏览器里访问 <a href="http://localhost:3658/" target="_blank" rel="noopener">http://localhost:3658/</a> 也可以访问到Web Console了。</p><p>在后续的版本里，考虑默认只侦听 3658端口，减少用户的配置项。</p><h2 id="monitor-tt-trace等命令提供更精确的时间统计"><a href="#monitor-tt-trace等命令提供更精确的时间统计" class="headerlink" title="monitor/tt/trace等命令提供更精确的时间统计"></a>monitor/tt/trace等命令提供更精确的时间统计</h2><p>以前Arthas被诟病比较多的一个问题是，monitor/tt/trace等命令时间统计误差大。因为以前只使用了一个int来保存时间，所以不精确。</p><p>在新版本里，改用一个高效的stack来保存数据，时间的准确度大大提升，欢迎大家反馈效果。</p><p>感谢社区里 @huangjIT 的贡献。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>总之，<code>3.1.5</code>版本的Arthas引入了开箱即用的Profiler/火焰图功能，欢迎大家使用反馈。</p><ul><li>Release Note: <a href="https://github.com/alibaba/arthas/releases/tag/arthas-all-3.1.5" target="_blank" rel="noopener">https://github.com/alibaba/arthas/releases/tag/arthas-all-3.1.5</a></li><li>火焰图的一个参考文章：<a href="https://openresty.org/posts/dynamic-tracing/" target="_blank" rel="noopener">https://openresty.org/posts/dynamic-tracing/</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;img src=&quot;https://alibaba.github.io/arthas/_images/arthas.png&quot; alt=&quot;Arthas&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Arthas&lt;/code&gt;是Alibaba开源的Java诊断工具，深受开发者喜爱。&lt;/p&gt;
&lt;
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="profiler" scheme="http://hengyunabc.github.io/tags/profiler/"/>
    
  </entry>
  
  <entry>
    <title>Arthas开源一周年，Github Star 16K，我们一直在坚持什么？</title>
    <link href="http://hengyunabc.github.io/arthas-one-year/"/>
    <id>http://hengyunabc.github.io/arthas-one-year/</id>
    <published>2019-09-27T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>最近看到一个很流行的标题，《开源XX年，star XXX，我是如何坚持的》。</p><p>看到这样的标题，忽然发觉Arthas从2018年9月开源以来，刚好一年了，正好在这个秋高气爽的时节做下总结和回顾。</p><p><img src="https://alibaba.github.io/arthas/_images/arthas.png" alt="Arthas"></p><p><code>Arthas</code>是Alibaba开源的Java诊断工具，深受开发者喜爱。</p><ul><li>Github： <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas" target="_blank" rel="noopener">https://alibaba.github.io/arthas</a></li></ul><p>回顾Arthas Star数的历史，一直保持快速增长，目前已经突破16K。</p><p><img src="https://user-images.githubusercontent.com/1683936/65661356-05ca3480-e064-11e9-92ea-8cc21e45a71f.png" alt="Arthas Github Star历史曲线"></p><p>感谢用户的支持，既是压力也是动力。在过去开源的一年里，Arthas发布了7个Release版本，我们一直坚持三点：</p><ul><li>持续改进易用性</li><li>持续增加好用的命令</li><li>从开源社区中获取力量，回报社区</li></ul><h2 id="持续改进易用性"><a href="#持续改进易用性" class="headerlink" title="持续改进易用性"></a>持续改进易用性</h2><p>Arthas一直把易用性放在第一位，在开源之后，我们做了下面的改进：</p><ul><li>开发arthas boot，支持Windows/Linux/Mac统一体验</li><li>丝滑的自动补全，参考了jshell的体验</li><li>高效的历史命令匹配，<code>Up/Down</code>直达</li><li>改进类搜索匹配功能，更好支持lambda和内部类</li><li>完善重定向机制</li><li>支持JDK 9/10/11</li><li>支持Docker</li><li>支持rpm/deb包安装</li></ul><p>尽管我们在易用性下了很大的功夫，但是发现很多时候用户比较难入门，因此，我们参考了k8s的 Interactive Tutorial，推出了Arthas的在线教程：</p><ul><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-basics" target="_blank" rel="noopener">Arthas基础教程</a></li><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-advanced" target="_blank" rel="noopener">Arthas进阶教程</a></li></ul><p>通过基础教程，可以在交互终端里一步步入门，通过进阶教程可以深入理解Arthas排查问题的案例。</p><p>另外，为了方便用户大规模部署，我们实现了tunnel server和用户数据回报功能：</p><ul><li>增加tunnel server，统一管理Agent连接</li><li>增加用户数据回报功能，方便做安全管控</li></ul><h2 id="持续增加好用的命令"><a href="#持续增加好用的命令" class="headerlink" title="持续增加好用的命令"></a>持续增加好用的命令</h2><p>Arthas号称是Java应用诊断利器，那么我们自己要对得起这个口号。在开源之后，Arthas持续增加了10多个命令。</p><ul><li>ognl      命令任意代码执行</li><li>mc        线上内存编译器</li><li>redefine  命令线上热更新代码</li><li>logger    命令一键查看应用里的所有logger配置</li><li>sysprop   查看更新System Properties</li><li>sysenv    查看环境变量</li><li>vmoption  查看更新VM option</li><li>logger    查看logger配置，更新level</li><li>mbean     查看JMX信息</li><li>heapdump  堆内存快照</li></ul><p>下面重点介绍两个功能。</p><h3 id="jad-mc-redefine-一条龙热更新线上代码"><a href="#jad-mc-redefine-一条龙热更新线上代码" class="headerlink" title="jad/mc/redefine 一条龙热更新线上代码"></a>jad/mc/redefine 一条龙热更新线上代码</h3><p>以 <a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-advanced" target="_blank" rel="noopener">Arthas在线教程</a> 里的<code>UserController</code>为例：</p><ol><li><p>使用jad反编译代码</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jad --<span class="built_in">source</span>-only com.example.demo.arthas.user.UserController &gt; /tmp/UserController.java</span><br></pre></td></tr></table></figure></li><li><p>使用vim编译代码</p><p> 当 user id 小于1时，也正常返回，不抛出异常：</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/user/&#123;id&#125;"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUserById</span><span class="params">(@PathVariable Integer id)</span> </span>&#123;</span><br><span class="line">    logger.info(<span class="string">"id: &#123;&#125;"</span> , id);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (id != <span class="keyword">null</span> &amp;&amp; id &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(id, <span class="string">"name"</span> + id);</span><br><span class="line">        <span class="comment">// throw new IllegalArgumentException("id &lt; 1");</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(id, <span class="string">"name"</span> + id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>使用<code>mc</code>命令编译修改后的<code>UserController.java</code></p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mc /tmp/UserController.java -d /tmp</span><br><span class="line">Memory compiler output:</span><br><span class="line">/tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 346 ms</span><br></pre></td></tr></table></figure></li><li><p>使用<code>redefine</code>命令，因为可以热更新代码</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redefine /tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">redefine success, size: 1</span><br></pre></td></tr></table></figure></li></ol><h3 id="通过logger命令查看配置，修改level"><a href="#通过logger命令查看配置，修改level" class="headerlink" title="通过logger命令查看配置，修改level"></a>通过logger命令查看配置，修改level</h3><p>在网站压力大的时候（比如双11），有个缓解措施就是把应用的日志level修改为ERROR。 那么有两个问题：</p><ul><li>复杂应用的日志系统可能会有多个，那么哪个日志系统配置真正生效了？</li><li>怎样在线上动态修改logger的level？</li></ul><p>通过<code>logger</code>命令，可以查看应用里logger的详细配置信息，比如<code>FileAppender</code>输出的文件，<code>AsyncAppender</code>是否<code>blocking</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">[arthas@2062]$ logger</span><br><span class="line"> name                                   ROOT</span><br><span class="line"> class                                  ch.qos.logback.classic.Logger</span><br><span class="line"> classLoader                            sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line"> classLoaderHash                        2a139a55</span><br><span class="line"> level                                  INFO</span><br><span class="line"> effectiveLevel                         INFO</span><br><span class="line"> additivity                             <span class="literal">true</span></span><br><span class="line"> codeSource                             file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar</span><br><span class="line"> appenders                              name            CONSOLE</span><br><span class="line">                                        class           ch.qos.logback.core.ConsoleAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        target          System.out</span><br><span class="line">                                        name            APPLICATION</span><br><span class="line">                                        class           ch.qos.logback.core.rolling.RollingFileAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        file            app.log</span><br><span class="line">                                        name            ASYNC</span><br><span class="line">                                        class           ch.qos.logback.classic.AsyncAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        blocking        <span class="literal">false</span></span><br><span class="line">                                        appenderRef     [APPLICATION]</span><br></pre></td></tr></table></figure><p>也可以在线修改logger的level：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[arthas@2062]$ logger --name ROOT --level debug</span><br><span class="line">update logger level success.</span><br></pre></td></tr></table></figure><h2 id="从开源社区中获取力量，回报社区"><a href="#从开源社区中获取力量，回报社区" class="headerlink" title="从开源社区中获取力量，回报社区"></a>从开源社区中获取力量，回报社区</h2><h3 id="感谢67位Contributors"><a href="#感谢67位Contributors" class="headerlink" title="感谢67位Contributors"></a>感谢67位Contributors</h3><p>Arthas开源以来，一共有67位 Contributors，感谢他们贡献的改进：</p><p> <img src="https://opencollective.com/arthas/contributors.svg?width=890&amp;button=false" alt="Arthas Contributors"></p><p>社区提交了一系列的改进，下面列出一些点（不完整）：</p><ul><li>翻译了大部分英文文档的</li><li>trace命令支持行号</li><li>打包格式支持rpm/deb</li><li>改进命令行提示符为 <code>arthas@pid</code></li><li>改进对windows的支持</li><li>增加<code>mbean</code>命令</li><li>改进webconsole的体验</li></ul><p>另外，有83个公司/组织登记了他们的使用信息，欢迎更多的用户来登记：</p><p><img src="https://user-images.githubusercontent.com/1683936/65661477-5772bf00-e064-11e9-91c1-5cd32b09e7c6.png" alt="Arthas Users"></p><h3 id="洐生项目"><a href="#洐生项目" class="headerlink" title="洐生项目"></a>洐生项目</h3><p>基于Arthas，还产生了一些洐生项目，下面是其中两个：</p><ul><li>Bistoury: 去哪儿网开源的集成了Arthas的项目</li><li>arthas-mvel: 一个使用MVEL脚本的fork</li></ul><h3 id="用户案例分享"><a href="#用户案例分享" class="headerlink" title="用户案例分享"></a>用户案例分享</h3><p>广大用户在使用Arthas排查问题过程中，分享了很多排查过程和心得，欢迎大家来分享。</p><p><img src="https://user-images.githubusercontent.com/1683936/65661495-5cd00980-e064-11e9-98e5-4b76956af1c4.png" alt="Arthas用户案例分享"></p><h3 id="回馈开源"><a href="#回馈开源" class="headerlink" title="回馈开源"></a>回馈开源</h3><p>Arthas本身使用了很多开源项目的代码，在开源过程中，我们给netty, ognl, cfr等都贡献了改进代码，回馈上游。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p> 在做Arthas宣传小册子时，Arthas的宣传语是：</p><blockquote><p>“赠人玫瑰之手，经久犹有余香”</p></blockquote><p>希望Arthas未来能帮助到更多的用户解决问题，也希望广大的开发者对Arthas提出更多的改进和建议。</p><p>最后是抽奖环节，大家可以转发文章，在公众号后台留言自己和Arthas的故事，或者给Arthas提出建议，奖品是Arthas的卫衣一件：</p><p><img src="https://user-images.githubusercontent.com/1683936/65737523-619ec700-e111-11e9-8e3f-f91edfd33084.png" alt="Arthas卫衣"></p><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>欢迎关注横云断岭的专栏，专注Java，Spring Boot，Arthas，Dubbo。</p><p><img src="http://hengyunabc.github.io/img/qrcode_gongzhonghao.jpg" alt="横云断岭的专栏"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;缘起&quot;&gt;&lt;a href=&quot;#缘起&quot; class=&quot;headerlink&quot; title=&quot;缘起&quot;&gt;&lt;/a&gt;缘起&lt;/h2&gt;&lt;p&gt;最近看到一个很流行的标题，《开源XX年，star XXX，我是如何坚持的》。&lt;/p&gt;
&lt;p&gt;看到这样的标题，忽然发觉Arthas从2018年
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="logger" scheme="http://hengyunabc.github.io/tags/logger/"/>
    
  </entry>
  
  <entry>
    <title>Alibaba Arthas 3.1.2版本增加logger/heapdump/vmoption命令，增加tunnel server功能</title>
    <link href="http://hengyunabc.github.io/arthas-3.1.2/"/>
    <id>http://hengyunabc.github.io/arthas-3.1.2/</id>
    <published>2019-09-09T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://alibaba.github.io/arthas/_images/arthas.png" alt="Arthas"></p><p><code>Arthas</code>是Alibaba开源的Java诊断工具，深受开发者喜爱。</p><ul><li>Github： <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas" target="_blank" rel="noopener">https://alibaba.github.io/arthas</a></li></ul><p>Arthas 3.1.2版本持续增加新特性，下面重点介绍：</p><ul><li><code>logger/heapdump/vmoption/stop</code>命令</li><li>通过tunnel server连接不同网络的arthas，方便统一管控</li><li>易用性持续提升：提示符修改为<code>arthas@pid</code>形式，支持<code>ctrl + k</code>清屏快捷键</li></ul><h2 id="logger-heapdump-vmoption-stop命令"><a href="#logger-heapdump-vmoption-stop命令" class="headerlink" title="logger/heapdump/vmoption/stop命令"></a>logger/heapdump/vmoption/stop命令</h2><h3 id="logger命令"><a href="#logger命令" class="headerlink" title="logger命令"></a>logger命令</h3><blockquote><p>查看logger信息，更新logger level</p></blockquote><ul><li><a href="https://alibaba.github.io/arthas/logger.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/logger.html</a></li></ul><h4 id="查看所有logger信息"><a href="#查看所有logger信息" class="headerlink" title="查看所有logger信息"></a>查看所有logger信息</h4><p>以下面的<code>logback.xml</code>为例：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">"APPLICATION"</span> <span class="attr">class</span>=<span class="string">"ch.qos.logback.core.rolling.RollingFileAppender"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">file</span>&gt;</span>app.log<span class="tag">&lt;/<span class="name">file</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rollingPolicy</span> <span class="attr">class</span>=<span class="string">"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">fileNamePattern</span>&gt;</span>mylog-%d&#123;yyyy-MM-dd&#125;.%i.txt<span class="tag">&lt;/<span class="name">fileNamePattern</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxFileSize</span>&gt;</span>100MB<span class="tag">&lt;/<span class="name">maxFileSize</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">maxHistory</span>&gt;</span>60<span class="tag">&lt;/<span class="name">maxHistory</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">totalSizeCap</span>&gt;</span>2GB<span class="tag">&lt;/<span class="name">totalSizeCap</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">rollingPolicy</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%logger&#123;35&#125; - %msg%n<span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">"ASYNC"</span> <span class="attr">class</span>=<span class="string">"ch.qos.logback.classic.AsyncAppender"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">"APPLICATION"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">appender</span> <span class="attr">name</span>=<span class="string">"CONSOLE"</span> <span class="attr">class</span>=<span class="string">"ch.qos.logback.core.ConsoleAppender"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">encoder</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pattern</span>&gt;</span>%-4relative [%thread] %-5level %logger&#123;35&#125; - %msg %n</span><br><span class="line">            <span class="tag">&lt;/<span class="name">pattern</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">charset</span>&gt;</span>utf8<span class="tag">&lt;/<span class="name">charset</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">encoder</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">appender</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">root</span> <span class="attr">level</span>=<span class="string">"INFO"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">"CONSOLE"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">appender-ref</span> <span class="attr">ref</span>=<span class="string">"ASYNC"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">root</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><p>使用<code>logger</code>命令打印的结果是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">[arthas@2062]$ logger</span><br><span class="line"> name                                   ROOT</span><br><span class="line"> class                                  ch.qos.logback.classic.Logger</span><br><span class="line"> classLoader                            sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line"> classLoaderHash                        2a139a55</span><br><span class="line"> level                                  INFO</span><br><span class="line"> effectiveLevel                         INFO</span><br><span class="line"> additivity                             <span class="literal">true</span></span><br><span class="line"> codeSource                             file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar</span><br><span class="line"> appenders                              name            CONSOLE</span><br><span class="line">                                        class           ch.qos.logback.core.ConsoleAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        target          System.out</span><br><span class="line">                                        name            APPLICATION</span><br><span class="line">                                        class           ch.qos.logback.core.rolling.RollingFileAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        file            app.log</span><br><span class="line">                                        name            ASYNC</span><br><span class="line">                                        class           ch.qos.logback.classic.AsyncAppender</span><br><span class="line">                                        classLoader     sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line">                                        classLoaderHash 2a139a55</span><br><span class="line">                                        appenderRef     [APPLICATION]</span><br></pre></td></tr></table></figure><p>从<code>appenders</code>的信息里，可以看到</p><ul><li><code>CONSOLE</code> logger的target是<code>System.out</code></li><li><code>APPLICATION</code> logger是<code>RollingFileAppender</code>，它的file是<code>app.log</code></li><li><code>ASYNC</code>它的<code>appenderRef</code>是<code>APPLICATION</code>，即异步输出到文件里</li></ul><h4 id="查看指定名字的logger信息"><a href="#查看指定名字的logger信息" class="headerlink" title="查看指定名字的logger信息"></a>查看指定名字的logger信息</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[arthas@2062]$ logger -n org.springframework.web</span><br><span class="line"> name                                   org.springframework.web</span><br><span class="line"> class                                  ch.qos.logback.classic.Logger</span><br><span class="line"> classLoader                            sun.misc.Launcher<span class="variable">$AppClassLoader</span>@2a139a55</span><br><span class="line"> classLoaderHash                        2a139a55</span><br><span class="line"> level                                  null</span><br><span class="line"> effectiveLevel                         INFO</span><br><span class="line"> additivity                             <span class="literal">true</span></span><br><span class="line"> codeSource                             file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar</span><br></pre></td></tr></table></figure><h4 id="更新logger-level"><a href="#更新logger-level" class="headerlink" title="更新logger level"></a>更新logger level</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[arthas@2062]$ logger --name ROOT --level debug</span><br><span class="line">update logger level success.</span><br></pre></td></tr></table></figure><h3 id="heapdump命令"><a href="#heapdump命令" class="headerlink" title="heapdump命令"></a>heapdump命令</h3><blockquote><p>dump java heap, 类似jmap命令的heap dump功能。</p></blockquote><ul><li><a href="https://alibaba.github.io/arthas/heapdump.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/heapdump.html</a></li></ul><h4 id="dump到指定文件"><a href="#dump到指定文件" class="headerlink" title="dump到指定文件"></a>dump到指定文件</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[arthas@58205]$ heapdump /tmp/dump.hprof</span><br><span class="line">Dumping heap to /tmp/dump.hprof...</span><br><span class="line">Heap dump file created</span><br></pre></td></tr></table></figure><h4 id="只dump-live对象"><a href="#只dump-live对象" class="headerlink" title="只dump live对象"></a>只dump live对象</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[arthas@58205]$ heapdump --live /tmp/dump.hprof</span><br><span class="line">Dumping heap to /tmp/dump.hprof...</span><br><span class="line">Heap dump file created</span><br></pre></td></tr></table></figure><h3 id="vmoption命令"><a href="#vmoption命令" class="headerlink" title="vmoption命令"></a>vmoption命令</h3><blockquote><p>查看，更新VM诊断相关的参数</p></blockquote><ul><li><a href="https://alibaba.github.io/arthas/vmoption.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/vmoption.html</a></li></ul><h4 id="查看所有的option"><a href="#查看所有的option" class="headerlink" title="查看所有的option"></a>查看所有的option</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">[arthas@56963]$ vmoption</span><br><span class="line"> KEY                    VALUE                   ORIGIN                 WRITEABLE</span><br><span class="line">---------------------------------------------------------------------------------------------</span><br><span class="line"> HeapDumpBeforeFullGC   <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> HeapDumpAfterFullGC    <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> HeapDumpOnOutOfMemory  <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> Error</span><br><span class="line"> HeapDumpPath                                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> CMSAbortablePrecleanW  100                     DEFAULT                <span class="literal">true</span></span><br><span class="line"> aitMillis</span><br><span class="line"> CMSWaitDuration        2000                    DEFAULT                <span class="literal">true</span></span><br><span class="line"> CMSTriggerInterval     -1                      DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintGC                <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintGCDetails         <span class="literal">true</span>                    MANAGEMENT             <span class="literal">true</span></span><br><span class="line"> PrintGCDateStamps      <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintGCTimeStamps      <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintGCID              <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintClassHistogramBe  <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> foreFullGC</span><br><span class="line"> PrintClassHistogramAf  <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> terFullGC</span><br><span class="line"> PrintClassHistogram    <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br><span class="line"> MinHeapFreeRatio       0                       DEFAULT                <span class="literal">true</span></span><br><span class="line"> MaxHeapFreeRatio       100                     DEFAULT                <span class="literal">true</span></span><br><span class="line"> PrintConcurrentLocks   <span class="literal">false</span>                   DEFAULT                <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="查看指定的option"><a href="#查看指定的option" class="headerlink" title="查看指定的option"></a>查看指定的option</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[arthas@56963]$ vmoption PrintGCDetails</span><br><span class="line"> KEY                    VALUE                   ORIGIN                 WRITEABLE</span><br><span class="line">---------------------------------------------------------------------------------------------</span><br><span class="line"> PrintGCDetails         <span class="literal">false</span>                   MANAGEMENT             <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="更新指定的option"><a href="#更新指定的option" class="headerlink" title="更新指定的option"></a>更新指定的option</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[arthas@56963]$ vmoption PrintGCDetails <span class="literal">true</span></span><br><span class="line">Successfully updated the vm option.</span><br><span class="line">PrintGCDetails=<span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="stop命令"><a href="#stop命令" class="headerlink" title="stop命令"></a>stop命令</h3><p>之前有用户吐槽，不小心退出Arthas console之后，<code>shutdown</code>会关闭系统，因此增加了<code>stop</code>命令来退出arthas，功能和<code>shutdown</code>命令一致。</p><h2 id="通过tunnel-server连接不同网络的arthas"><a href="#通过tunnel-server连接不同网络的arthas" class="headerlink" title="通过tunnel server连接不同网络的arthas"></a>通过tunnel server连接不同网络的arthas</h2><ul><li><a href="https://alibaba.github.io/arthas/web-console.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/web-console.html</a></li></ul><p>在新版本里，增加了arthas tunnel server的功能，用户可以通过tunnel server很方便连接不同网络里的arthas agent，适合做统一管控。</p><h3 id="启动arthas时连接到tunnel-server"><a href="#启动arthas时连接到tunnel-server" class="headerlink" title="启动arthas时连接到tunnel server"></a>启动arthas时连接到tunnel server</h3><p>在启动arthas，可以传递<code>--tunnel-server</code>参数，比如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">as.sh --tunnel-server <span class="string">'ws://47.75.156.201:7777/ws'</span></span><br></pre></td></tr></table></figure><blockquote><p>目前<code>47.75.156.201</code>是一个测试服务器，用户可以自己搭建arthas tunnel server</p></blockquote><ul><li>如果有特殊需求，可以通过<code>--agent-id</code>参数里指定agentId。默认情况下，会生成随机ID。</li></ul><p>attach成功之后，会打印出agentId，比如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.</span><br><span class="line"> /  O  \ |  .--. <span class="string">''</span>--.  .--<span class="string">'|  '</span>--<span class="string">'  | /  O  \ '</span>   .-<span class="string">'</span></span><br><span class="line"><span class="string">|  .-.  ||  '</span>--<span class="string">'.'</span>   |  |   |  .--.  ||  .-.  |`.  `-.</span><br><span class="line">|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-<span class="string">'    |</span></span><br><span class="line"><span class="string">`--'</span> `--<span class="string">'`--'</span> <span class="string">'--'</span>   `--<span class="string">'   `--'</span>  `--<span class="string">'`--'</span> `--<span class="string">'`-----'</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">wiki      https://alibaba.github.io/arthas</span><br><span class="line">tutorials https://alibaba.github.io/arthas/arthas-tutorials</span><br><span class="line">version   3.1.2</span><br><span class="line">pid       86183</span><br><span class="line">time      2019-08-30 15:40:53</span><br><span class="line">id        URJZ5L48RPBR2ALI5K4V</span><br></pre></td></tr></table></figure><p>如果是启动时没有连接到 tunnel server，也可以在后续自动重连成功之后，通过 session命令来获取 agentId：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[arthas@86183]$ session</span><br><span class="line"> Name           Value</span><br><span class="line">-----------------------------------------------------</span><br><span class="line"> JAVA_PID       86183</span><br><span class="line"> SESSION_ID     f7273eb5-e7b0-4a00-bc5b-3fe55d741882</span><br><span class="line"> AGENT_ID       URJZ5L48RPBR2ALI5K4V</span><br><span class="line"> TUNNEL_SERVER  ws://47.75.156.201:7777/ws</span><br></pre></td></tr></table></figure><p>以上面的为例，在浏览器里访问 <a href="http://47.75.156.201:8080/" target="_blank" rel="noopener">http://47.75.156.201:8080/</a> ，输入 <code>agentId</code>，就可以连接到本机上的arthas了。</p><p><img src="https://alibaba.github.io/arthas/_images/arthas-tunnel-server.png" alt></p><h4 id="Arthas-tunnel-server的工作原理"><a href="#Arthas-tunnel-server的工作原理" class="headerlink" title="Arthas tunnel server的工作原理"></a>Arthas tunnel server的工作原理</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">browser &lt;-&gt; arthas tunnel server &lt;-&gt; arthas tunnel client &lt;-&gt; arthas agent</span><br></pre></td></tr></table></figure><p><a href="https://github.com/alibaba/arthas/blob/master/tunnel-server/README.md" target="_blank" rel="noopener">https://github.com/alibaba/arthas/blob/master/tunnel-server/README.md</a></p><h2 id="易用性持续提升"><a href="#易用性持续提升" class="headerlink" title="易用性持续提升"></a>易用性持续提升</h2><ul><li><p>提示符修改为<code>arthas@pid</code>形式，用户可以确定当前进程ID，避免多个进程时误操作</p>  <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[arthas@86183]$ help</span><br></pre></td></tr></table></figure></li><li><p>增加<code>ctrl + k</code>清屏快捷键</p></li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>总之，<code>3.1.2</code>版本的Arthas新增加了<code>logger/heapdump/vmoption/stop</code>命令，增加了tunnel server，方便统一管控。另外还有一些bug修复等，可以参考</p><ul><li>Release Note: <a href="https://github.com/alibaba/arthas/releases/tag/3.1.2" target="_blank" rel="noopener">https://github.com/alibaba/arthas/releases/tag/3.1.2</a></li></ul><p>最后，Arthas的在线教程考虑重新组织，欢迎大家参与，提出建议：</p><ul><li><a href="https://github.com/alibaba/arthas/issues/847" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/847</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;img src=&quot;https://alibaba.github.io/arthas/_images/arthas.png&quot; alt=&quot;Arthas&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Arthas&lt;/code&gt;是Alibaba开源的Java诊断工具，深受开发者喜爱。&lt;/p&gt;
&lt;
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="logger" scheme="http://hengyunabc.github.io/tags/logger/"/>
    
  </entry>
  
  <entry>
    <title>在bash高效匹配历史命令的技巧</title>
    <link href="http://hengyunabc.github.io/bash_completion_tips/"/>
    <id>http://hengyunabc.github.io/bash_completion_tips/</id>
    <published>2019-07-19T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="在bash里补全历史命令"><a href="#在bash里补全历史命令" class="headerlink" title="在bash里补全历史命令"></a>在bash里补全历史命令</h2><p>本文介绍本人觉得最好用的bash配置技巧，一次配置，每天受益。</p><p>在bash里，最常见的搜索历史命令的办法是<code>ctrl + r</code>，但是这个步骤太多，比较麻烦。</p><p>下面介绍一种非常快捷的补全方式。</p><p>执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl -L http://hengyunabc.github.io/bash_completion_install.sh | sh</span><br><span class="line"><span class="built_in">bind</span> -f  ~/.inputrc</span><br></pre></td></tr></table></figure><p>这样子，先输入部分命令，再按键盘的<code>Up/Down</code>就可以自动补全出历史命令了。</p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>实际上给<code>~/.inputrc</code>文件添加了下面的内容：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"\e[A"</span>: <span class="built_in">history</span>-search-backward</span><br><span class="line"><span class="string">"\e[B"</span>: <span class="built_in">history</span>-search-forward</span><br><span class="line"><span class="built_in">set</span> show-all-if-ambiguous on</span><br><span class="line"><span class="built_in">set</span> completion-ignore-case on</span><br></pre></td></tr></table></figure><p>前面两行自然是绑定了快捷键，后面两行是什么意思呢？</p><p><code>show-all-if-ambiguous</code> 是指tab补全时，按一次tab就会把最长匹配的自动补全。具体参考 <a href="https://stackoverflow.com/a/42193784" target="_blank" rel="noopener">https://stackoverflow.com/a/42193784</a></p><p><code>completion-ignore-case</code> 是指tab补全时，忽略大小写，这点也非常方便。</p><blockquote><p>注意，在修改完<code>~/.inputrc</code>文件，要显式执行<code>bind -f  ~/.inputrc</code>才会生效。</p></blockquote><p>题外话，在<code>arthas</code>里也支持了<code>Up/Down</code>自动补全历史命令这个特性，所以在<code>arthas</code>里自动补全历史命令非常的方便。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;在bash里补全历史命令&quot;&gt;&lt;a href=&quot;#在bash里补全历史命令&quot; class=&quot;headerlink&quot; title=&quot;在bash里补全历史命令&quot;&gt;&lt;/a&gt;在bash里补全历史命令&lt;/h2&gt;&lt;p&gt;本文介绍本人觉得最好用的bash配置技巧，一次配置，每天受益
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="bash" scheme="http://hengyunabc.github.io/tags/bash/"/>
    
  </entry>
  
  <entry>
    <title>Arthas源码分析--jad反编译原理</title>
    <link href="http://hengyunabc.github.io/arthas-jad/"/>
    <id>http://hengyunabc.github.io/arthas-jad/</id>
    <published>2019-06-27T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p>Arthas是阿里巴巴开源的Java应用诊断利器，本文介绍Arthas 3.1.1版本里<code>jad</code>命令的实现原理。</p><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li></ul><h2 id="jad命令介绍"><a href="#jad命令介绍" class="headerlink" title="jad命令介绍"></a><code>jad</code>命令介绍</h2><ul><li><a href="https://alibaba.github.io/arthas/jad.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/jad.html</a></li></ul><p>jad即java decompiler，把JVM已加载类的字节码反编译成Java代码。比如反编译<code>String</code>类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">$ jad java.lang.String</span><br><span class="line"> </span><br><span class="line">ClassLoader:</span><br><span class="line"> </span><br><span class="line">Location:</span><br><span class="line"> </span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* Decompiled with CFR .</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">package</span> java.lang;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">import</span> java.io.ObjectStreamField;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">String</span></span></span><br><span class="line"><span class="class"><span class="keyword">implements</span> <span class="title">Serializable</span>,</span></span><br><span class="line"><span class="class"><span class="title">Comparable</span>&lt;<span class="title">String</span>&gt;,</span></span><br><span class="line"><span class="class"><span class="title">CharSequence</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">char</span>[] value;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> hash;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = -<span class="number">6849794470754667710L</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ObjectStreamField[] serialPersistentFields = <span class="keyword">new</span> ObjectStreamField[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> Comparator&lt;String&gt; CASE_INSENSITIVE_ORDER = <span class="keyword">new</span> CaseInsensitiveComparator();</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">String</span><span class="params">(<span class="keyword">byte</span>[] arrby, <span class="keyword">int</span> n, <span class="keyword">int</span> n2)</span> </span>&#123;</span><br><span class="line">        String.checkBounds(arrby, n, n2);</span><br><span class="line">        <span class="keyword">this</span>.value = StringCoding.decode(arrby, n, n2);</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2 id="获取到类的字节码"><a href="#获取到类的字节码" class="headerlink" title="获取到类的字节码"></a>获取到类的字节码</h2><p>反编译有两部分工作：</p><ol><li>获取到字节码</li><li>反编译为Java代码</li></ol><p>那么怎么从运行的JVM里获取到字节码？</p><p>最常见的思路是，在<code>classpaths</code>下面查找，比如 <code>ClassLoader.getResource(&quot;java/lang/String.class&quot;)</code>，但是这样子查找到的字节码不一定对。比如可能有多个冲突的jar，或者有Java Agent修改了字节码。</p><h2 id="ClassFileTransformer机制"><a href="#ClassFileTransformer机制" class="headerlink" title="ClassFileTransformer机制"></a>ClassFileTransformer机制</h2><p>从JDK 1.5起，有一套<code>ClassFileTransformer</code>的机制，Java Agent通过<code>Instrumentation</code>注册<code>ClassFileTransformer</code>，那么在类加载或者<code>retransform</code>时就可以回调修改字节码。</p><p>显然，在Arthas里，要增强的类是已经被加载的，所以它们的字节码都是在<code>retransform</code>时被修改的。<br>通过显式调用<code>Instrumentation.retransformClasses(Class&lt;?&gt;...)</code>可以触发回调。</p><p>Arthas里增强字节码的<code>watch</code>/<code>trace</code>/<code>stack</code>/<code>tt</code>等命令都是通过<code>ClassFileTransformer</code>来实现的。</p><p><code>ClassFileTransformer</code>的接口如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ClassFileTransformer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">byte</span>[]</span><br><span class="line">    transform(  ClassLoader         loader,</span><br><span class="line">                String              className,</span><br><span class="line">                Class&lt;?&gt;            classBeingRedefined,</span><br><span class="line">                ProtectionDomain    protectionDomain,</span><br><span class="line">                <span class="keyword">byte</span>[]              classfileBuffer)</span><br><span class="line">        <span class="keyword">throws</span> IllegalClassFormatException;</span><br></pre></td></tr></table></figure><p>看到这里，读者应该猜到<code>jad</code>是怎么获取到字节码的了：</p><ol><li>注册一个<code>ClassFileTransformer</code></li><li>通过<code>Instrumentation.retransformClasses</code>触发回调</li><li>在回调的<code>transform</code>函数里获取到字节码</li><li>删掉注册的<code>ClassFileTransformer</code></li></ol><h2 id="使用cfr来反编译"><a href="#使用cfr来反编译" class="headerlink" title="使用cfr来反编译"></a>使用cfr来反编译</h2><p>获取到字节码之后，怎样转换为Java代码呢？</p><p>以前大家使用比较多的反编译软件可能是<code>jd-gui</code>，但是它不支持JDK8的lambda语法和一些新版本JDK的特性。</p><p>后面比较成熟的反编译软件是<code>cfr</code>，它以前是不开源的。直到最近的<code>0.145</code>版本，作者终于开源了，可喜可贺。地址是</p><ul><li><a href="https://github.com/leibnitz27/cfr" target="_blank" rel="noopener">https://github.com/leibnitz27/cfr</a></li></ul><p>在Arthas <code>jad</code>命令里，通过调用<code>cfr</code>来完成反编译。</p><h2 id="jad命令的缺陷"><a href="#jad命令的缺陷" class="headerlink" title="jad命令的缺陷"></a><code>jad</code>命令的缺陷</h2><p>99%的情况下，<code>jad</code>命令dump下来的字节码是准确的，除了一些极端情况。</p><ol><li>因为JVM里注册的<code>ClassFileTransformer</code>可能有多个，那么在JVM里运行的字节码里，可能是被多个<code>ClassFileTransformer</code>处理过的。</li><li>触发了<code>retransformClasses</code>之后，这些注册的<code>ClassFileTransformer</code>会被依次回，上一个处理的字节码传递到下一个。<br>所以不能保证这些<code>ClassFileTransformer</code>第二次执行会返回同样的结果。</li><li>有可能一些<code>ClassFileTransformer</code>会被删掉，触发<code>retransformClasses</code>之后，之前的一些修改就会丢失掉。</li></ol><p>所以目前在Arthas里，如果开两个窗口，一个窗口执行<code>watch</code>/<code>tt</code>等命令，另一个窗口对这个类执行<code>jad</code>，那么可以观察到<code>watch</code>/<code>tt</code>停止了输出，实际上是因为字节码在触发了<code>retransformClasses</code>之后，<code>watch</code>/<code>tt</code>所做的修改丢失了。</p><h2 id="精确获取JVM内运行的java字节码的办法"><a href="#精确获取JVM内运行的java字节码的办法" class="headerlink" title="精确获取JVM内运行的java字节码的办法"></a>精确获取JVM内运行的java字节码的办法</h2><p>如果想精确获取到JVM内运行的Java字节码，可以使用这个<code>dumpclass</code>工具，它是通过<code>sa-jdi.jar</code>来实现的，保证dump下来的字节码是JVM内所运行的。</p><ul><li><a href="https://github.com/hengyunabc/dumpclass" target="_blank" rel="noopener">https://github.com/hengyunabc/dumpclass</a></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总结<code>jad</code>命令的工作原理：</p><ul><li>通过注册<code>ClassFileTransformer</code>，再触发<code>retransformClasses</code>来获取字节码</li><li>通过<code>cfr</code>来反编译</li><li><code>ClassFileTransformer</code>的方式来获取字节码有一定缺陷</li><li>通过<code>dumpclass</code>工具可以精确获取字节码</li></ul><p><code>jad</code>命令可以在线上快速检查运行时的代码，并且结合<code>mc</code>/<code>redefine</code>命令可以热更新代码：</p><ul><li>jad/mc/redefine线上热更新一条龙: <a href="http://hengyunabc.github.io/arthas-online-hotswap/">http://hengyunabc.github.io/arthas-online-hotswap/</a></li></ul><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li>Arthas开源：<a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>dumpclass: <a href="https://github.com/hengyunabc/dumpclass" target="_blank" rel="noopener">https://github.com/hengyunabc/dumpclass</a></li><li><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Arthas是阿里巴巴开源的Java应用诊断利器，本文介绍Arthas 3.1.1版本里&lt;code&gt;jad&lt;/code&gt;命令的实现原理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alibaba/arthas&quot; target=&quot;_bl
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="bytecode" scheme="http://hengyunabc.github.io/tags/bytecode/"/>
    
      <category term="jad" scheme="http://hengyunabc.github.io/tags/jad/"/>
    
  </entry>
  
  <entry>
    <title>通过Heap dump排查Java JMX连接不上的问题</title>
    <link href="http://hengyunabc.github.io/jmx-local-connect-problem/"/>
    <id>http://hengyunabc.github.io/jmx-local-connect-problem/</id>
    <published>2019-06-11T01:01:41.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>最近排查一个JMX本地连接问题，记录一下。</p><p>我们的启动脚本在应用启动后，会通过JMX来动态检查应用状态，那么这里就需要动态启动JMX功能了。</p><h3 id="动态打开Java进程的JMX端口"><a href="#动态打开Java进程的JMX端口" class="headerlink" title="动态打开Java进程的JMX端口"></a>动态打开Java进程的JMX端口</h3><ol><li>通过下面的代码，可以动态的让目标进程加载<code>management-agent</code></li><li>打开JMX功能后，通过获取<code>com.sun.management.jmxremote.localConnectorAddress</code>的Agent Property，可以获取到JMX URL</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> MBeanServerConnection <span class="title">connect</span><span class="params">(String pid)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    String address = attachJmx(pid);</span><br><span class="line"></span><br><span class="line">    JMXServiceURL serviceURL = <span class="keyword">new</span> JMXServiceURL(address);</span><br><span class="line">    connector = JMXConnectorFactory.connect(serviceURL);</span><br><span class="line">    <span class="keyword">return</span> connector.getMBeanServerConnection();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> String <span class="title">attachJmx</span><span class="params">(String pid)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        virtualmachine = VirtualMachine.attach(pid);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (AttachNotSupportedException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IOException(e);</span><br><span class="line">    &#125;</span><br><span class="line">    String javaHome = virtualmachine.getSystemProperties().getProperty(<span class="string">"java.home"</span>);</span><br><span class="line">    String agentPath = javaHome + File.separator + <span class="string">"jre"</span> + File.separator + <span class="string">"lib"</span> + File.separator</span><br><span class="line">                       + <span class="string">"management-agent.jar"</span>;</span><br><span class="line">    File file = <span class="keyword">new</span> File(agentPath);</span><br><span class="line">    <span class="keyword">if</span> (!file.exists()) &#123;</span><br><span class="line">        agentPath = javaHome + File.separator + <span class="string">"lib"</span> + File.separator + <span class="string">"management-agent.jar"</span>;</span><br><span class="line">        file = <span class="keyword">new</span> File(agentPath);</span><br><span class="line">        <span class="keyword">if</span> (!file.exists()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IOException(<span class="string">"Management agent not found"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    agentPath = file.getCanonicalPath();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        virtualmachine.loadAgent(agentPath, <span class="string">"com.sun.management.jmxremote"</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (AgentLoadException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IOException(e);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (AgentInitializationException agentinitializationexception) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IOException(agentinitializationexception);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Properties properties = virtualmachine.getAgentProperties();</span><br><span class="line">    String address = (String) properties.get(<span class="string">"com.sun.management.jmxremote.localConnectorAddress"</span>);</span><br><span class="line">    virtualmachine.detach();</span><br><span class="line">    <span class="keyword">return</span> address;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="为什么JMX连接会失败？"><a href="#为什么JMX连接会失败？" class="headerlink" title="为什么JMX连接会失败？"></a>为什么JMX连接会失败？</h3><p>在用上面的代码动态去连接目标进程时，抛出了下面的异常：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">java.rmi.ConnectException: Connection refused to host: 11.164.235.11; nested exception is:</span><br><span class="line">  java.net.ConnectException: Connection refused</span><br><span class="line">  at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:619)</span><br><span class="line">  at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216)</span><br><span class="line">  at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)</span><br><span class="line">  at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:130)</span><br><span class="line">  at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:227)</span><br><span class="line">  at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:179)</span><br><span class="line">  at com.sun.proxy.$Proxy0.newClient(Unknown Source)</span><br><span class="line">  at javax.management.remote.rmi.RMIConnector.getConnection(RMIConnector.java:2430)</span><br><span class="line">  at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:308)</span><br><span class="line">  at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:270)</span><br><span class="line">  at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:229)</span><br><span class="line">  at com.test.jmx.JmxLocalConnector.connect(JmxLocalConnector.java:28)</span><br><span class="line">Caused by: java.net.ConnectException: Connection refused</span><br><span class="line">  at java.net.PlainSocketImpl.socketConnect(Native Method)</span><br><span class="line">  at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)</span><br><span class="line">  at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)</span><br><span class="line">  at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)</span><br><span class="line">  at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)</span><br><span class="line">  at java.net.Socket.connect(Socket.java:589)</span><br><span class="line">  at java.net.Socket.connect(Socket.java:538)</span><br><span class="line">  at java.net.Socket.&lt;init&gt;(Socket.java:434)</span><br><span class="line">  at java.net.Socket.&lt;init&gt;(Socket.java:211)</span><br><span class="line">  at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:40)</span><br><span class="line">  at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:148)</span><br><span class="line">  at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:613)</span><br><span class="line">  ... 13 more</span><br></pre></td></tr></table></figure><ul><li>检查本机IP是 <code>11.164.234.171</code></li><li>为什么rmi连接的是一个外部的IP <code>11.164.235.11</code>？</li></ul><p>通过调试，发现<code>management-agent</code>加载成功了，<code>localConnectorAddress</code>的值是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmx:rmi://127.0.0.1/stub/rO0ABXN9AAAAAQAlamF2YXgubWFuYWdlbWVudC5yZW1vdGUucm1pLlJNSVNlcnZlcnhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAC1qYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN0SW52b2NhdGlvbkhhbmRsZXIAAAAAAAAAAgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc4AAtVbmljYXN0UmVmMgAADTExLjE2NC4yMzUuMTEAAIfoCEScYyGQodFlwEdFAAABawK/zE6AAQB4</span><br></pre></td></tr></table></figure><p><strong>为什么显示的是<code>127.0.0.1</code>，但实际连接的是<code>11.164.235.11</code>？是不是在连接时出的问题？</strong></p><p>再仔细调试，发现</p><ol><li>jmx是获取到stub后的字符串</li><li>做base64解密，再通过<code>ObjectInputStream</code>解析</li><li><code>readObject</code>得到<code>RMIServer</code>对象来连接的。</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//javax.management.remote.rmi.RMIConnector.findRMIServer(JMXServiceURL, Map&lt;String, Object&gt;)</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//--------------------------------------------------------------------</span></span><br><span class="line">    <span class="comment">// Private stuff - RMIServer creation</span></span><br><span class="line">    <span class="comment">//--------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> RMIServer <span class="title">findRMIServer</span><span class="params">(JMXServiceURL directoryURL,</span></span></span><br><span class="line"><span class="function"><span class="params">            Map&lt;String, Object&gt; environment)</span></span></span><br><span class="line"><span class="function">            <span class="keyword">throws</span> NamingException, IOException </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">boolean</span> isIiop = RMIConnectorServer.isIiopURL(directoryURL,<span class="keyword">true</span>);</span><br><span class="line">        <span class="keyword">if</span> (isIiop) &#123;</span><br><span class="line">            <span class="comment">// Make sure java.naming.corba.orb is in the Map.</span></span><br><span class="line">            environment.put(EnvHelp.DEFAULT_ORB,resolveOrb(environment));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        String path = directoryURL.getURLPath();</span><br><span class="line">        <span class="keyword">int</span> end = path.indexOf(<span class="string">';'</span>);</span><br><span class="line">        <span class="keyword">if</span> (end &lt; <span class="number">0</span>) end = path.length();</span><br><span class="line">        <span class="keyword">if</span> (path.startsWith(<span class="string">"/jndi/"</span>))</span><br><span class="line">            <span class="keyword">return</span> findRMIServerJNDI(path.substring(<span class="number">6</span>,end), environment, isIiop);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (path.startsWith(<span class="string">"/stub/"</span>))</span><br><span class="line">            <span class="keyword">return</span> findRMIServerJRMP(path.substring(<span class="number">6</span>,end), environment, isIiop);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (path.startsWith(<span class="string">"/ior/"</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!IIOPHelper.isAvailable())</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> IOException(<span class="string">"iiop protocol not available"</span>);</span><br><span class="line">            <span class="keyword">return</span> findRMIServerIIOP(path.substring(<span class="number">5</span>,end), environment, isIiop);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">final</span> String msg = <span class="string">"URL path must begin with /jndi/ or /stub/ "</span> +</span><br><span class="line">                    <span class="string">"or /ior/: "</span> + path;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> MalformedURLException(msg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> RMIServer <span class="title">findRMIServerJRMP</span><span class="params">(String base64, Map&lt;String, ?&gt; env, <span class="keyword">boolean</span> isIiop)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="comment">// could forbid "iiop:" URL here -- but do we need to?</span></span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">byte</span>[] serialized;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            serialized = base64ToByteArray(base64);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalArgumentException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> MalformedURLException(<span class="string">"Bad BASE64 encoding: "</span> +</span><br><span class="line">                    e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> ByteArrayInputStream bin = <span class="keyword">new</span> ByteArrayInputStream(serialized);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> ClassLoader loader = EnvHelp.resolveClientClassLoader(env);</span><br><span class="line">        <span class="keyword">final</span> ObjectInputStream oin =</span><br><span class="line">                (loader == <span class="keyword">null</span>) ?</span><br><span class="line">                    <span class="keyword">new</span> ObjectInputStream(bin) :</span><br><span class="line">                    <span class="keyword">new</span> ObjectInputStreamWithLoader(bin, loader);</span><br><span class="line">        <span class="keyword">final</span> Object stub;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            stub = oin.readObject();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> MalformedURLException(<span class="string">"Class not found: "</span> + e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> (RMIServer)stub;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>通过代码处理，发现</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rO0ABXN9AAAAAQAlamF2YXgubWFuYWdlbWVudC5yZW1vdGUucm1pLlJNSVNlcnZlcnhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAC1qYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN0SW52b2NhdGlvbkhhbmRsZXIAAAAAAAAAAgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc4AAtVbmljYXN0UmVmMgAADTExLjE2NC4yMzUuMTEAAIfoCEScYyGQodFlwEdFAAABawK/zE6AAQB4</span><br></pre></td></tr></table></figure><p>转换为了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RMIServerImpl_Stub[UnicastRef2 [liveRef: [endpoint:[11.164.235.11:26449](remote),objID:[-5ddae53d:16b0887d710:-7fff, 7209064096623493021]]]]</span><br></pre></td></tr></table></figure><p><strong>可见RMI Server的IP的确是<code>11.164.235.11</code>。</strong></p><p>那么现在问题变成了：</p><ul><li>为什么JVM动态加载了<code>management-agent</code>，得到的JMX URL是指向外部IP的？</li></ul><h3 id="通过heap-dump定位IP字符串"><a href="#通过heap-dump定位IP字符串" class="headerlink" title="通过heap dump定位IP字符串"></a>通过heap dump定位IP字符串</h3><p>但是调试<code>management-agent</code>的加载过程可能会比较痛苦，于是考虑从别的地方入手。</p><p>从上面的调查里，发现<code>management-agent</code>启动之后，<code>11.164.235.11</code>这个外部IP就会出现在JVM内存里，那么考虑用heap dump的方式来定位。</p><p>通过执行heap dump，再用<code>jvisualvm</code>来分析。</p><p>用OQL来搜索所有包含<code>11.164.235.11</code>的String：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> s <span class="keyword">from</span> java.lang.String s <span class="keyword">where</span> s.toString().equals(<span class="string">"11.164.235.11"</span>)</span><br></pre></td></tr></table></figure><p>可以发现有好几个结果：</p><p><img src="https://user-images.githubusercontent.com/1683936/58687979-bde91880-83b5-11e9-88f8-ee498e22337f.png" alt></p><p>再依次点开，查看引用，<strong>发现其中一个引用的字段名是<code>localHost</code></strong>：</p><p><img src="https://user-images.githubusercontent.com/1683936/58688109-1c15fb80-83b6-11e9-8afb-cfbedfd0f036.jpg" alt></p><p><strong>因此可以猜测：是不是localHost域名解析有问题？</strong></p><h3 id="定位localHost域名解析问题"><a href="#定位localHost域名解析问题" class="headerlink" title="定位localHost域名解析问题"></a>定位localHost域名解析问题</h3><p>执行hostname命令，得到机器名，再ping一下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$hostname</span><br><span class="line">web-app201641.we42</span><br><span class="line"></span><br><span class="line">$ping web-app201641.we42</span><br><span class="line">PING web-app201641.we42 (11.164.235.11) 56(84) bytes of data.</span><br></pre></td></tr></table></figure><p>发现本机被解析到<code>11.164.235.11</code>了，但是本机的IP是<code>11.164.234.171</code>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ifconfig</span><br><span class="line">eth0: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1500</span><br><span class="line">        inet 11.164.234.171  netmask 255.255.255.0  broadcast 11.164.234.255</span><br></pre></td></tr></table></figure><p>到这里，大概猜到原因了，检查下 <code>/etc/hosts</code>文件，果然发现有配置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">11.164.235.11  web-app201641.we42</span><br></pre></td></tr></table></figure><p>把这个错误的host配置去掉之后，再执行jmx连接终于成功了。</p><p>为什么会有错误的hosts配置呢？据说是机器迁移时遗留的。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>动态JMX连接的工作原理：</p><ol><li>让目标<code>VirtualMachine</code>动态加载<code>management-agent</code></li><li>从Agent Properties里获取到JMX连接地址：<code>com.sun.management.jmxremote.localConnectorAddress</code></li><li>JMX URL里带<code>stub</code>的字符串，实际上是base64转换为byte[]，再用<code>ObjectInputStream</code>转换为<code>RMIServer</code></li><li>JMX实际上是通过RMI来连接的</li></ol><p>排查问题的关键：</p><ol><li>定位错误连接的IP</li><li>heap dump</li><li>用OQL从heap dump里查找IP字符串，再查看相关的引用来获取信息</li></ol><h3 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h3><ul><li><a href="https://visualvm.github.io/" target="_blank" rel="noopener">ViauslVM</a></li><li><a href="http://cr.openjdk.java.net/~sundar/8022483/webrev.01/raw_files/new/src/share/classes/com/sun/tools/hat/resources/oqlhelp.html" target="_blank" rel="noopener">Object Query Language (OQL)</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h3 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h3&gt;&lt;p&gt;最近排查一个JMX本地连接问题，记录一下。&lt;/p&gt;
&lt;p&gt;我们的启动脚本在应用启动后，会通过JMX来动态检查应用状态，那么这里就需要动态启动
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="jvm" scheme="http://hengyunabc.github.io/tags/jvm/"/>
    
      <category term="visualvm" scheme="http://hengyunabc.github.io/tags/visualvm/"/>
    
      <category term="jmx" scheme="http://hengyunabc.github.io/tags/jmx/"/>
    
  </entry>
  
  <entry>
    <title>Arthas实践：是哪个Controller处理了请求？</title>
    <link href="http://hengyunabc.github.io/arthas-springmvc-controller/"/>
    <id>http://hengyunabc.github.io/arthas-springmvc-controller/</id>
    <published>2019-05-31T01:01:41.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Arthas是阿里巴巴开源的Java诊断利器，深受开发者喜爱。</p><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn" target="_blank" rel="noopener">Arthas在线教程</a></li></ul><p>之前分享了Arthas怎样排查 404/401 的问题: <a href="http://hengyunabc.github.io/arthas-spring-boot-404-401/">http://hengyunabc.github.io/arthas-spring-boot-404-401/</a></p><p>我们可以快速定位一个请求是被哪些<code>Filter</code>拦截的，或者请求最终是由哪些<code>Servlet</code>处理的。</p><p>但有时，我们想知道一个请求是被哪个Spring MVC Controller处理的。如果翻代码的话，会比较难找，并且不一定准确。</p><p>通过Arthas可以精确定位是哪个<code>Controller</code>处理请求。</p><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p>还是以这个demo为例： <a href="https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-404-401" target="_blank" rel="noopener">https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-404-401</a></p><p>启动之后，访问： <a href="http://localhost:8080/user/1" target="_blank" rel="noopener">http://localhost:8080/user/1</a> ，会返回一个user对象。那么这个请求是被哪个<code>Controller</code>处理的呢？</p><h2 id="trace定位DispatcherServlet"><a href="#trace定位DispatcherServlet" class="headerlink" title="trace定位DispatcherServlet"></a>trace定位DispatcherServlet</h2><p>我们先试下跟踪<code>Servlet</code>：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">trace javax.servlet.Servlet *</span><br></pre></td></tr></table></figure><p>从trace的结果可以看出来，请求最终是被<code>DispatcherServlet#doDispatch()</code>处理了，但是没有办法知道是哪个<code>Controller</code>处理。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">`---[27.453122ms] org.springframework.web.servlet.DispatcherServlet:doDispatch()</span><br><span class="line">    +---[0.005822ms] org.springframework.web.context.request.async.WebAsyncUtils:getAsyncManager() #929</span><br><span class="line">    +---[0.107365ms] org.springframework.web.servlet.DispatcherServlet:checkMultipart() #936</span><br><span class="line">    |   `---[0.062451ms] org.springframework.web.servlet.DispatcherServlet:checkMultipart()</span><br><span class="line">    |       `---[0.016924ms] org.springframework.web.multipart.MultipartResolver:isMultipart() #1093</span><br><span class="line">    +---[2.103935ms] org.springframework.web.servlet.DispatcherServlet:getHandler() #940</span><br><span class="line">    |   `---[2.036042ms] org.springframework.web.servlet.DispatcherServlet:getHandler()</span><br></pre></td></tr></table></figure><h2 id="watch定位handler"><a href="#watch定位handler" class="headerlink" title="watch定位handler"></a>watch定位handler</h2><p>trace结果里把调用的行号打印出来了，我们可以直接在IDE里查看代码（也可以用jad命令反编译）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// org.springframework.web.servlet.DispatcherServlet</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doDispatch</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">HttpServletRequest processedRequest = request;</span><br><span class="line">HandlerExecutionChain mappedHandler = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">boolean</span> multipartRequestParsed = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">ModelAndView mv = <span class="keyword">null</span>;</span><br><span class="line">Exception dispatchException = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">processedRequest = checkMultipart(request);</span><br><span class="line">multipartRequestParsed = (processedRequest != request);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Determine handler for the current request.</span></span><br><span class="line">mappedHandler = getHandler(processedRequest);</span><br><span class="line"><span class="keyword">if</span> (mappedHandler == <span class="keyword">null</span> || mappedHandler.getHandler() == <span class="keyword">null</span>) &#123;</span><br><span class="line">noHandlerFound(processedRequest, response);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>仔细看代码，可以发现<code>mappedHandler = getHandler(processedRequest);</code>得到了处理请求的handler</li></ul><p>下面用<code>watch</code>命令来获取<code>getHandler</code>函数的返回结果。</p><p><code>watch</code>之后，再次访问 <a href="http://localhost:8080/user/1" target="_blank" rel="noopener">http://localhost:8080/user/1</a> </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ watch org.springframework.web.servlet.DispatcherServlet getHandler returnObj</span><br><span class="line">Press Q or Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:1 , method-cnt:1) cost <span class="keyword">in</span> 332 ms.</span><br><span class="line">ts=2019-06-04 11:38:06; [cost=2.75218ms] result=@HandlerExecutionChain[</span><br><span class="line">    logger=@SLF4JLocationAwareLog[org.apache.commons.logging.impl.SLF4JLocationAwareLog@665c08a],</span><br><span class="line">    handler=@HandlerMethod[public com.example.demo.arthas.user.User com.example.demo.arthas.user.UserController.findUserById(java.lang.Integer)],</span><br><span class="line">    interceptors=null,</span><br><span class="line">    interceptorList=@ArrayList[isEmpty=<span class="literal">false</span>;size=2],</span><br><span class="line">    interceptorIndex=@Integer[-1],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>可以看到处理请求的handler是 <code>com.example.demo.arthas.user.UserController.findUserById</code>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>Spring MVC的请求是在<code>DispatcherServlet</code>分发，查找到对应的<code>mappedHandler</code>来处理</li><li>使用Arthas时，灵活结合代码，可以快速精确定位问题</li></ul><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li><a href="https://alibaba.github.io/arthas/watch.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/watch.html</a></li><li><a href="https://alibaba.github.io/arthas/trace.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/trace.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;Arthas是阿里巴巴开源的Java诊断利器，深受开发者喜爱。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.c
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="spring" scheme="http://hengyunabc.github.io/tags/spring/"/>
    
      <category term="jvm" scheme="http://hengyunabc.github.io/tags/jvm/"/>
    
  </entry>
  
  <entry>
    <title>Arthas Github Star破万啦，回顾开源历程，展望未来</title>
    <link href="http://hengyunabc.github.io/arthas-star-10000/"/>
    <id>http://hengyunabc.github.io/arthas-star-10000/</id>
    <published>2019-02-20T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、Arthas的历史"><a href="#一、Arthas的历史" class="headerlink" title="一、Arthas的历史"></a>一、Arthas的历史</h2><p><img src="https://alibaba.github.io/arthas/_images/arthas.png" alt="Arthas"></p><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li></ul><p><code>Arthas</code>在阿里巴巴内部起源于2015年，当时微服务方兴未艾，我们团队一方面专注Spring Boot落地，提高开发效率，另外一方面，希望可以提高线上排查问题的能力和效率。当时我们经过选型讨论，选择基于<code>Greys</code>来开发，提供更好的应用诊断体验。</p><p>我们在用户体验上做了大量的改进：彩色UI，Web Console，内网一键诊断等。下面是内网一键在线诊断的截图，很多同事线上诊断问题的必备工具：</p><p><img src="https://user-images.githubusercontent.com/1683936/53077314-b0a5cd80-352c-11e9-99c7-25e5561c0ec0.png" alt="image"></p><h2 id="二、Arthas开源之后的工作"><a href="#二、Arthas开源之后的工作" class="headerlink" title="二、Arthas开源之后的工作"></a>二、Arthas开源之后的工作</h2><p>尽管<code>Arthas</code>在阿里内部广受好评，但它只是内部工具，很多离职同事都在一些文章里提到。做为Java开发的一员，我们使用到了很多开源的代码和工具，我们也希望可以提升广大开发人员的诊断效率，因此在2018年9月底，我们正式开源了Arthas。</p><p>在开源之后，<code>Arthas</code>多次登顶Github Trending，还被<code>@Java</code>官方twitter转发：</p><p><img src="https://user-images.githubusercontent.com/1683936/53077222-7fc59880-352c-11e9-837f-14948ac7c948.png" alt="image"></p><p>在开源之后，<code>Arthas</code>发布了3个release版本，做了大量的改进：</p><ul><li>全新的LOGO</li><li>arthas-boot统一跨平台体验</li><li>Arthas在线教程</li><li>全新版本的Web Console</li><li>全新的中英文档，感谢社区的大力支持</li><li>JDK11全面支持，lamda类支持</li><li>Docker支持</li><li>灵活的ognl命令</li><li>增加内存编译器，实现jad/mc/redefine一条龙</li><li>Q键退出，history匹配，快捷键支持</li><li>不断完善的自动补全支持</li><li>重构重定向的支持</li></ul><p>目前，<code>Arthas</code> Github star数10000+，月下载量7000+。在开源中国2018开源软件排行榜里，<code>Arthas</code>获得国产新秀榜第一名。</p><p>这是对我们过去工作的支持和肯定，也是我们持续改进<code>Arthas</code>的动力。</p><p><img src="https://user-images.githubusercontent.com/1683936/53077846-edbe8f80-352d-11e9-8764-6c97a0fc91ff.png" alt="Arthas Github"></p><h2 id="三、感谢贡献者们"><a href="#三、感谢贡献者们" class="headerlink" title="三、感谢贡献者们"></a>三、感谢贡献者们</h2><p><code>Arthas</code>在开源以来，不断收获到国内外贡献者的支持，目前已有40+贡献者，非常感谢他们的工作：</p><p><img src="https://opencollective.com/arthas/contributors.svg?width=890&amp;button=false" alt="Arthas贡献者"></p><p>特别感谢<code>@Hearen</code>贡献了大部分的英文翻译，<code>@wetsion</code>重构了新版本的Web Console。</p><p>参与贡献： <a href="https://github.com/alibaba/arthas/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener">https://github.com/alibaba/arthas/blob/master/CONTRIBUTING.md</a></p><h2 id="四、Arthas实践系列文章"><a href="#四、Arthas实践系列文章" class="headerlink" title="四、Arthas实践系列文章"></a>四、Arthas实践系列文章</h2><p>做为Arthas的用户，我们在实践中积累了很多经验，总结为一系列的文章，希望对大家线上排查问题有帮助：</p><ul><li><a href="https://mp.weixin.qq.com/s/fsHhkwfE-vrQRkQvUL1JGA" target="_blank" rel="noopener">Arthas实践–jad/mc/redefine线上热更新一条龙</a></li><li><a href="https://mp.weixin.qq.com/s/PlCwMhEFdtgHZTNBaNVIRQ" target="_blank" rel="noopener">Alibaba Arthas实践–获取到Spring Context，然后为所欲为</a></li><li><a href="https://mp.weixin.qq.com/s/RQmYur3m2AsXFiuLwUCDnw" target="_blank" rel="noopener">Arthas实践–快速排查Spring Boot应用404/401问题</a></li><li><a href="https://mp.weixin.qq.com/s/-jhSV86_2E7WYhXbtVpAGQ" target="_blank" rel="noopener">当Dubbo遇上Arthas：排查问题的实践</a></li><li><a href="https://mp.weixin.qq.com/s/boGS0VK45mZ_zT25K44S5Q" target="_blank" rel="noopener">使用Arthas抽丝剥茧排查线上应用日志打满问题</a></li><li><a href="https://mp.weixin.qq.com/s/eCIDxM9lXYX0cM7LGgfn1w" target="_blank" rel="noopener">深入Spring Boot：利用Arthas排查NoSuchMethodError</a></li></ul><h2 id="五、Arthas-4-0规划"><a href="#五、Arthas-4-0规划" class="headerlink" title="五、Arthas 4.0规划"></a>五、Arthas 4.0规划</h2><ul><li>提供一个新的字节码框架，名为<code>bytekit</code></li><li>插件化支持</li><li>view分层，支持Web白屏化</li></ul><p>详细链接： <a href="https://github.com/alibaba/arthas/issues/536" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/536</a> ，同时希望大家可以提出建议和参与。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;一、Arthas的历史&quot;&gt;&lt;a href=&quot;#一、Arthas的历史&quot; class=&quot;headerlink&quot; title=&quot;一、Arthas的历史&quot;&gt;&lt;/a&gt;一、Arthas的历史&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;https://alibaba.github.i
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="github" scheme="http://hengyunabc.github.io/tags/github/"/>
    
  </entry>
  
  <entry>
    <title>Arthas实践--jad/mc/redefine线上热更新一条龙</title>
    <link href="http://hengyunabc.github.io/arthas-online-hotswap/"/>
    <id>http://hengyunabc.github.io/arthas-online-hotswap/</id>
    <published>2019-02-19T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>尽管在生产环境热更新代码，并不是很好的行为，很可能导致：热更不规范，同事两行泪。</p><p>但很多时候我们的确希望能热更新代码，比如：</p><blockquote><p>线上排查问题，找到修复思路了，但应用重启之后，环境现场就变了，难以复现。怎么验证修复方案？</p></blockquote><p>又比如：</p><blockquote><p>本地开发时，发现某个开源组件有bug，希望修改验证。如果是自己编译开源组件再发布，流程非常的长，还不一定能编译成功。有没有办法快速测试？</p></blockquote><p>Arthas是阿里巴巴开源的Java应用诊断利器，深受开发者喜爱。</p><p>下面介绍利用Arthas 3.1.0版本的 <code>jad</code>/<code>mc</code>/<code>redefine</code> 一条龙来热更新代码。</p><ul><li>Arthas: <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>jad命令：<a href="https://alibaba.github.io/arthas/jad.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/jad.html</a></li><li>mc命令：<a href="https://alibaba.github.io/arthas/mc.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/mc.html</a></li><li>redefine命令：<a href="https://alibaba.github.io/arthas/redefine.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/redefine.html</a></li></ul><h2 id="Arthas在线教程"><a href="#Arthas在线教程" class="headerlink" title="Arthas在线教程"></a>Arthas在线教程</h2><p>下面通过Arthas在线教程演示热更新代码的过程。</p><ul><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-advanced" target="_blank" rel="noopener">Arthas进阶教程</a></li></ul><p><img src="/img/arthas-online-hotswap.png" alt="arthas-online-hotswap"></p><p>在例子里，访问 <code>curl http://localhost/user/0</code>，会返回500错误：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"timestamp"</span>: <span class="number">1550223186170</span>,</span><br><span class="line">    <span class="string">"status"</span>: <span class="number">500</span>,</span><br><span class="line">    <span class="string">"error"</span>: <span class="string">"Internal Server Error"</span>,</span><br><span class="line">    <span class="string">"exception"</span>: <span class="string">"java.lang.IllegalArgumentException"</span>,</span><br><span class="line">    <span class="string">"message"</span>: <span class="string">"id &lt; 1"</span>,</span><br><span class="line">    <span class="string">"path"</span>: <span class="string">"/user/0"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面通过热更新代码，修改这个逻辑。</p><h2 id="jad反编译代码"><a href="#jad反编译代码" class="headerlink" title="jad反编译代码"></a>jad反编译代码</h2><p>反编译<code>UserController</code>，保存到 <code>/tmp/UserController.java</code>文件里。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jad --<span class="built_in">source</span>-only com.example.demo.arthas.user.UserController &gt; /tmp/UserController.java</span><br></pre></td></tr></table></figure><h2 id="修改反编译出来的代码"><a href="#修改反编译出来的代码" class="headerlink" title="修改反编译出来的代码"></a>修改反编译出来的代码</h2><p>用文本编辑器修改<code>/tmp/UserController.java</code>，把抛出异常改为正常返回：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(value=&#123;<span class="string">"/user/&#123;id&#125;"</span>&#125;)</span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUserById</span><span class="params">(@PathVariable Integer id)</span> </span>&#123;</span><br><span class="line">    logger.info(<span class="string">"id: &#123;&#125;"</span>, (Object)id);</span><br><span class="line">    <span class="keyword">if</span> (id != <span class="keyword">null</span> &amp;&amp; id &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(id, <span class="string">"name"</span> + id);</span><br><span class="line">        <span class="comment">// throw new IllegalArgumentException("id &lt; 1");</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> User(id.intValue(), <span class="string">"name"</span> + id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="sc查找加载UserController的ClassLoader"><a href="#sc查找加载UserController的ClassLoader" class="headerlink" title="sc查找加载UserController的ClassLoader"></a>sc查找加载UserController的ClassLoader</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sc -d *UserController | grep classLoaderHash</span><br><span class="line"> classLoaderHash   1be6f5c3</span><br></pre></td></tr></table></figure><p>可以发现是spring boot的 <code>LaunchedURLClassLoader@1be6f5c3</code> 加载的。</p><h2 id="mc内存编译代码"><a href="#mc内存编译代码" class="headerlink" title="mc内存编译代码"></a>mc内存编译代码</h2><p>保存好<code>/tmp/UserController.java</code>之后，使用mc(Memory Compiler)命令来编译，并且通过<code>-c</code>参数指定<code>ClassLoader</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp</span><br><span class="line">Memory compiler output:</span><br><span class="line">/tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 346 ms</span><br></pre></td></tr></table></figure><h2 id="redefine热更新代码"><a href="#redefine热更新代码" class="headerlink" title="redefine热更新代码"></a>redefine热更新代码</h2><p>再使用redefine命令重新加载新编译好的<code>UserController.class</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redefine /tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">redefine success, size: 1</span><br></pre></td></tr></table></figure><h2 id="检验热更新结果"><a href="#检验热更新结果" class="headerlink" title="检验热更新结果"></a>检验热更新结果</h2><p>再次访问 <code>curl http://localhost/user/0</code>，会正常返回：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"id"</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="string">"name"</span>: <span class="string">"name0"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Arthas里 <code>jad</code>/<code>mc</code>/<code>redefine</code> 一条龙来线上热更新代码，非常强大，但也很危险，需要做好权限管理。</p><p>比如，线上应用启动帐号是 admin，当用户可以切换到admin，那么</p><ul><li>用户可以修改，获取到应用的任意内存值（不管是否java应用）</li><li>用户可以attach jvm</li><li>attach jvm之后，利用jvm本身的api可以redefine class</li></ul><p>所以：</p><ul><li>应用的安全主要靠用户权限本身的管理</li><li>Arthas主要是让jvm redefine更容易了。用户也可以利用其它工具达到同样的效果</li></ul><p>最后，Arthas提醒您： <strong>诊断千万条，规范第一条，热更不规范，同事两行泪</strong>。</p><h2 id="Arthas实践系列"><a href="#Arthas实践系列" class="headerlink" title="Arthas实践系列"></a>Arthas实践系列</h2><ul><li><a href="/arthas-spring-context/">Alibaba Arthas实践–获取到Spring Context，然后为所欲为</a></li><li><a href="/arthas-spring-boot-404-401/">Arthas实践–快速排查Spring Boot应用404/401问题</a></li><li><a href="/dubbo-meet-arthas/">当Dubbo遇上Arthas：排查问题的实践</a></li><li><a href="/arthas-redefine-case/">Arthas实践–使用redefine排查应用奇怪的日志来源</a></li><li><a href="/arthas-logger-problem/">使用Arthas抽丝剥茧排查线上应用日志打满问题</a></li><li><a href="/spring-boot-arthas-NoSuchMethodError/">深入Spring Boot：利用Arthas排查NoSuchMethodError</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;尽管在生产环境热更新代码，并不是很好的行为，很可能导致：热更不规范，同事两行泪。&lt;/p&gt;
&lt;p&gt;但很多时候我们的确希望能热更新代码，比如：&lt;
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="redefine" scheme="http://hengyunabc.github.io/tags/redefine/"/>
    
      <category term="compiler" scheme="http://hengyunabc.github.io/tags/compiler/"/>
    
  </entry>
  
  <entry>
    <title>Alibaba Arthas 3.1.0版本：在线教程、内存编译器和强大的自动补全</title>
    <link href="http://hengyunabc.github.io/arthas-3.1.0/"/>
    <id>http://hengyunabc.github.io/arthas-3.1.0/</id>
    <published>2019-02-13T16:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://alibaba.github.io/arthas/_images/arthas.png" alt="Arthas"></p><p><code>Arthas</code>是Alibaba开源的Java诊断工具，深受开发者喜爱。<br>从Arthas上个版本发布，已经过去两个多月了，Arthas 3.1.0版本不仅带来大家投票出来的新LOGO，还带来强大的新功能和更好的易用性，下面一一介绍。</p><ul><li>Github： <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas" target="_blank" rel="noopener">https://alibaba.github.io/arthas</a></li></ul><h3 id="在线教程"><a href="#在线教程" class="headerlink" title="在线教程"></a>在线教程</h3><p>在新版本Arthas里，增加了在线教程，用户可以在线运行Demo，一步步学习Arthas的各种用法，推荐新手尝试：</p><ul><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-basics" target="_blank" rel="noopener">Arthas基础教程</a></li><li><a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-advanced" target="_blank" rel="noopener">Arthas进阶教程</a></li></ul><p>非常欢迎大家来完善这些教程。</p><h3 id="增加内存编译器支持，在线编辑热更新代码"><a href="#增加内存编译器支持，在线编辑热更新代码" class="headerlink" title="增加内存编译器支持，在线编辑热更新代码"></a>增加内存编译器支持，在线编辑热更新代码</h3><p><code>3.1.0</code>版本里新增命令<code>mc</code>，不是方块游戏mc，而是Memory Compiler。</p><p>在之前版本里，增加了<code>redefine</code>命令，可以热更新字节码。但是有个不方便的地方：需要把<code>.class</code>文件上传到服务器上。</p><p>在<code>3.1.0</code>版本里，结合<code>jad</code>/<code>mc</code>/<code>redefine</code> 可以完美实现热更新代码。</p><p>以 <a href="https://alibaba.github.io/arthas/arthas-tutorials?language=cn&amp;id=arthas-advanced" target="_blank" rel="noopener">Arthas在线教程</a> 里的<code>UserController</code>为例：</p><ol><li><p>使用jad反编译代码</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jad --<span class="built_in">source</span>-only com.example.demo.arthas.user.UserController &gt; /tmp/UserController.java</span><br></pre></td></tr></table></figure></li><li><p>使用vim编译代码</p><p> 当 user id 小于1时，也正常返回，不抛出异常：</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/user/&#123;id&#125;"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUserById</span><span class="params">(@PathVariable Integer id)</span> </span>&#123;</span><br><span class="line">    logger.info(<span class="string">"id: &#123;&#125;"</span> , id);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (id != <span class="keyword">null</span> &amp;&amp; id &lt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(id, <span class="string">"name"</span> + id);</span><br><span class="line">        <span class="comment">// throw new IllegalArgumentException("id &lt; 1");</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(id, <span class="string">"name"</span> + id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>使用<code>mc</code>命令编译修改后的<code>UserController.java</code></p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ mc /tmp/UserController.java -d /tmp</span><br><span class="line">Memory compiler output:</span><br><span class="line">/tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 346 ms</span><br></pre></td></tr></table></figure></li><li><p>使用<code>redefine</code>命令，因为可以热更新代码</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redefine /tmp/com/example/demo/arthas/user/UserController.class</span><br><span class="line">redefine success, size: 1</span><br></pre></td></tr></table></figure></li></ol><h3 id="丝滑的自动补全"><a href="#丝滑的自动补全" class="headerlink" title="丝滑的自动补全"></a>丝滑的自动补全</h3><p>在新版本里，改进了很多命令的自动补全，比如 <code>watch/trace/tt/monitor/stack</code>等。</p><p>下面是watch命令的第一个<code>Tab</code>补全结果，用户可以很方便的一步步补全类名，函数名：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ watch</span><br><span class="line">com.   sun.   javax. ch.    io.    demo.  jdk.   org.   java.</span><br></pre></td></tr></table></figure><p>另外，新增加了 <code>jad/sc/sm/redefine</code> 等命令的自动补全支持，多按<code>Tab</code>有惊喜。</p><h3 id="新版本的Web-console"><a href="#新版本的Web-console" class="headerlink" title="新版本的Web console"></a>新版本的Web console</h3><p>新版本的Web Console切换到了<code>xtermd.js</code>，更好地支持现代浏览器。</p><ul><li>支持<code>Ctrl + C</code>复制</li><li>支持全屏</li></ul><p><img src="https://alibaba.github.io/arthas/_images/web-console-local.png" alt="web console"></p><h3 id="Docker镜像支持"><a href="#Docker镜像支持" class="headerlink" title="Docker镜像支持"></a>Docker镜像支持</h3><p>Arthas支持Docker镜像了</p><ul><li>用户可以很方便地诊断Docker/k8s里的Java进程</li><li>也可以很方便地把Arthas加到自己的基础镜像里</li></ul><p>参考： <a href="https://alibaba.github.io/arthas/docker.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/docker.html</a></p><h3 id="重定向重新设计"><a href="#重定向重新设计" class="headerlink" title="重定向重新设计"></a>重定向重新设计</h3><p>之前的版本里，Arthas的重定向是会放到一个<code>~/logs/arthas-cache/</code>目录里，违反直觉。</p><p>在新版本里，重定向和Linux下面的一致，<code>&gt;</code>/<code>&gt;&gt;</code>的行为也和Linux下一致。</p><p>并且，增加了 <code>cat</code>/<code>pwd</code>命令，可以配置使用。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>总之，<code>3.1.0</code>版本的Arthas带了非常多的新功能，改进了很多的用户体验，欢迎大家使用反馈。</p><ul><li>Arthas在线教程可以学到很多技巧</li><li>jad/mc/redefine 一条龙非常强大</li><li>丝滑的自动补全值得尝试</li><li>新版本的Web Console有惊奇</li></ul><p>Release Note: <a href="https://github.com/alibaba/arthas/releases/tag/3.1.0" target="_blank" rel="noopener">https://github.com/alibaba/arthas/releases/tag/3.1.0</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;&lt;img src=&quot;https://alibaba.github.io/arthas/_images/arthas.png&quot; alt=&quot;Arthas&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Arthas&lt;/code&gt;是Alibaba开源的Java诊断工具，深受开发者喜爱。&lt;br&gt;从A
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="bytecode" scheme="http://hengyunabc.github.io/tags/bytecode/"/>
    
  </entry>
  
  <entry>
    <title>Alibaba Arthas实践--获取到Spring Context，然后为所欲为</title>
    <link href="http://hengyunabc.github.io/arthas-spring-context/"/>
    <id>http://hengyunabc.github.io/arthas-spring-context/</id>
    <published>2019-01-28T01:27:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Arthas 是Alibaba开源的Java诊断工具，深受开发者喜爱。</p><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li></ul><p>Arthas提供了非常丰富的关于调用拦截的命令，比如 trace/watch/monitor/tt 。但是很多时候我们在排查问题时，需要更多的线索，并不只是函数的参数和返回值。<br>比如在一个spring应用里，想获取到spring context里的其它bean。如果能随意获取到spring bean，那就可以“为所欲为”了。</p><p>下面介绍如何利用Arthas获取到spring context。</p><p>Demo： <a href="https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-arthas-spring-boot" target="_blank" rel="noopener">https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-arthas-spring-boot</a></p><p>Arthas快速开始：<a href="https://alibaba.github.io/arthas/quick-start.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/quick-start.html</a></p><h2 id="使用tt命令获取到spring-context"><a href="#使用tt命令获取到spring-context" class="headerlink" title="使用tt命令获取到spring context"></a>使用tt命令获取到spring context</h2><p>Demo是一个spring mvc应用，请求会经过一系列的spring bean处理，那么我们可以在spring mvc的类里拦截到一些请求。</p><p>启动Demo： <code>mvn spring-boot:run</code></p><p>使用Arthas Attach成功之后，执行<code>tt</code>命令来记录<code>RequestMappingHandlerAdapter#invokeHandlerMethod</code>的请求</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod</span><br></pre></td></tr></table></figure><p>然后访问一个网页： <a href="http://localhost:8080/" target="_blank" rel="noopener">http://localhost:8080/</a></p><p>可以看到Arthas会拦截到这个调用，index是1000，并且打印出：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ watch com.example.demo.Test * <span class="string">'params[0]@sss'</span></span><br><span class="line">$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:1 , method-cnt:1) cost <span class="keyword">in</span> 101 ms.</span><br><span class="line"> INDEX  TIMESTAMP         COST(ms  IS-RE  IS-EX  OBJECT       CLASS                     METHOD</span><br><span class="line">                          )        T      P</span><br><span class="line">------------------------------------------------------------------------------------------------------------------</span><br><span class="line"> 1000   2019-01-27 16:31  3.66744  <span class="literal">true</span>   <span class="literal">false</span>  0x4465cf70   RequestMappingHandlerAda  invokeHandlerMethod</span><br><span class="line">        :54                                                   pter</span><br></pre></td></tr></table></figure><p><strong>那么怎样获取到spring context？</strong></p><p>可以用<code>tt</code>命令的<code>-i</code>参数来指定index，并且用<code>-w</code>参数来执行ognl表达式来获取spring context：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ tt -i 1000 -w <span class="string">'target.getApplicationContext()'</span></span><br><span class="line">@AnnotationConfigEmbeddedWebApplicationContext[</span><br><span class="line">    reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@35dc90ec],</span><br><span class="line">    scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@72078a14],</span><br><span class="line">    annotatedClasses=null,</span><br><span class="line">    basePackages=null,</span><br><span class="line">]</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 7 ms.</span><br></pre></td></tr></table></figure><h2 id="从spring-context里获取任意bean"><a href="#从spring-context里获取任意bean" class="headerlink" title="从spring context里获取任意bean"></a>从spring context里获取任意bean</h2><p>获取到spring context之后，就可以获取到任意的bean了，比如获取到<code>helloWorldService</code>，并调用<code>getHelloMessage()</code>函数：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ tt -i 1000 -w <span class="string">'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'</span></span><br><span class="line">@String[Hello World]</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 5 ms.</span><br></pre></td></tr></table></figure><h2 id="更多的思路"><a href="#更多的思路" class="headerlink" title="更多的思路"></a>更多的思路</h2><p>在很多代码里都有static函数或者static holder类，顺滕摸瓜，可以获取很多其它的对象。比如在Dubbo里通过<code>SpringExtensionFactory</code>获取spring context：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ ognl <span class="string">'#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, </span></span><br><span class="line"><span class="string">#context.getBean("userServiceImpl").findUser(1)'</span></span><br><span class="line">@User[</span><br><span class="line">    id=@Integer[1],</span><br><span class="line">    name=@String[Deanna Borer],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li>Arthas: <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li><a href="https://alibaba.github.io/arthas/tt.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/tt.html</a></li><li><a href="https://alibaba.github.io/arthas/ognl.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/ognl.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;Arthas 是Alibaba开源的Java诊断工具，深受开发者喜爱。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gith
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="spring-boot" scheme="http://hengyunabc.github.io/tags/spring-boot/"/>
    
      <category term="spring" scheme="http://hengyunabc.github.io/tags/spring/"/>
    
  </entry>
  
  <entry>
    <title>Arthas实践--快速排查Spring Boot应用404/401问题</title>
    <link href="http://hengyunabc.github.io/arthas-spring-boot-404-401/"/>
    <id>http://hengyunabc.github.io/arthas-spring-boot-404-401/</id>
    <published>2019-01-06T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在Java Web/Spring Boot开发时，很常见的问题是：</p><ul><li>网页访问404了，为什么访问不到？</li><li>登陆失败了，请求返回401，到底是哪个Filter拦截了我的请求？</li></ul><p>碰到这种问题时，通常很头痛，特别是在线上环境时。</p><p>本文介绍使用Alibaba开源的Java诊断利器Arthas，来快速定位这类Web请求404/401问题。</p><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li></ul><h2 id="Java-Web里一个请求被处理的流程"><a href="#Java-Web里一个请求被处理的流程" class="headerlink" title="Java Web里一个请求被处理的流程"></a>Java Web里一个请求被处理的流程</h2><p>在进入正题之前，先温习下知识。一个普通的Java Web请求处理流程大概是这样子的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Request  -&gt; Filter1 -&gt; Filter2 ... -&gt; Servlet</span><br><span class="line">                                        |</span><br><span class="line">Response &lt;- Filter1 &lt;- Filter2 ... &lt;- Servlet</span><br></pre></td></tr></table></figure><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p>本文的介绍基于一个很简单的Demo：<a href="https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-404-401" target="_blank" rel="noopener">https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-404-401</a></p><ul><li>访问 <a href="http://localhost:8080/" target="_blank" rel="noopener">http://localhost:8080/</a> ，返回200，正常打印Welconme信息</li><li>访问 <a href="http://localhost:8080/a.txt" target="_blank" rel="noopener">http://localhost:8080/a.txt</a> ，返回404</li><li>访问 <a href="http://localhost:8080/admin" target="_blank" rel="noopener">http://localhost:8080/admin</a> ，返回401</li></ul><h2 id="是哪个Servlet返回了404？"><a href="#是哪个Servlet返回了404？" class="headerlink" title="是哪个Servlet返回了404？"></a>是哪个Servlet返回了404？</h2><p>Demo启动后，访问：<a href="http://localhost:8080/a.txt" target="_blank" rel="noopener">http://localhost:8080/a.txt</a> ，返回404：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ curl http://localhost:8080/a.txt</span><br><span class="line">&#123;<span class="string">"timestamp"</span>:1546790485831,<span class="string">"status"</span>:404,<span class="string">"error"</span>:<span class="string">"Not Found"</span>,<span class="string">"message"</span>:<span class="string">"No message available"</span>,<span class="string">"path"</span>:<span class="string">"/a.txt"</span>&#125;</span><br></pre></td></tr></table></figure><p><strong>我们知道一个HTTP Request，大部分情况下都是由一个Servlet处理的，那么到底是哪个Servlet返回了404？</strong></p><p>我们使用Arthas的<code>trace</code>命令来定位：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ trace javax.servlet.Servlet *</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:7 , method-cnt:185) cost <span class="keyword">in</span> 1018 ms.</span><br></pre></td></tr></table></figure><p>然后访问 <a href="http://localhost:8080/a.txt" target="_blank" rel="noopener">http://localhost:8080/a.txt</a> ，Arthas会打印出整个请求树，完整的输出太长，这里只截取关键的一输出：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">+---[<span class="number">13.087083</span>ms] org.springframework.web.servlet.DispatcherServlet:resolveViewName()</span><br><span class="line">|   `---[<span class="number">13.020984</span>ms] org.springframework.web.servlet.DispatcherServlet:resolveViewName()</span><br><span class="line">|       +---[<span class="number">0.002777</span>ms] java.util.List:iterator()</span><br><span class="line">|       +---[<span class="number">0.001955</span>ms] java.util.Iterator:hasNext()</span><br><span class="line">|       +---[<span class="number">0.001739</span>ms] java.util.Iterator:next()</span><br><span class="line">|       `---[<span class="number">12.891979</span>ms] org.springframework.web.servlet.ViewResolver:resolveViewName()</span><br><span class="line">|           +---[<span class="number">0.089811</span>ms] javax.servlet.GenericServlet:&lt;init&gt;()</span><br><span class="line">|           +---[min=<span class="number">0.037696</span>ms,max=<span class="number">0.054478</span>ms,total=<span class="number">0.092174</span>ms,count=<span class="number">2</span>] org.springframework.web.servlet.view.freemarker.FreeMarkerView$GenericServletAdapter:&lt;init&gt;()</span><br></pre></td></tr></table></figure><p>可以看出请求经过Spring MVC的<code>DispatcherServlet</code>处理，最终由<code>ViewResolver</code>分派给<code>FreeMarkerView$GenericServletAdapter</code>处理。所以我们可以知道这个请求最终是被<code>FreeMarker</code>处理的。<br>后面再排查<code>FreeMarker</code>的配置就可以了。</p><p>这个神奇的<code>trace javax.servlet.Servlet *</code>到底是怎样工作的呢？</p><p>实际上Arthas会匹配到JVM里所有实现了<code>javax.servlet.Servlet</code>的类，然后<code>trace</code>它们的所有函数，所以HTTP请求会被打印出来。</p><blockquote><p>这里留一个小问题，为什么只访问了<code>http://localhost:8080/a.txt</code>，但Arthas的<code>trace</code>命令打印出了两个请求树？</p></blockquote><h2 id="是哪个Filter返回了401？"><a href="#是哪个Filter返回了401？" class="headerlink" title="是哪个Filter返回了401？"></a>是哪个Filter返回了401？</h2><p>在Demo里，访问 <a href="http://localhost:8080/admin" target="_blank" rel="noopener">http://localhost:8080/admin</a> ，会返回401，即没有权限。那么是哪个Filter拦截了请求？</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ curl http://localhost:8080/admin</span><br><span class="line">&#123;<span class="string">"timestamp"</span>:1546794743674,<span class="string">"status"</span>:401,<span class="string">"error"</span>:<span class="string">"Unauthorized"</span>,<span class="string">"message"</span>:<span class="string">"admin filter error."</span>,<span class="string">"path"</span>:<span class="string">"/admin"</span>&#125;</span><br></pre></td></tr></table></figure><p>我们还是使用Arthas的<code>trace</code>命令来定位，不过这次<code>trace</code>的是<code>javax.servlet.Filter</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ trace javax.servlet.Filter *</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:13 , method-cnt:75) cost <span class="keyword">in</span> 278 ms.</span><br></pre></td></tr></table></figure><p>再次访问admin，在Arthas里，把整个请求经过哪些Filter处理，都打印为树。这里截取关键部分：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">+---[<span class="number">0.704625</span>ms] org.springframework.web.filter.OncePerRequestFilter:doFilterInternal()</span><br><span class="line">|   `---[<span class="number">0.60387</span>ms] org.springframework.web.filter.RequestContextFilter:doFilterInternal()</span><br><span class="line">|       +---[<span class="number">0.022704</span>ms] org.springframework.web.context.request.ServletRequestAttributes:&lt;init&gt;()</span><br><span class="line">|       +---[<span class="number">0.217636</span>ms] org.springframework.web.filter.RequestContextFilter:initContextHolders()</span><br><span class="line">|       |   `---[<span class="number">0.180323</span>ms] org.springframework.web.filter.RequestContextFilter:initContextHolders()</span><br><span class="line">|       |       +---[<span class="number">0.034656</span>ms] javax.servlet.http.HttpServletRequest:getLocale()</span><br><span class="line">|       |       +---[<span class="number">0.0311</span>ms] org.springframework.context.i18n.LocaleContextHolder:setLocale()</span><br><span class="line">|       |       +---[<span class="number">0.008691</span>ms] org.springframework.web.context.request.RequestContextHolder:setRequestAttributes()</span><br><span class="line">|       |       `---[<span class="number">0.014918</span>ms] org.apache.commons.logging.Log:isDebugEnabled()</span><br><span class="line">|       +---[<span class="number">0.215481</span>ms] javax.servlet.FilterChain:doFilter()</span><br><span class="line">|       |   `---[<span class="number">0.072186</span>ms] com.example.demo404401.AdminFilterConfig$AdminFilter:doFilter()</span><br><span class="line">|       |       `---[<span class="number">0.021945</span>ms] javax.servlet.http.HttpServletResponse:sendError()</span><br></pre></td></tr></table></figure><p>可以看到HTTP Request最终是被<code>com.example.demo404401.AdminFilterConfig$AdminFilter</code>处理的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>通过trace Servlet/Filter，可以快速定位Java Web问题</li><li>trace是了解应用执行流程的利器，只要trace到关键的接口或者类上</li><li>仔细观察trace的结果，可以学习到Spring MVC是处理Web请求细节</li></ul><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li><a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li><a href="https://alibaba.github.io/arthas/trace.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/trace.html</a></li></ul><p>打个广告，Arthas正在征集使用者，您的使用是对我们最大的支持。<br>如果Arthas对您排查问题有帮助，请到这个Issue登记下，并在30分钟内成为Arthas Contributor：</p><p><a href="https://github.com/alibaba/arthas/issues/395" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/395</a> </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;在Java Web/Spring Boot开发时，很常见的问题是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网页访问404了，为什么访问不到？&lt;/li&gt;

      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="trace" scheme="http://hengyunabc.github.io/tags/trace/"/>
    
      <category term="spring-boot" scheme="http://hengyunabc.github.io/tags/spring-boot/"/>
    
  </entry>
  
  <entry>
    <title>2018读书总结</title>
    <link href="http://hengyunabc.github.io/reading-notes-2018/"/>
    <id>http://hengyunabc.github.io/reading-notes-2018/</id>
    <published>2019-01-05T23:51:14.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="赋能：打造应对不确定性的敏捷团队"><a href="#赋能：打造应对不确定性的敏捷团队" class="headerlink" title="赋能：打造应对不确定性的敏捷团队"></a>赋能：打造应对不确定性的敏捷团队</h2><p><a href="https://book.douban.com/subject/27204181/" target="_blank" rel="noopener">https://book.douban.com/subject/27204181/</a></p><p>赋能，这个词经常听到，但不知道是啥意思。看了这本书，算是理解到了。</p><p>这本书讲的是在美国特种部队，在伊拉克应对各种“不确定性”事件，不得不把信息公开透明化，加强互动，从而提高应对速度和能力，打造应对不确定性的敏捷团队的故事。</p><p>在公司里，很多时候都是各种小团体，软件开发也是各种小群讨论。同一个大部门里，不同的组件都要互相打架。</p><p>感觉大部分人并没有意识到事情要公开，信息要流动，事情才能做好。也许是KPI导向的结果，也许是缺少共同目标，每个人都管自己的一亩三分地。</p><p>我个人感受是，尽量获取不同部门，不同组件的信息，把解决问题的过程记录下来，并公开化。</p><h2 id="美国大城市的死与生"><a href="#美国大城市的死与生" class="headerlink" title="美国大城市的死与生"></a>美国大城市的死与生</h2><p><a href="https://book.douban.com/subject/1870268/" target="_blank" rel="noopener">https://book.douban.com/subject/1870268/</a></p><p>这本书很早就知道了，但没有动机去看。真正起了兴趣去看，是因为看到一位美国留学生的Blog，里面提到了各种街区。所以去看了这本书。</p><p>感觉中国的城市设计者们受这本书的影响很深，城市基本都是混合功能的，并没有像美国那样子整齐的街区划分。之前还有传言说要把小区的墙都拆掉。</p><p>这本书是1961年出版的，所以我很好奇这本书到底在美国产生了怎样的影响。后面在《规模》里找到了一些答案。</p><p>这本书只看了三分之二，因为翻译真的不好。</p><p>也许中国人不能真正理解美国的街区，正如美国人不能理解中国人的户口。</p><h2 id="规模：复杂世界的简单法则"><a href="#规模：复杂世界的简单法则" class="headerlink" title="规模：复杂世界的简单法则"></a>规模：复杂世界的简单法则</h2><p><a href="https://book.douban.com/subject/30244461/" target="_blank" rel="noopener">https://book.douban.com/subject/30244461/</a></p><p>推荐。这本书是同事推荐的，里面谈的都是事物上了规模之后的一些统计学规律。看了可能有点沮丧，人逃脱不了这些规律。</p><p>不过看了这本书，明白了一个事情：城市化是不可逆转的，超大规模的城市群会越来越多。</p><p>书里提到很多《美国大城市的死与生》给美国人的影响。读书有乐趣的一点就是，两本书的互相印证。</p><h2 id="新教伦理与资本主义精神"><a href="#新教伦理与资本主义精神" class="headerlink" title="新教伦理与资本主义精神"></a>新教伦理与资本主义精神</h2><p><a href="https://book.douban.com/subject/6436046/" target="_blank" rel="noopener">https://book.douban.com/subject/6436046/</a></p><p>推荐。这本书说的是新教怎么影响资本主义的。翻译的版本里有一些很不错的配图，导致我一开始以为是80/90年代的书，但看到中间时，感觉不太对，去查了下，发现是1905年出版的，十分惊讶。</p><p>无论这本书是否是给资本主义正名，但里面提到的一点比较有意思：新教的禁欲主义使得新教徒很勤奋，所以他们资本财富不断增加。但财富增长到一定程度之后，这种宗教的精神慢慢泯灭了。这点和现在的中国很像，70/80还比较能体会到艰苦奋斗，90/00对艰苦奋斗这种概念已经很淡薄了。日本也曾经如此。</p><p>还有一点，现代人可能不太能理解为什么当时的人对资本主义深恶痛绝。在《众病之王：癌症传》里提到有一种消失了的癌症：阴囊癌。18世纪的很多童工因为身体接触到这些煤烟而患上阴囊癌。一开始是立法禁止8岁以下儿童，后面才逐步完全禁止。</p><p>有关联的一本书：《非理性的人》，在看中，推荐。</p><h2 id="见识：商业的本质和人生的智慧"><a href="#见识：商业的本质和人生的智慧" class="headerlink" title="见识：商业的本质和人生的智慧"></a>见识：商业的本质和人生的智慧</h2><p><a href="https://book.douban.com/subject/27167992/" target="_blank" rel="noopener">https://book.douban.com/subject/27167992/</a></p><p>推荐。这本书里学到了一个观点：让用户充值不重要，重要的是要让用户花出去。</p><h2 id="死人经"><a href="#死人经" class="headerlink" title="死人经"></a>死人经</h2><p><a href="https://book.douban.com/subject/26365250/" target="_blank" rel="noopener">https://book.douban.com/subject/26365250/</a></p><p>一般，武侠小说，只看了不到一半，太啰嗦了。</p><h2 id="高山下的花环"><a href="#高山下的花环" class="headerlink" title="高山下的花环"></a>高山下的花环</h2><p><a href="https://book.douban.com/subject/2159124/" target="_blank" rel="noopener">https://book.douban.com/subject/2159124/</a></p><p>很有时代特征，可以看到很多细节。但是都没有写到位。军队的腐败也只能带一下。现在的人对越战有认知的很少，希望未来可以有对越战争的电影。</p><h2 id="搞懂金融的第一本书"><a href="#搞懂金融的第一本书" class="headerlink" title="搞懂金融的第一本书"></a>搞懂金融的第一本书</h2><p><a href="https://book.douban.com/subject/10434287/" target="_blank" rel="noopener">https://book.douban.com/subject/10434287/</a></p><p>对小白来说，比较有意思。学到了点通货膨胀，钱币贬值的知识，不过现在已忘记了，哈哈。</p><h2 id="你一生的故事"><a href="#你一生的故事" class="headerlink" title="你一生的故事"></a>你一生的故事</h2><p><a href="https://book.douban.com/subject/26295448/" target="_blank" rel="noopener">https://book.douban.com/subject/26295448/</a></p><p>因为《降临》电影不知所云，找的原著来看。书是由多个小故事组成的，只看了两个故事：《你一生的故事》和《巴比伦塔》。太多宗教色彩了，提不起兴趣再看。</p><h2 id="断舍离"><a href="#断舍离" class="headerlink" title="断舍离"></a>断舍离</h2><p><a href="https://book.douban.com/subject/24749465/" target="_blank" rel="noopener">https://book.douban.com/subject/24749465/</a></p><p>囤积物品是人进化的天性，丢掉旧东西，也许是现代人的一种解脱方式。今年搬了一次家，把一些两三年都没用过的东西丢了，感觉有点爽。</p><p>还有一个关联的日剧《我的家里空无一物》，看了一两集，没追。</p><h2 id="沉默的大多数"><a href="#沉默的大多数" class="headerlink" title="沉默的大多数"></a>沉默的大多数</h2><p><a href="https://book.douban.com/subject/1054685/" target="_blank" rel="noopener">https://book.douban.com/subject/1054685/</a> </p><p>王小波的杂文集。王小波的小说一直没有看进去，偶然看到这本杂文还挺有意思的。</p><h2 id="黑色皮革手册"><a href="#黑色皮革手册" class="headerlink" title="黑色皮革手册"></a>黑色皮革手册</h2><p><a href="https://book.douban.com/subject/26301856/" target="_blank" rel="noopener">https://book.douban.com/subject/26301856/</a></p><p>因为同名日剧去看的，日剧本身没看，直接看的小说，还行。</p><h2 id="对于历史，科学家有话说"><a href="#对于历史，科学家有话说" class="headerlink" title="对于历史，科学家有话说"></a>对于历史，科学家有话说</h2><p><a href="https://book.douban.com/subject/26954748/" target="_blank" rel="noopener">https://book.douban.com/subject/26954748/</a></p><p>了解一些“知识分子”的历史，书里都是当事人的访谈。“知识分子”这个词，对于年青人是比较陌生的，没有经历过的人，可能真的没办法理解。</p><h2 id="青年们，读马克思吧！"><a href="#青年们，读马克思吧！" class="headerlink" title="青年们，读马克思吧！"></a>青年们，读马克思吧！</h2><p><a href="https://book.douban.com/subject/25728591/" target="_blank" rel="noopener">https://book.douban.com/subject/25728591/</a></p><p>日本人写的，怎样读马克思的书。感觉有点神奇。通过两个作者的书信来往，想讨论马克思是怎样思考的，马克思的方法论。</p><h2 id="笑傲江湖-和-倚天屠龙记"><a href="#笑傲江湖-和-倚天屠龙记" class="headerlink" title="笑傲江湖 和 倚天屠龙记"></a>笑傲江湖 和 倚天屠龙记</h2><p>金庸去世，把《笑傲江湖》和《倚天屠龙记》重新看了一遍。 为什么这两本呢？因为小学的时候，从角落里翻到《笑傲江湖》的第二本，《倚天屠龙记》的第一本。当时看完了又找不到后续，只能翻来覆去的看了好几遍。直到上高中，从图书馆借书才看了完本。</p><p>《笑傲江湖》有个点很有意思，岳不群和左冷禅争五岳盟主时，岳不群说：“只须方针一变，天下同道协力以赴，期之以五十年、一百年，决无不成之理。”金庸在后记里专门说明小说是早年写的，并非影射。不管真相是怎样，看到还是觉得挻搞笑的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>2018年把今日头条卸载了，因为推的内容越来越单一，越来越八卦。发现这些软件都没有重置的功能，应该允许用户重新选择感兴趣的内容，重新开始推荐。大数据时代，每个人都只能看到自己圈子里的，越来越难看到另外领域的信息了。</p><p>五星推荐：</p><ul><li>赋能：打造应对不确定性的敏捷团队</li><li>规模：复杂世界的简单法则</li><li>新教伦理与资本主义精神</li><li>见识：商业的本质和人生的智慧</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;赋能：打造应对不确定性的敏捷团队&quot;&gt;&lt;a href=&quot;#赋能：打造应对不确定性的敏捷团队&quot; class=&quot;headerlink&quot; title=&quot;赋能：打造应对不确定性的敏捷团队&quot;&gt;&lt;/a&gt;赋能：打造应对不确定性的敏捷团队&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https
      
    
    </summary>
    
      <category term="读书" scheme="http://hengyunabc.github.io/categories/%E8%AF%BB%E4%B9%A6/"/>
    
    
      <category term="读书" scheme="http://hengyunabc.github.io/tags/%E8%AF%BB%E4%B9%A6/"/>
    
  </entry>
  
  <entry>
    <title>当Dubbo遇上Arthas：排查问题的实践</title>
    <link href="http://hengyunabc.github.io/dubbo-meet-arthas/"/>
    <id>http://hengyunabc.github.io/dubbo-meet-arthas/</id>
    <published>2018-12-05T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p>Apache Dubbo是Alibaba开源的高性能RPC框架，在国内有非常多的用户。</p><ul><li>Github: <a href="https://github.com/apache/incubator-dubbo" target="_blank" rel="noopener">https://github.com/apache/incubator-dubbo</a></li><li>文档：<a href="http://dubbo.incubator.apache.org/zh-cn/" target="_blank" rel="noopener">http://dubbo.incubator.apache.org/zh-cn/</a></li></ul><p>Arthas是Alibaba开源的应用诊断利器，9月份开源以来，Github Star数三个月超过6000。</p><ul><li>Github: <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas/" target="_blank" rel="noopener">https://alibaba.github.io/arthas/</a></li><li>Arthas开源交流QQ群: 916328269</li><li>Arthas开源交流钉钉群: 21965291</li></ul><p>当Dubbo遇上Arthas，会碰撞出什么样的火花呢？下面来分享Arthas排查Dubbo问题的一些经验。</p><h3 id="dubbo-arthas-demo"><a href="#dubbo-arthas-demo" class="headerlink" title="dubbo-arthas-demo"></a>dubbo-arthas-demo</h3><p>下面的排查分享基于这个<code>dubbo-arthas-demo</code>，非常简单的一个应用，浏览器请求从Spring MVC到Dubbo Client，再发送到Dubbo Server。</p><p>Demo里有两个spring boot应用，可以先启动<code>server-demo</code>，再启动<code>client-demo</code>。</p><ul><li><a href="https://github.com/hengyunabc/dubbo-arthas-demo" target="_blank" rel="noopener">https://github.com/hengyunabc/dubbo-arthas-demo</a></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/user/&#123;id&#125;    -&gt;   UserService    -&gt;   UserServiceImpl </span><br><span class="line"> Browser           Dubbo Client          Dubbo Server</span><br></pre></td></tr></table></figure><p>Client端：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Reference</span>(version = <span class="string">"1.0.0"</span>)</span><br><span class="line"><span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/user/&#123;id&#125;"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUserById</span><span class="params">(@PathVariable Integer id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> userService.findUser(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Server端：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span>(version = <span class="string">"1.0.0"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUser</span><span class="params">(<span class="keyword">int</span> id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (id &lt; <span class="number">1</span>) &#123;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"user id &lt; 1, id: "</span> + id);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line"><span class="keyword">if</span> (user.getId() == id) &#123;</span><br><span class="line"><span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Can not find user, id: "</span> + id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Arthas快速开始"><a href="#Arthas快速开始" class="headerlink" title="Arthas快速开始"></a>Arthas快速开始</h3><ul><li><a href="https://alibaba.github.io/arthas/install-detail.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/install-detail.html</a></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ wget https://alibaba.github.io/arthas/arthas-boot.jar</span><br><span class="line">$ java -jar arthas-boot.jar</span><br></pre></td></tr></table></figure><p>启动后，会列出所有的java进程，选择1，然后回车，就会连接上<code>ServerDemoApplication</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">$ java -jar arthas-boot.jar</span><br><span class="line">* [1]: 43523 ServerDemoApplication</span><br><span class="line">  [2]: 22342</span><br><span class="line">  [3]: 44108 ClientDemoApplication</span><br><span class="line">1</span><br><span class="line">[INFO] arthas home: /Users/hengyunabc/.arthas/lib/3.0.5/arthas</span><br><span class="line">[INFO] Try to attach process 43523</span><br><span class="line">[INFO] Attach process 43523 success.</span><br><span class="line">[INFO] arthas-client connect 127.0.0.1 3658</span><br><span class="line">  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.</span><br><span class="line"> /  O  \ |  .--. <span class="string">''</span>--.  .--<span class="string">'|  '</span>--<span class="string">'  | /  O  \ '</span>   .-<span class="string">'</span></span><br><span class="line"><span class="string">|  .-.  ||  '</span>--<span class="string">'.'</span>   |  |   |  .--.  ||  .-.  |`.  `-.</span><br><span class="line">|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-<span class="string">'    |</span></span><br><span class="line"><span class="string">`--'</span> `--<span class="string">'`--'</span> <span class="string">'--'</span>   `--<span class="string">'   `--'</span>  `--<span class="string">'`--'</span> `--<span class="string">'`-----'</span></span><br><span class="line"></span><br><span class="line">wiki: https://alibaba.github.io/arthas</span><br><span class="line">version: 3.0.5</span><br><span class="line">pid: 43523</span><br><span class="line">time: 2018-12-05 16:23:52</span><br><span class="line"></span><br><span class="line">$</span><br></pre></td></tr></table></figure><h3 id="Dubbo线上服务抛出异常，怎么获取调用参数？"><a href="#Dubbo线上服务抛出异常，怎么获取调用参数？" class="headerlink" title="Dubbo线上服务抛出异常，怎么获取调用参数？"></a>Dubbo线上服务抛出异常，怎么获取调用参数？</h3><ul><li><a href="https://alibaba.github.io/arthas/watch.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/watch.html</a></li></ul><p>当线上服务抛出异常时，最着急的是什么参数导致了抛异常？</p><p>在demo里，访问 <a href="http://localhost:8080/user/0" target="_blank" rel="noopener">http://localhost:8080/user/0</a> ，<code>UserServiceImpl</code>就会抛出一个异常，因为user id不合法。</p><p>在Arthas里执行 <code>watch com.example.UserService * -e -x 2 &#39;{params,throwExp}&#39;</code> ，然后再次访问，就可以看到watch命令把参数和异常都打印出来了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">$ watch com.example.UserService * -e -x 2 &apos;&#123;params,throwExp&#125;&apos;</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:1 , method-cnt:4) cost in 230 ms.</span><br><span class="line">ts=2018-12-05 16:26:44; [cost=3.905523ms] result=@ArrayList[</span><br><span class="line">    @Object[][</span><br><span class="line">        @Integer[0],</span><br><span class="line">    ],</span><br><span class="line">    java.lang.IllegalArgumentException: user id &lt; 1, id: 0</span><br><span class="line">at com.example.UserServiceImpl.findUser(UserServiceImpl.java:24)</span><br><span class="line">at com.alibaba.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)</span><br><span class="line">at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:45)</span><br><span class="line">at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:71)</span><br><span class="line">at com.alibaba.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:48)</span><br><span class="line">at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:52)</span><br><span class="line">at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:61)</span><br></pre></td></tr></table></figure><h3 id="怎样线上调试Dubbo服务代码"><a href="#怎样线上调试Dubbo服务代码" class="headerlink" title="怎样线上调试Dubbo服务代码?"></a>怎样线上调试Dubbo服务代码?</h3><ul><li><a href="https://alibaba.github.io/arthas/redefine.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/redefine.html</a></li></ul><p>在本地开发时，可能会用到热部署工具，直接改代码，不需要重启应用。但是在线上环境，有没有办法直接动态调试代码？比如增加日志。</p><p>在Arthas里，可以通过<code>redefine</code>命令来达到线上不重启，动态更新代码的效果。</p><p>比如我们修改下<code>UserServiceImpl</code>，用<code>System.out</code>打印出具体的<code>User</code>对象来：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> User <span class="title">findUser</span><span class="params">(<span class="keyword">int</span> id)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (id &lt; <span class="number">1</span>) &#123;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"user id &lt; 1, id: "</span> + id);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (User user : users) &#123;</span><br><span class="line"><span class="keyword">if</span> (user.getId() == id) &#123;</span><br><span class="line">System.out.println(user);</span><br><span class="line"><span class="keyword">return</span> user;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Can not find user, id: "</span> + id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>本地编绎后，把<code>server-demo/target/classes/com/example/UserServiceImpl.class</code>传到线上服务器，然后用<code>redefine</code>命令来更新代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redefine -p /tmp/UserServiceImpl.class</span><br><span class="line">redefine success, size: 1</span><br></pre></td></tr></table></figure><p>这样子更新成功之后，访问 <a href="http://localhost:8080/user/1" target="_blank" rel="noopener">http://localhost:8080/user/1</a> ，在<code>ServerDemoApplication</code>的控制台里就可以看到打印出了user信息。</p><h3 id="怎样动态修改Dubbo的logger级别"><a href="#怎样动态修改Dubbo的logger级别" class="headerlink" title="怎样动态修改Dubbo的logger级别?"></a>怎样动态修改Dubbo的logger级别?</h3><ul><li><a href="https://alibaba.github.io/arthas/ognl.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/ognl.html</a></li><li><a href="https://alibaba.github.io/arthas/sc.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/sc.html</a></li><li><a href="https://commons.apache.org/proper/commons-ognl/language-guide.html" target="_blank" rel="noopener">https://commons.apache.org/proper/commons-ognl/language-guide.html</a></li></ul><p>在排查问题时，需要查看到更多的信息，如果可以把logger级别修改为<code>DEBUG</code>，就非常有帮助。</p><p><code>ognl</code>是apache开源的一个轻量级表达式引擎。下面通过Arthas里的<code>ognl</code>命令来动态修改logger级别。</p><p>首先获取Dubbo里<code>TraceFilter</code>的一个logger对象，看下它的实现类，可以发现是log4j。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ognl <span class="string">'@com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter@logger.logger'</span></span><br><span class="line">@Log4jLogger[</span><br><span class="line">    FQCN=@String[com.alibaba.dubbo.common.logger.support.FailsafeLogger],</span><br><span class="line">    logger=@Logger[org.apache.log4j.Logger@2f19bdcf],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>再用<code>sc</code>命令来查看具体从哪个jar包里加载的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ sc -d org.apache.log4j.Logger</span><br><span class="line"> class-info        org.apache.log4j.Logger</span><br><span class="line"> code-source       /Users/hengyunabc/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar</span><br><span class="line"> name              org.apache.log4j.Logger</span><br><span class="line"> isInterface       <span class="literal">false</span></span><br><span class="line"> isAnnotation      <span class="literal">false</span></span><br><span class="line"> isEnum            <span class="literal">false</span></span><br><span class="line"> isAnonymousClass  <span class="literal">false</span></span><br><span class="line"> isArray           <span class="literal">false</span></span><br><span class="line"> isLocalClass      <span class="literal">false</span></span><br><span class="line"> isMemberClass     <span class="literal">false</span></span><br><span class="line"> isPrimitive       <span class="literal">false</span></span><br><span class="line"> isSynthetic       <span class="literal">false</span></span><br><span class="line"> simple-name       Logger</span><br><span class="line"> modifier          public</span><br><span class="line"> annotation</span><br><span class="line"> interfaces</span><br><span class="line"> super-class       +-org.apache.log4j.Category</span><br><span class="line">                     +-java.lang.Object</span><br><span class="line"> class-loader      +-sun.misc.Launcher<span class="variable">$AppClassLoader</span>@5c647e05</span><br><span class="line">                     +-sun.misc.Launcher<span class="variable">$ExtClassLoader</span>@59878d35</span><br><span class="line"> classLoaderHash   5c647e05</span><br><span class="line"></span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 126 ms.</span><br></pre></td></tr></table></figure><p><strong>可以看到log4j是通过slf4j代理的。</strong></p><p>那么通过<code>org.slf4j.LoggerFactory</code>获取<code>root</code> logger，再修改它的level：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ognl &apos;@org.slf4j.LoggerFactory@getLogger(&quot;root&quot;).setLevel(@ch.qos.logback.classic.Level@DEBUG)&apos;</span><br><span class="line">null</span><br><span class="line">$ ognl &apos;@org.slf4j.LoggerFactory@getLogger(&quot;root&quot;).getLevel().toString()&apos;</span><br><span class="line">@String[DEBUG]</span><br></pre></td></tr></table></figure><p>可以看到修改之后，<code>root</code> logger的level变为<code>DEBUG</code>。</p><h3 id="怎样减少测试小姐姐重复发请求的麻烦"><a href="#怎样减少测试小姐姐重复发请求的麻烦" class="headerlink" title="怎样减少测试小姐姐重复发请求的麻烦?"></a>怎样减少测试小姐姐重复发请求的麻烦?</h3><ul><li><a href="https://alibaba.github.io/arthas/tt.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/tt.html</a></li></ul><p>在平时开发时，可能需要测试小姐姐发请求过来联调，但是我们在debug时，可能不小心直接跳过去了。这样子就尴尬了，需要测试小姐姐再发请求过来。</p><p>Arthas里提供了<code>tt</code>命令，可以减少这种麻烦，可以直接重放请求。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ tt -t com.example.UserServiceImpl findUser</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:1 , method-cnt:1) cost <span class="keyword">in</span> 145 ms.</span><br><span class="line"> INDEX      TIMESTAMP              COST(ms)      IS-RET     IS-EXP    OBJECT       CLASS              METHOD</span><br><span class="line">----------------------------------------------------------------------------------------------------------------</span><br><span class="line"> 1000       2018-12-05 17:47:52    1.56523       <span class="literal">true</span>       <span class="literal">false</span>     0x3233483    UserServiceImpl    findUser</span><br><span class="line"> 1001       2018-12-05 17:48:03    0.286176      <span class="literal">false</span>      <span class="literal">true</span>      0x3233483    UserServiceImpl    findUser</span><br><span class="line"> 1002       2018-12-05 17:48:11    90.324335     <span class="literal">true</span>       <span class="literal">false</span>     0x3233483    UserServiceImpl    findUser</span><br></pre></td></tr></table></figure><p>上面的<code>tt -t</code>命令捕获到了3个请求。然后通过<code>tt --play</code>可以重放请求：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">$ tt --play -i 1000</span><br><span class="line"> RE-INDEX       1000</span><br><span class="line"> GMT-REPLAY     2018-12-05 17:55:50</span><br><span class="line"> OBJECT         0x3233483</span><br><span class="line"> CLASS          com.example.UserServiceImpl</span><br><span class="line"> METHOD         findUser</span><br><span class="line"> PARAMETERS[0]  @Integer[1]</span><br><span class="line"> IS-RETURN      <span class="literal">true</span></span><br><span class="line"> IS-EXCEPTION   <span class="literal">false</span></span><br><span class="line"> RETURN-OBJ     @User[</span><br><span class="line">                    id=@Integer[1],</span><br><span class="line">                    name=@String[Deanna Borer],</span><br><span class="line">                ]</span><br><span class="line">Time fragment[1000] successfully replayed.</span><br><span class="line">Affect(row-cnt:1) cost <span class="keyword">in</span> 4 ms.</span><br></pre></td></tr></table></figure><h3 id="Dubbo运行时有哪些Filter-耗时是多少"><a href="#Dubbo运行时有哪些Filter-耗时是多少" class="headerlink" title="Dubbo运行时有哪些Filter? 耗时是多少?"></a>Dubbo运行时有哪些Filter? 耗时是多少?</h3><ul><li><a href="https://alibaba.github.io/arthas/trace.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/trace.html</a></li></ul><p>Dubbo运行时会加载很多的Filter，那么一个请求会经过哪些Filter处理，Filter里的耗时又是多少呢？</p><p>通过Arthas的<code>trace</code>命令，可以很方便地知道Filter的信息，可以看到详细的调用栈和耗时。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">$ trace com.alibaba.dubbo.rpc.Filter *</span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:19 , method-cnt:59) cost <span class="keyword">in</span> 1441 ms.</span><br><span class="line">`---ts=2018-12-05 19:07:26;thread_name=DubboServerHandler-30.5.125.152:20880-thread-10;id=3e;is_daemon=<span class="literal">true</span>;priority=5;TCCL=sun.misc.Launcher<span class="variable">$AppClassLoader</span>@5c647e05</span><br><span class="line">    `---[8.435844ms] com.alibaba.dubbo.rpc.filter.EchoFilter:invoke()</span><br><span class="line">        +---[0.124572ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()</span><br><span class="line">        +---[0.065123ms] java.lang.String:equals()</span><br><span class="line">        `---[7.762928ms] com.alibaba.dubbo.rpc.Invoker:invoke()</span><br><span class="line">            `---[7.494124ms] com.alibaba.dubbo.rpc.filter.ClassLoaderFilter:invoke()</span><br><span class="line">                +---[min=0.00355ms,max=0.049922ms,total=0.057637ms,count=3] java.lang.Thread:currentThread()</span><br><span class="line">                +---[0.0126ms] java.lang.Thread:getContextClassLoader()</span><br><span class="line">                +---[0.02188ms] com.alibaba.dubbo.rpc.Invoker:getInterface()</span><br><span class="line">                +---[0.004115ms] java.lang.Class:getClassLoader()</span><br><span class="line">                +---[min=0.003906ms,max=0.014058ms,total=0.017964ms,count=2] java.lang.Thread:setContextClassLoader()</span><br><span class="line">                `---[7.033486ms] com.alibaba.dubbo.rpc.Invoker:invoke()</span><br><span class="line">                    `---[6.869488ms] com.alibaba.dubbo.rpc.filter.GenericFilter:invoke()</span><br><span class="line">                        +---[0.01481ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()</span><br></pre></td></tr></table></figure><h3 id="Dubbo动态代理是怎样实现的"><a href="#Dubbo动态代理是怎样实现的" class="headerlink" title="Dubbo动态代理是怎样实现的?"></a>Dubbo动态代理是怎样实现的?</h3><ul><li><a href="https://alibaba.github.io/arthas/jad.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/jad.html</a></li><li>com.alibaba.dubbo.common.bytecode.Wrapper</li></ul><p>通过Arthas的<code>jad</code>命令，可以看到Dubbo通过javaassist动态生成的Wrappr类的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">$ jad com.alibaba.dubbo.common.bytecode.Wrapper1</span><br><span class="line"></span><br><span class="line">ClassLoader:</span><br><span class="line">+-sun.misc.Launcher$AppClassLoader@<span class="number">5</span>c647e05</span><br><span class="line">  +-sun.misc.Launcher$ExtClassLoader@<span class="number">59878</span>d35</span><br><span class="line"></span><br><span class="line">Location:</span><br><span class="line">/Users/hengyunabc/.m2/repository/com/alibaba/dubbo/<span class="number">2.5</span><span class="number">.10</span>/dubbo-<span class="number">2.5</span><span class="number">.10</span>.jar</span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> com.alibaba.dubbo.common.bytecode;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Wrapper1</span></span></span><br><span class="line"><span class="class"><span class="keyword">extends</span> <span class="title">Wrapper</span></span></span><br><span class="line"><span class="class"><span class="keyword">implements</span> <span class="title">ClassGenerator</span>.<span class="title">DC</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">invokeMethod</span><span class="params">(Object object, String string, Class[] arrclass, Object[] arrobject)</span> <span class="keyword">throws</span> InvocationTargetException </span>&#123;</span><br><span class="line">        UserServiceImpl userServiceImpl;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            userServiceImpl = (UserServiceImpl)object;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> (Throwable throwable) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(throwable);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">"findUser"</span>.equals(string) &amp;&amp; arrclass.length == <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> userServiceImpl.findUser(((Number)arrobject[<span class="number">0</span>]).intValue());</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">"listUsers"</span>.equals(string) &amp;&amp; arrclass.length == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> userServiceImpl.listUsers();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">"findUserByName"</span>.equals(string) &amp;&amp; arrclass.length == <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> userServiceImpl.findUserByName((String)arrobject[<span class="number">0</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><h3 id="获取Spring-context"><a href="#获取Spring-context" class="headerlink" title="获取Spring context"></a>获取Spring context</h3><p>除了上面介绍的一些排查技巧，下面分享一个获取Spring Context，然后“为所欲为”的例子。</p><p>在Dubbo里有一个扩展<code>com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory</code>，把Spring Context保存到了里面。<br>因此，我们可以通过<code>ognl</code>命令获取到。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ognl <span class="string">'#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("userServiceImpl").findUser(1)'</span></span><br><span class="line">@User[</span><br><span class="line">    id=@Integer[1],</span><br><span class="line">    name=@String[Deanna Borer],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ul><li><a href="mailto:`SpringExtensionFactory@contexts.iterator.next" target="_blank" rel="noopener">`SpringExtensionFactory@contexts.iterator.next</a><code>获取到</code>SpringExtensionFactory`里保存的spring context对象</li><li><code>#context.getBean(&quot;userServiceImpl&quot;).findUser(1)</code> 获取到<code>userServiceImpl</code>再执行一次调用</li></ul><p>只要充分发挥想像力，组合Arthas里的各种命令，可以发挥出神奇的效果。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本篇文章来自杭州Dubbo Meetup的分享《当DUBBO遇上Arthas - 排查问题的实践》，希望对大家线上排查Dubbo问题有帮助。</p><p>分享的PDF可以在 <a href="https://github.com/alibaba/arthas/issues/327" target="_blank" rel="noopener">https://github.com/alibaba/arthas/issues/327</a> 里下载。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Apache Dubbo是Alibaba开源的高性能RPC框架，在国内有非常多的用户。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Github: &lt;a href=&quot;https://github.com/apache/incubator-dubbo&quot; target=&quot;_blank&quot; rel=
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="dubbo" scheme="http://hengyunabc.github.io/tags/dubbo/"/>
    
  </entry>
  
  <entry>
    <title>Alibaba应用诊断利器Arthas 3.0.5版本发布：提升全平台用户体验</title>
    <link href="http://hengyunabc.github.io/arthas-3.0.5/"/>
    <id>http://hengyunabc.github.io/arthas-3.0.5/</id>
    <published>2018-11-29T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<p>Arthas从9月份开源以来，受到广大Java开发者的支持，Github Star数三个月超过6000，非常感谢用户支持。同时用户给Arthas提出了很多建议，其中反映最多的是：</p><ol><li>Windows平台用户体验不好</li><li>Attach的进程和最终连接的进程不一致</li><li>某些环境下没有安装Telnet，不能连接到Arthas Server</li><li>本地启动，不需要下载远程（很多公司安全考虑）</li><li>下载速度慢（默认从maven central repository下载）</li></ol><p>在Arthas 3.0.5版本里，我们在用户体验方面做了很多改进，下面逐一介绍。</p><ul><li>Github: <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>文档：<a href="https://alibaba.github.io/arthas/" target="_blank" rel="noopener">https://alibaba.github.io/arthas/</a></li></ul><h3 id="全平台通用的arthas-boot"><a href="#全平台通用的arthas-boot" class="headerlink" title="全平台通用的arthas-boot"></a>全平台通用的arthas-boot</h3><p><code>arthas-boot</code>是新增加的支持全平台的启动器，Windows/Mac/Linux下使用体验一致。下载后，直接<code>java -jar</code>命令启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://alibaba.github.io/arthas/arthas-boot.jar</span><br><span class="line">java -jar arthas-boot.jar</span><br></pre></td></tr></table></figure><p><code>arthas-boot</code>的功能比以前的<code>as.sh</code>更强大。</p><ul><li><p>比如下载速度比较慢，可以指定阿里云的镜像。</p>  <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar arthas-boot.jar --repo-mirror aliyun --use-http</span><br></pre></td></tr></table></figure></li><li><p>比如可以通过<code>session-timeout</code>指定超时时间：</p>  <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar arthas-boot.jar --session-timeout 3600</span><br></pre></td></tr></table></figure></li><li><p>更多的功能可以通过<code>java -jar arthas-boot.jar -h</code>查看</p></li></ul><p><code>arthas-boot</code>在attach成功之后，会启动一个java telent client去连接Arthas Server，用户没有安装telnet的情况下也可以正常使用。</p><h3 id="优先使用当前目录的Arthas"><a href="#优先使用当前目录的Arthas" class="headerlink" title="优先使用当前目录的Arthas"></a>优先使用当前目录的Arthas</h3><p>在新版本里，默认会从<code>arthas-boot.jar</code>和<code>as.sh</code>所在的目录下查找arthas home，这样子用户全量安装之后，不需要再从远程下载Arthas。</p><ul><li>用户可以更方便地集成到自己的基础镜像，或者docker镜像里</li><li>对安全要求严格的公司，不用再担心从远程下载的问题</li></ul><h3 id="Attach之前先检测端口"><a href="#Attach之前先检测端口" class="headerlink" title="Attach之前先检测端口"></a>Attach之前先检测端口</h3><p>在之前的版本里，用户困扰最多的是，明明选择了进程A，但是实际连接到的却是进程B。</p><p>原因是之前attach了进程B，没有执行<code>shutdown</code>，下次再执行时，还是连接到进程B。</p><p>在新版本里，做了改进：</p><ul><li>在attach之前，检测使用3658端口的进程</li><li>在Attach时，如果端口和进程不匹配，会打印出ERROR信息</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ java -jar arthas-boot.jar</span><br><span class="line">[INFO] Process 1680 already using port 3658</span><br><span class="line">[INFO] Process 1680 already using port 8563</span><br><span class="line">* [1]: 1680 Demo</span><br><span class="line">  [2]: 35542</span><br><span class="line">  [3]: 82334 Demo</span><br><span class="line">3</span><br><span class="line">[ERROR] Target process 82334 is not the process using port 3658, you will connect to an unexpected process.</span><br><span class="line">[ERROR] If you still want to attach target process 82334, Try to <span class="built_in">set</span> a different telnet port by using --telnet-port argument.</span><br><span class="line">[ERROR] Or try to shutdown the process 1680 using the telnet port first.</span><br></pre></td></tr></table></figure><h3 id="更好的历史命令匹配功能"><a href="#更好的历史命令匹配功能" class="headerlink" title="更好的历史命令匹配功能"></a>更好的历史命令匹配功能</h3><ul><li><p>新版本对键盘<code>Up/Down</code>有了更好的匹配机制，试用有惊喜:)</p><p>  比如执行了多次trace，但是在命令行输入 trace后，想不起来之前trace的具体类名，那么按<code>Up</code>，可以很轻松地匹配到之前的历史命令，不需要辛苦翻页。</p></li><li><p>新版本增加了<code>history</code>命令</p></li></ul><h3 id="改进Web-Console的体验"><a href="#改进Web-Console的体验" class="headerlink" title="改进Web Console的体验"></a>改进Web Console的体验</h3><ul><li><p>改进对Windows的字体支持</p><p>  之前Windows下面使用的是非等宽字体，看起来很难受。新版本里统一为等宽字体。</p></li><li>增大字体，不再伤害眼睛</li></ul><p><img src="https://user-images.githubusercontent.com/1683936/49217204-f2c8e280-f407-11e8-9314-65f04a9e55da.png" alt="image"></p><h3 id="新增sysenv命令"><a href="#新增sysenv命令" class="headerlink" title="新增sysenv命令"></a>新增sysenv命令</h3><p>sysenv命令和sysprop类似，可以打印JVM的环境变量。</p><ul><li><a href="https://alibaba.github.io/arthas/sysenv.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/sysenv.html</a></li></ul><h3 id="新增ognl命令"><a href="#新增ognl命令" class="headerlink" title="新增ognl命令"></a>新增ognl命令</h3><p>ognl命令提供了单独执行ognl脚本的功能。可以很方便调用各种代码。</p><p>比如执行多行表达式，赋值给临时变量，返回一个List：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ognl <span class="string">'#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), &#123;#value1, #value2&#125;'</span></span><br><span class="line"><span class="meta">@ArrayList</span>[</span><br><span class="line">    <span class="meta">@String</span>[/opt/java/<span class="number">8.0</span><span class="number">.181</span>-zulu/jre],</span><br><span class="line">    <span class="meta">@String</span>[OpenJDK Runtime Environment],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><ul><li><a href="https://alibaba.github.io/arthas/ognl.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/ognl.html</a></li></ul><h3 id="watch命令打印耗时，更方便定位性能瓶颈"><a href="#watch命令打印耗时，更方便定位性能瓶颈" class="headerlink" title="watch命令打印耗时，更方便定位性能瓶颈"></a>watch命令打印耗时，更方便定位性能瓶颈</h3><p>之前watch命令只支持打印入参返回值等，新版本同时打印出调用耗时，可以很方便定位性能瓶颈。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ watch demo.MathGame primeFactors <span class="string">'params[0]'</span></span><br><span class="line">Press Ctrl+C to abort.</span><br><span class="line">Affect(class-cnt:1 , method-cnt:1) cost <span class="keyword">in</span> 22 ms.</span><br><span class="line">ts=2018-11-29 17:53:54; [cost=0.131383ms] result=@Integer[-387929024]</span><br><span class="line">ts=2018-11-29 17:53:55; [cost=0.132368ms] result=@Integer[-1318275764]</span><br><span class="line">ts=2018-11-29 17:53:56; [cost=0.496598ms] result=@Integer[76446257]</span><br><span class="line">ts=2018-11-29 17:53:57; [cost=4.9617ms] result=@Integer[1853966253]</span><br></pre></td></tr></table></figure><ul><li><a href="https://alibaba.github.io/arthas/watch.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/watch.html</a></li></ul><h3 id="改进类搜索匹配功能，更好支持lambda和内部类"><a href="#改进类搜索匹配功能，更好支持lambda和内部类" class="headerlink" title="改进类搜索匹配功能，更好支持lambda和内部类"></a>改进类搜索匹配功能，更好支持lambda和内部类</h3><p>之前的版本里，在搜索lambda类时，或者反编绎lambda类有可能会失败。新版本做了修复。比如</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">$ jad Test$$Lambda$<span class="number">1</span>/<span class="number">1406718218</span></span><br><span class="line"></span><br><span class="line">ClassLoader:</span><br><span class="line">+-sun.misc.Launcher$AppClassLoader@<span class="number">5</span>c647e05</span><br><span class="line">  +-sun.misc.Launcher$ExtClassLoader@<span class="number">3</span>c1491ce</span><br><span class="line"></span><br><span class="line">Location:</span><br><span class="line">/tmp/classes</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Decompiled with CFR 0_132.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Could not load the following classes:</span></span><br><span class="line"><span class="comment"> *  Test</span></span><br><span class="line"><span class="comment"> *  Test$$Lambda$1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">import</span> java.lang.invoke.LambdaForm;</span><br><span class="line"><span class="keyword">import</span> java.util.function.Consumer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span>$$<span class="title">Lambda</span>$1</span></span><br><span class="line"><span class="class"><span class="keyword">implements</span> <span class="title">Consumer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Test$$Lambda$<span class="number">1</span>() &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@LambdaForm</span>.Hidden</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">accept</span><span class="params">(Object object)</span> </span>&#123;</span><br><span class="line">        Test.lambda$<span class="number">0</span>((Integer)((Integer)object));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="更好的tab自动补全"><a href="#更好的tab自动补全" class="headerlink" title="更好的tab自动补全"></a>更好的tab自动补全</h3><p>改进了很多命令的<code>tab</code>自动补全功能，有停顿时，可以多按<code>tab</code>尝试下。</p><h3 id="Release-Note"><a href="#Release-Note" class="headerlink" title="Release Note"></a>Release Note</h3><p>详细的Release Note：<a href="https://github.com/alibaba/arthas/releases/tag/arthas-all-3.0.5" target="_blank" rel="noopener">https://github.com/alibaba/arthas/releases/tag/arthas-all-3.0.5</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Arthas从9月份开源以来，受到广大Java开发者的支持，Github Star数三个月超过6000，非常感谢用户支持。同时用户给Arthas提出了很多建议，其中反映最多的是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Windows平台用户体验不好&lt;/li&gt;
&lt;li&gt;Attach的进程
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
  </entry>
  
  <entry>
    <title>Arthas实践--使用redefine排查应用奇怪的日志来源</title>
    <link href="http://hengyunabc.github.io/arthas-redefine-case/"/>
    <id>http://hengyunabc.github.io/arthas-redefine-case/</id>
    <published>2018-10-31T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>随着应用越来越复杂，依赖越来越多，日志系统越来越混乱，有时会出现一些奇怪的日志，比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[] [] [] No credential found</span><br></pre></td></tr></table></figure><p>那么怎样排查这些奇怪的日志从哪里打印出来的呢？因为搞不清楚是什么logger打印出来的，所以想定位就比较头疼。</p><p>下面介绍用Alibaba开源的应用诊断利器Arthas的redefine命令快速定位奇怪日志来源。</p><ul><li>Arthas: <a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li><li>redefine命令：<a href="https://alibaba.github.io/arthas/redefine.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/redefine.html</a></li></ul><h2 id="修改StringBuilder"><a href="#修改StringBuilder" class="headerlink" title="修改StringBuilder"></a>修改StringBuilder</h2><p>首先在java代码里，字符串拼接基本都是通过<code>StringBuilder</code>来实现的。比如下面的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">hello</span><span class="params">(String world)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">"hello "</span> + world;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际上生成的字节码也是用<code>StringBuilder</code>来拼接的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> java.lang.<span class="function">String <span class="title">hello</span><span class="params">(java.lang.String)</span></span>;</span><br><span class="line">  descriptor: (Ljava/lang/String;)Ljava/lang/String;</span><br><span class="line">  flags: ACC_PUBLIC, ACC_STATIC</span><br><span class="line">  Code:</span><br><span class="line">    stack=<span class="number">3</span>, locals=<span class="number">1</span>, args_size=<span class="number">1</span></span><br><span class="line">       0: new           #22                 // class java/lang/StringBuilder</span><br><span class="line">       <span class="number">3</span>: dup</span><br><span class="line">       4: ldc           #24                 // String hello</span><br><span class="line">       6: invokespecial #26                 // Method java/lang/StringBuilder."&lt;init&gt;":(Ljava/lang/String;)V</span><br><span class="line">       <span class="number">9</span>: aload_0</span><br><span class="line">      10: invokevirtual #29                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;</span><br><span class="line">      13: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;</span><br><span class="line">      <span class="number">16</span>: areturn</span><br></pre></td></tr></table></figure><p>在java的logger系统里，输出日志时通常也是<code>StringBuilder</code>来实现的，最终会调用<code>StringBuilder.toString()</code>，那么我们可以修改<code>StringBuilder</code>的代码来检测到日志来源。</p><p><code>StringBuilder.toString()</code> 的原生实现是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Create a copy, don't share the array</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> String(value, <span class="number">0</span>, count);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Create a copy, don't share the array</span></span><br><span class="line">String result = <span class="keyword">new</span> String(value, <span class="number">0</span>, count);</span><br><span class="line"><span class="keyword">if</span>(result.contains(<span class="string">"No credential found"</span>)) &#123;</span><br><span class="line">System.err.println(result);</span><br><span class="line"><span class="keyword">new</span> Throwable().printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>增加的逻辑是：<strong>当String里包含<code>No credential found</code>时打印出当前栈，这样子就可以定位日志输出来源了。</strong></p><h2 id="编绎修改过的StringBuilder"><a href="#编绎修改过的StringBuilder" class="headerlink" title="编绎修改过的StringBuilder"></a>编绎修改过的StringBuilder</h2><p>其实很简单，在IDE里把<code>StringBuilder</code>的代码复制一份，然后贴到任意一个工程里，然后编绎即可。</p><p>也可以直接用javac来编绎：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac StringBuilder.java</span><br></pre></td></tr></table></figure><h2 id="启动应用，使用Arthas-redefine修改过的StringBuilder"><a href="#启动应用，使用Arthas-redefine修改过的StringBuilder" class="headerlink" title="启动应用，使用Arthas redefine修改过的StringBuilder"></a>启动应用，使用Arthas redefine修改过的StringBuilder</h2><p>启动应用后，在奇怪日志输出之前，先使用arthas attach应用，再redefine StringBuilder:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ redefine -p /tmp/StringBuilder.class</span><br><span class="line">redefine success, size: 1</span><br></pre></td></tr></table></figure><p>当执行到输出<code>[] [] [] No credential found</code>的logger代码时，会打印当前栈。实际运行结果是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[] [] [] No credential found</span><br><span class="line">java.lang.Throwable</span><br><span class="line">at java.lang.StringBuilder.toString(StringBuilder.java:<span class="number">410</span>)</span><br><span class="line">at com.taobao.middleware.logger.util.MessageUtil.getMessage(MessageUtil.java:<span class="number">26</span>)</span><br><span class="line">at com.taobao.middleware.logger.util.MessageUtil.getMessage(MessageUtil.java:<span class="number">15</span>)</span><br><span class="line">at com.taobao.middleware.logger.slf4j.Slf4jLogger.info(Slf4jLogger.java:<span class="number">77</span>)</span><br><span class="line">at com.taobao.spas.sdk.common.log.SpasLogger.info(SpasLogger.java:<span class="number">18</span>)</span><br><span class="line">at com.taobao.spas.sdk.client.identity.CredentialWatcher.loadCredential(CredentialWatcher.java:<span class="number">128</span>)</span><br><span class="line">at com.taobao.spas.sdk.client.identity.CredentialWatcher.access$<span class="number">200</span>(CredentialWatcher.java:<span class="number">18</span>)</span><br><span class="line">at com.taobao.spas.sdk.client.identity.CredentialWatcher$<span class="number">1</span>.run(CredentialWatcher.java:<span class="number">58</span>)</span><br><span class="line">at java.util.TimerThread.mainLoop(Timer.java:<span class="number">555</span>)</span><br><span class="line">at java.util.TimerThread.run(Timer.java:<span class="number">505</span>)</span><br></pre></td></tr></table></figure><p><strong>可以看到是<code>spas.sdk</code>打印出了<code>[] [] [] No credential found</code>的日志。</strong></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>logger最终会用StringBuilder来输出</li><li>修改StringBuilder来定位输出特定日志的地方</li><li>使用Arthas redefine命令来加载修改过的StringBuilder</li><li>redefine命令实际上实现了任意代码线上debug的功能，可以随意本地修改代码重新编绎，然后线上redefine加载</li><li>redefine的功能过于强大，所以请小心使用:)</li></ul><h2 id="Arthas实践系列"><a href="#Arthas实践系列" class="headerlink" title="Arthas实践系列"></a>Arthas实践系列</h2><ul><li><a href="/arthas-logger-problem/">使用Arthas抽丝剥茧排查线上应用日志打满问题</a></li><li><a href="/spring-boot-arthas-NoSuchMethodError/">深入Spring Boot：利用Arthas排查NoSuchMethodError</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;随着应用越来越复杂，依赖越来越多，日志系统越来越混乱，有时会出现一些奇怪的日志，比如：&lt;/p&gt;
&lt;figure class=&quot;highlig
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="logger" scheme="http://hengyunabc.github.io/tags/logger/"/>
    
      <category term="redefine" scheme="http://hengyunabc.github.io/tags/redefine/"/>
    
  </entry>
  
  <entry>
    <title>Arthas实践--抽丝剥茧排查线上应用日志打满问题</title>
    <link href="http://hengyunabc.github.io/arthas-logger-problem/"/>
    <id>http://hengyunabc.github.io/arthas-logger-problem/</id>
    <published>2018-10-22T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.251Z</updated>
    
    <content type="html"><![CDATA[<h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>在应用的 <code>service_stdout.log</code>里一直输出下面的日志，直接把磁盘打满了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 14 times in a row.</span><br><span class="line">23:07:34.460 [TAIRCLIENT-1-thread-3] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.</span><br><span class="line">23:07:34.461 [TAIRCLIENT-1-thread-4] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.</span><br><span class="line">23:07:34.462 [TAIRCLIENT-1-thread-5] DEBUG io.netty.channel.nio.NioEventLoop - Selector.select() returned prematurely 3 times in a row.</span><br></pre></td></tr></table></figure><p> <code>service_stdout.log</code>是进程标准输出的重定向，可以初步判定是tair插件把日志输出到了stdout里。</p><p>尽管有了初步的判断，但是具体logger为什么会打到stdout里，还需要进一步排查，常见的方法可能是本地debug。</p><p>下面介绍利用arthas直接在线上定位问题的过程，主要使用<code>sc</code>和<code>getstatic</code>命令。</p><ul><li><a href="https://alibaba.github.io/arthas/sc.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/sc.html</a></li><li><a href="https://alibaba.github.io/arthas/getstatic.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/getstatic.html</a></li></ul><h2 id="定位logger的具体实现"><a href="#定位logger的具体实现" class="headerlink" title="定位logger的具体实现"></a>定位logger的具体实现</h2><p>日志是<code>io.netty.channel.nio.NioEventLoop</code>输出的，到netty的代码里查看，发现是DEBUG级别的输出：</p><ul><li><a href="https://github.com/netty/netty/blob/netty-4.0.35.Final/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java#L673" target="_blank" rel="noopener">https://github.com/netty/netty/blob/netty-4.0.35.Final/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java#L673</a></li></ul><p>然后用arthas的<code>sc</code>命令来查看具体的<code>io.netty.channel.nio.NioEventLoop</code>是从哪里加载的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span>-<span class="title">info</span>        <span class="title">io</span>.<span class="title">netty</span>.<span class="title">channel</span>.<span class="title">nio</span>.<span class="title">NioEventLoop</span></span></span><br><span class="line"> code-source       file:/opt/app/plugins/tair-plugin/lib/netty-all-4.0.35.Final.jar!/</span><br><span class="line"> name              io.netty.channel.nio.NioEventLoop</span><br><span class="line"> isInterface       <span class="keyword">false</span></span><br><span class="line"> isAnnotation      <span class="keyword">false</span></span><br><span class="line"> isEnum            <span class="keyword">false</span></span><br><span class="line"> isAnonymousClass  <span class="keyword">false</span></span><br><span class="line"> isArray           <span class="keyword">false</span></span><br><span class="line"> isLocalClass      <span class="keyword">false</span></span><br><span class="line"> isMemberClass     <span class="keyword">false</span></span><br><span class="line"> isPrimitive       <span class="keyword">false</span></span><br><span class="line"> isSynthetic       <span class="keyword">false</span></span><br><span class="line"> simple-name       NioEventLoop</span><br><span class="line"> modifier          <span class="keyword">final</span>,<span class="keyword">public</span></span><br><span class="line"> annotation</span><br><span class="line"> interfaces</span><br><span class="line"> <span class="keyword">super</span>-<span class="class"><span class="keyword">class</span>       +-<span class="title">io</span>.<span class="title">netty</span>.<span class="title">channel</span>.<span class="title">SingleThreadEventLoop</span></span></span><br><span class="line"><span class="class">                     +-<span class="title">io</span>.<span class="title">netty</span>.<span class="title">util</span>.<span class="title">concurrent</span>.<span class="title">SingleThreadEventExecutor</span></span></span><br><span class="line"><span class="class">                       +-<span class="title">io</span>.<span class="title">netty</span>.<span class="title">util</span>.<span class="title">concurrent</span>.<span class="title">AbstractScheduledEventExecutor</span></span></span><br><span class="line"><span class="class">                         +-<span class="title">io</span>.<span class="title">netty</span>.<span class="title">util</span>.<span class="title">concurrent</span>.<span class="title">AbstractEventExecutor</span></span></span><br><span class="line"><span class="class">                           +-<span class="title">java</span>.<span class="title">util</span>.<span class="title">concurrent</span>.<span class="title">AbstractExecutorService</span></span></span><br><span class="line"><span class="class">                             +-<span class="title">java</span>.<span class="title">lang</span>.<span class="title">Object</span></span></span><br><span class="line"><span class="class"> <span class="title">class</span>-<span class="title">loader</span>      +-<span class="title">tair</span>-<span class="title">plugin</span>'<span class="title">s</span> <span class="title">ModuleClassLoader</span></span></span><br><span class="line"><span class="class"> <span class="title">classLoaderHash</span>   73<span class="title">ad2d6</span></span></span><br></pre></td></tr></table></figure><p>可见，的确是从tair插件里加载的。</p><p>查看NioEventLoop的代码，可以发现它有一个<code>logger</code>的field：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">NioEventLoop</span> <span class="keyword">extends</span> <span class="title">SingleThreadEventLoop</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> InternalLogger logger = InternalLoggerFactory.getInstance(NioEventLoop<span class="class">.<span class="keyword">class</span>)</span>;</span><br></pre></td></tr></table></figure><p>使用arthas的<code>getstatic</code>命令来查看这个<code>logger</code>具体实现类是什么（使用<code>-c</code>参数指定classloader）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger &apos;getClass().getName()&apos;</span><br><span class="line">field: logger</span><br><span class="line">@String[io.netty.util.internal.logging.Slf4JLogger]</span><br></pre></td></tr></table></figure><p>可以发现是<code>Slf4JLogger</code>。 </p><p>再查看io.netty.util.internal.logging.Slf4JLogger的实现，发现它内部有一个logger的field：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.netty.util.internal.logging;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * &lt;a href="http://www.slf4j.org/"&gt;SLF4J&lt;/a&gt; logger.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Slf4JLogger</span> <span class="keyword">extends</span> <span class="title">AbstractInternalLogger</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">108038972685130825L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">transient</span> Logger logger;</span><br></pre></td></tr></table></figure><p>那么使用arthas的<code>getstatic</code>命令来查看这个<code>logger</code>属性的值：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ getstatic -c <span class="number">73</span>ad2d6 io.netty.channel.nio.NioEventLoop logger <span class="string">'logger'</span></span><br><span class="line">field: logger</span><br><span class="line"><span class="meta">@Logger</span>[</span><br><span class="line">    serialVersionUID=<span class="meta">@Long</span>[<span class="number">5454405123156820674</span>],</span><br><span class="line">    FQCN=<span class="meta">@String</span>[ch.qos.logback.classic.Logger],</span><br><span class="line">    name=<span class="meta">@String</span>[io.netty.channel.nio.NioEventLoop],</span><br><span class="line">    level=<span class="keyword">null</span>,</span><br><span class="line">    effectiveLevelInt=<span class="meta">@Integer</span>[<span class="number">10000</span>],</span><br><span class="line">    parent=<span class="meta">@Logger</span>[Logger[io.netty.channel.nio]],</span><br><span class="line">    childrenList=<span class="keyword">null</span>,</span><br><span class="line">    aai=<span class="keyword">null</span>,</span><br><span class="line">    additive=<span class="meta">@Boolean</span>[<span class="keyword">true</span>],</span><br><span class="line">    loggerContext=<span class="meta">@LoggerContext</span>[ch.qos.logback.classic.LoggerContext[<span class="keyword">default</span>]],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>可见，logger的最本质实现类是：<code>ch.qos.logback.classic.Logger</code>。</p><p>再次用<code>getstatic</code>命令来确定jar包的location：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ getstatic -c <span class="number">73</span>ad2d6 io.netty.channel.nio.NioEventLoop logger <span class="string">'logger.getClass().getProtectionDomain().getCodeSource().getLocation()'</span></span><br><span class="line">field: logger</span><br><span class="line"><span class="meta">@URL</span>[</span><br><span class="line">    BUILTIN_HANDLERS_PREFIX=<span class="meta">@String</span>[sun.net.www.protocol],</span><br><span class="line">    serialVersionUID=<span class="meta">@Long</span>[-<span class="number">7627629688361524110</span>],</span><br><span class="line">    protocolPathProp=<span class="meta">@String</span>[java.protocol.handler.pkgs],</span><br><span class="line">    protocol=<span class="meta">@String</span>[jar],</span><br><span class="line">    host=<span class="meta">@String</span>[],</span><br><span class="line">    port=<span class="meta">@Integer</span>[-<span class="number">1</span>],</span><br><span class="line">    file=<span class="meta">@String</span>[file:/opt/app/plugins/tair-plugin/lib/logback-classic-<span class="number">1.2</span><span class="number">.3</span>.jar!/],</span><br><span class="line">    query=<span class="keyword">null</span>,</span><br><span class="line">    authority=<span class="meta">@String</span>[],</span><br><span class="line">    path=<span class="meta">@String</span>[file:/opt/app/plugins/tair-plugin/lib/logback-classic-<span class="number">1.2</span><span class="number">.3</span>.jar!/],</span><br><span class="line">    userInfo=<span class="keyword">null</span>,</span><br><span class="line">    ref=<span class="keyword">null</span>,</span><br><span class="line">    hostAddress=<span class="keyword">null</span>,</span><br><span class="line">    handler=<span class="meta">@Handler</span>[com.taobao.pandora.loader.jar.Handler@<span class="number">1</span>a0c361e],</span><br><span class="line">    hashCode=<span class="meta">@Integer</span>[<span class="number">126346621</span>],</span><br><span class="line">    tempState=<span class="keyword">null</span>,</span><br><span class="line">    factory=<span class="meta">@TomcatURLStreamHandlerFactory</span>[org.apache.catalina.webresources.TomcatURLStreamHandlerFactory@<span class="number">3</span>edd7b7],</span><br><span class="line">    handlers=<span class="meta">@Hashtable</span>[isEmpty=<span class="keyword">false</span>;size=<span class="number">4</span>],</span><br><span class="line">    streamHandlerLock=<span class="meta">@Object</span>[java.lang.Object@<span class="number">488</span>ccac9],</span><br><span class="line">    serialPersistentFields=<span class="meta">@ObjectStreamField</span>[][isEmpty=<span class="keyword">false</span>;size=<span class="number">7</span>],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>可见这个<code>ch.qos.logback.classic.Logger</code>的确是tair插件里加载的。</p><h2 id="定位logger的level"><a href="#定位logger的level" class="headerlink" title="定位logger的level"></a>定位logger的level</h2><p>上面已经定位logger的实现类是<code>ch.qos.logback.classic.Logger</code>，但是为什么它会输出<code>DEBUG</code> level的日志？</p><p>其实在上面的<code>getstatic -c 73ad2d6 io.netty.channel.nio.NioEventLoop logger &#39;logger&#39;</code>输出里，已经打印出它的level是null了。如果对logger有所了解的话，可以知道当child logger的level为null时，它的level取决于parent logger的level。</p><p>我们再来看下<code>ch.qos.logback.classic.Logger</code>的代码，它有一个parent logger的属性：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Logger</span> <span class="keyword">implements</span> <span class="title">org</span>.<span class="title">slf4j</span>.<span class="title">Logger</span>, <span class="title">LocationAwareLogger</span>, <span class="title">AppenderAttachable</span>&lt;<span class="title">ILoggingEvent</span>&gt;, <span class="title">Serializable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * The parent of this category. All categories have at least one ancestor</span></span><br><span class="line"><span class="comment">     * which is the root category.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">transient</span> <span class="keyword">private</span> Logger parent;</span><br></pre></td></tr></table></figure><p>所以，我们可以通过<code>getstatic</code>来获取到这个parent属性的内容。然后通过多个parent操作，可以发现level都是null，最终发现ROOT level是DEBUG 。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ getstatic -c <span class="number">73</span>ad2d6 io.netty.channel.nio.NioEventLoop logger <span class="string">'logger.parent.parent.parent.parent.parent'</span></span><br><span class="line">field: logger</span><br><span class="line"><span class="meta">@Logger</span>[</span><br><span class="line">    serialVersionUID=<span class="meta">@Long</span>[<span class="number">5454405123156820674</span>],</span><br><span class="line">    FQCN=<span class="meta">@String</span>[ch.qos.logback.classic.Logger],</span><br><span class="line">    name=<span class="meta">@String</span>[ROOT],</span><br><span class="line">    level=<span class="meta">@Level</span>[DEBUG],</span><br><span class="line">    effectiveLevelInt=<span class="meta">@Integer</span>[<span class="number">10000</span>],</span><br><span class="line">    parent=<span class="keyword">null</span>,</span><br><span class="line">    childrenList=<span class="meta">@CopyOnWriteArrayList</span>[isEmpty=<span class="keyword">false</span>;size=<span class="number">2</span>],</span><br><span class="line">    aai=<span class="meta">@AppenderAttachableImpl</span>[ch.qos.logback.core.spi.AppenderAttachableImpl@<span class="number">1</span>ecf9bae],</span><br><span class="line">    additive=<span class="meta">@Boolean</span>[<span class="keyword">true</span>],</span><br><span class="line">    loggerContext=<span class="meta">@LoggerContext</span>[ch.qos.logback.classic.LoggerContext[<span class="keyword">default</span>]],</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>所以 <code>io.netty.channel.nio.NioEventLoop</code>的logger的level取的是ROOT logger的配置，即默认值<code>DEBUG</code>。</p><p>具体实现可以查看<code>ch.qos.logback.classic.LoggerContext</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">LoggerContext</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>();</span><br><span class="line">    <span class="keyword">this</span>.loggerCache = <span class="keyword">new</span> ConcurrentHashMap&lt;String, Logger&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.loggerContextRemoteView = <span class="keyword">new</span> LoggerContextVO(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.root = <span class="keyword">new</span> Logger(Logger.ROOT_LOGGER_NAME, <span class="keyword">null</span>, <span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.root.setLevel(Level.DEBUG);</span><br><span class="line">    loggerCache.put(Logger.ROOT_LOGGER_NAME, root);</span><br><span class="line">    initEvaluatorMap();</span><br><span class="line">    size = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">this</span>.frameworkPackages = <span class="keyword">new</span> ArrayList&lt;String&gt;();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么logback输出到了stdout里"><a href="#为什么logback输出到了stdout里" class="headerlink" title="为什么logback输出到了stdout里"></a>为什么logback输出到了stdout里</h2><p>上面我们得到结论</p><ul><li>tair插件里的logback默认的level是DEBUG，导致netty里的日志可以被打印出来</li></ul><p>那么我们可以猜测：</p><ul><li>tair里的logback没有特殊配置，或者只配置了tair自己的package，导致ROOT logger的日志直接输出到stdout里</li></ul><p>那么实现上tair里是使用了<code>logger-api</code>，它通过<code>LoggerFactory.getLogger</code>函数获取到了自己package的logger，然后设置level为<code>INFO</code>，并设置了appender。</p><p><strong>换而言之，tair插件里的logback没有设置ROOT logger，所以它的默认level是DEBUG，并且默认的appender会输出到stdout里。</strong></p><p>所以tair插件可以增加设置ROOT logger level为<code>INFO</code>来修复这个问题。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> com.taobao.middleware.logger.Logger logger</span><br><span class="line">= com.taobao.middleware.logger.LoggerFactory.getLogger(<span class="string">"com.taobao.tair"</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> com.taobao.middleware.logger.Logger infolog</span><br><span class="line">= com.taobao.middleware.logger.LoggerFactory.getLogger(<span class="string">"com.taobao.tair.custom-infolog"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> JM_LOG_RETAIN_COUNT = <span class="number">3</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String JM_LOG_FILE_SIZE = <span class="string">"200MB"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">String tmp = System.getProperty(<span class="string">"JM.LOG.RETAIN.COUNT"</span>, <span class="string">"3"</span>);</span><br><span class="line">JM_LOG_RETAIN_COUNT = Integer.parseInt(tmp);</span><br><span class="line">&#125; <span class="keyword">catch</span> (NumberFormatException e) &#123;</span><br><span class="line">&#125;</span><br><span class="line">JM_LOG_FILE_SIZE = System.getProperty(<span class="string">"JM.LOG.FILE.SIZE"</span>, <span class="string">"200MB"</span>);</span><br><span class="line"></span><br><span class="line">logger.setLevel(Level.INFO);</span><br><span class="line">logger.activateAppenderWithSizeRolling(<span class="string">"tair-client"</span>, <span class="string">"tair-client.log"</span>, <span class="string">"UTF-8"</span>,</span><br><span class="line">TairUtil.splitSize(JM_LOG_FILE_SIZE, <span class="number">0.8</span> / (JM_LOG_RETAIN_COUNT + <span class="number">1</span>)), JM_LOG_RETAIN_COUNT);</span><br><span class="line">logger.setAdditivity(<span class="keyword">false</span>);</span><br><span class="line">logger.activateAsync(<span class="number">500</span>, <span class="number">100</span>);</span><br><span class="line"></span><br><span class="line">logger.info(<span class="string">"JM_LOG_RETAIN_COUNT "</span> + JM_LOG_RETAIN_COUNT + <span class="string">" JM_LOG_FILE_SIZE "</span> + JM_LOG_FILE_SIZE);</span><br><span class="line"></span><br><span class="line">infolog.setLevel(Level.INFO);</span><br><span class="line">infolog.activateAppenderWithSizeRolling(<span class="string">"tair-client"</span>, <span class="string">"tair-client-info.log"</span>, <span class="string">"UTF-8"</span>, <span class="string">"10MB"</span>, <span class="number">1</span>);</span><br><span class="line">infolog.setAdditivity(<span class="keyword">false</span>);</span><br><span class="line">infolog.activateAsync(<span class="number">500</span>, <span class="number">100</span>);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>tair插件里直接以api的方式设置了自己package下的logger</li><li>tair插件里netty的logger的packge和tair并不一样，所以它最终取的是ROOT logger的配置</li><li>logback默认的ROOT logger level是<code>DEBUG</code>，输出是stdout</li><li>利用arthas的<code>sc</code>命令定位具体的类</li><li>利用arthas的<code>getstatic</code>获取static filed的值</li><li>利用logger parent层联的特性，可以向上一层层获取到ROOT logger的配置</li></ul><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li>Arthas开源：<a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">https://github.com/alibaba/arthas</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;现象&quot;&gt;&lt;a href=&quot;#现象&quot; class=&quot;headerlink&quot; title=&quot;现象&quot;&gt;&lt;/a&gt;现象&lt;/h2&gt;&lt;p&gt;在应用的 &lt;code&gt;service_stdout.log&lt;/code&gt;里一直输出下面的日志，直接把磁盘打满了：&lt;/p&gt;
&lt;figure c
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="logger" scheme="http://hengyunabc.github.io/tags/logger/"/>
    
      <category term="classloader" scheme="http://hengyunabc.github.io/tags/classloader/"/>
    
  </entry>
  
  <entry>
    <title>深入Spring Boot：利用Arthas排查NoSuchMethodError</title>
    <link href="http://hengyunabc.github.io/spring-boot-arthas-NoSuchMethodError/"/>
    <id>http://hengyunabc.github.io/spring-boot-arthas-NoSuchMethodError/</id>
    <published>2018-09-25T20:58:28.000Z</published>
    <updated>2020-03-02T17:57:00.255Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>有时spring boot应用会遇到<code>java.lang.NoSuchMethodError</code>的问题，下面以具体的demo来说明怎样利用<a href="https://github.com/alibaba/arthas" target="_blank" rel="noopener">arthas</a>来排查。</p><p>Demo: <a href="https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-NoSuchMethodError" target="_blank" rel="noopener">https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-NoSuchMethodError</a></p><h2 id="在应用的main函数里catch住异常，保证进程不退出"><a href="#在应用的main函数里catch住异常，保证进程不退出" class="headerlink" title="在应用的main函数里catch住异常，保证进程不退出"></a>在应用的main函数里catch住异常，保证进程不退出</h2><p>很多时候当应用抛出异常后，进程退出了，就比较难排查问题。可以先改下main函数，把异常catch住：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">SpringApplication.run(DemoNoSuchMethodErrorApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">&#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">e.printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// block</span></span><br><span class="line">System.in.read();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo启动之后，抛出的异常是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationAwareOrderComparator.sort(Ljava/util/List;)V</span><br><span class="line">at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:<span class="number">394</span>)</span><br><span class="line">at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:<span class="number">383</span>)</span><br><span class="line">at org.springframework.boot.SpringApplication.initialize(SpringApplication.java:<span class="number">249</span>)</span><br><span class="line">at org.springframework.boot.SpringApplication.&lt;init&gt;(SpringApplication.java:<span class="number">225</span>)</span><br><span class="line">at org.springframework.boot.SpringApplication.run(SpringApplication.java:<span class="number">1118</span>)</span><br><span class="line">at org.springframework.boot.SpringApplication.run(SpringApplication.java:<span class="number">1107</span>)</span><br><span class="line">at com.example.demoNoSuchMethodError.DemoNoSuchMethodErrorApplication.main(DemoNoSuchMethodErrorApplication.java:<span class="number">13</span>)</span><br></pre></td></tr></table></figure><p>显然，异常的意思是<code>AnnotationAwareOrderComparator</code>缺少<code>sort(Ljava/util/List;)V</code>这个函数。</p><h2 id="安装arthas"><a href="#安装arthas" class="headerlink" title="安装arthas"></a>安装arthas</h2><p>参考：<a href="https://alibaba.github.io/arthas/install-detail.html" target="_blank" rel="noopener">https://alibaba.github.io/arthas/install-detail.html</a></p><h2 id="使用sc命令查找类所在的jar包"><a href="#使用sc命令查找类所在的jar包" class="headerlink" title="使用sc命令查找类所在的jar包"></a>使用sc命令查找类所在的jar包</h2><p>应用需要抛出了异常，但是进程还没有退出，我们用arthas来attach上去。比如在mac下面：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./as.sh</span><br></pre></td></tr></table></figure><p>然后选择<code>com.example.demoNoSuchMethodError.DemoNoSuchMethodErrorApplication</code>进程。</p><p>再执行<a href="https://alibaba.github.io/arthas/sc.html" target="_blank" rel="noopener">sc</a>命令来查找类：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ sc -d org.springframework.core.annotation.AnnotationAwareOrderComparator</span><br><span class="line"> <span class="class"><span class="keyword">class</span>-<span class="title">info</span>        <span class="title">org</span>.<span class="title">springframework</span>.<span class="title">core</span>.<span class="title">annotation</span>.<span class="title">AnnotationAwareOrderComparator</span></span></span><br><span class="line"><span class="class"> <span class="title">code</span>-<span class="title">source</span>       /<span class="title">Users</span>/<span class="title">hengyunabc</span>/.<span class="title">m2</span>/<span class="title">repository</span>/<span class="title">org</span>/<span class="title">springframework</span>/<span class="title">spring</span>/2.5.6.<span class="title">SEC03</span>/<span class="title">spring</span>-2.5.6.<span class="title">SEC03</span>.<span class="title">jar</span></span></span><br><span class="line"><span class="class"> <span class="title">name</span>              <span class="title">org</span>.<span class="title">springframework</span>.<span class="title">core</span>.<span class="title">annotation</span>.<span class="title">AnnotationAwareOrderComparator</span></span></span><br><span class="line"><span class="class"> <span class="title">isInterface</span>       <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isAnnotation</span>      <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isEnum</span>            <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isAnonymousClass</span>  <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isArray</span>           <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isLocalClass</span>      <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isMemberClass</span>     <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isPrimitive</span>       <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">isSynthetic</span>       <span class="title">false</span></span></span><br><span class="line"><span class="class"> <span class="title">simple</span>-<span class="title">name</span>       <span class="title">AnnotationAwareOrderComparator</span></span></span><br><span class="line"><span class="class"> <span class="title">modifier</span>          <span class="title">public</span></span></span><br><span class="line"><span class="class"> <span class="title">annotation</span></span></span><br><span class="line"><span class="class"> <span class="title">interfaces</span></span></span><br><span class="line"><span class="class"> <span class="title">super</span>-<span class="title">class</span>       +-<span class="title">org</span>.<span class="title">springframework</span>.<span class="title">core</span>.<span class="title">OrderComparator</span></span></span><br><span class="line"><span class="class">                     +-<span class="title">java</span>.<span class="title">lang</span>.<span class="title">Object</span></span></span><br><span class="line"><span class="class"> <span class="title">class</span>-<span class="title">loader</span>      +-<span class="title">sun</span>.<span class="title">misc</span>.<span class="title">Launcher</span>$<span class="title">AppClassLoader</span>@5<span class="title">c647e05</span></span></span><br><span class="line"><span class="class">                     +-<span class="title">sun</span>.<span class="title">misc</span>.<span class="title">Launcher</span>$<span class="title">ExtClassLoader</span>@689<span class="title">e3d07</span></span></span><br><span class="line"><span class="class"> <span class="title">classLoaderHash</span>   5<span class="title">c647e05</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line">Affect(row-cnt:1) cost in 41 ms.</span><br></pre></td></tr></table></figure><p>可以看到<code>AnnotationAwareOrderComparator</code>是从<code>spring-2.5.6.SEC03.jar</code>里加载的。</p><h2 id="使用jad查看反编绎的源代码"><a href="#使用jad查看反编绎的源代码" class="headerlink" title="使用jad查看反编绎的源代码"></a>使用jad查看反编绎的源代码</h2><p>下面使用<a href="https://alibaba.github.io/arthas/jad.html" target="_blank" rel="noopener">jad</a>命令来查看<code>AnnotationAwareOrderComparator</code>的源代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">$ jad org.springframework.core.annotation.AnnotationAwareOrderComparator</span><br><span class="line"></span><br><span class="line">ClassLoader:</span><br><span class="line">+-sun.misc.Launcher$AppClassLoader@5c647e05</span><br><span class="line">  +-sun.misc.Launcher$ExtClassLoader@689e3d07</span><br><span class="line"></span><br><span class="line">Location:</span><br><span class="line">/Users/hengyunabc/.m2/repository/org/springframework/spring/2.5.6.SEC03/spring-2.5.6.SEC03.jar</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line"> * Decompiled with CFR 0_132.</span><br><span class="line"> */</span><br><span class="line">package org.springframework.core.annotation;</span><br><span class="line"></span><br><span class="line">import java.lang.annotation.Annotation;</span><br><span class="line">import org.springframework.core.OrderComparator;</span><br><span class="line">import org.springframework.core.Ordered;</span><br><span class="line">import org.springframework.core.annotation.Order;</span><br><span class="line"></span><br><span class="line">public class AnnotationAwareOrderComparator</span><br><span class="line">extends OrderComparator &#123;</span><br><span class="line">    protected int getOrder(Object obj) &#123;</span><br><span class="line">        Order order;</span><br><span class="line">        if (obj instanceof Ordered) &#123;</span><br><span class="line">            return ((Ordered)obj).getOrder();</span><br><span class="line">        &#125;</span><br><span class="line">        if (obj != null &amp;&amp; (order = obj.getClass().getAnnotation(Order.class)) != null) &#123;</span><br><span class="line">            return order.value();</span><br><span class="line">        &#125;</span><br><span class="line">        return Integer.MAX_VALUE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Affect(row-cnt:1) cost in 286 ms.</span><br></pre></td></tr></table></figure><p>可见，<code>AnnotationAwareOrderComparator</code>的确没有<code>sort(Ljava/util/List;)V</code>函数。</p><h2 id="排掉依赖，解决问题"><a href="#排掉依赖，解决问题" class="headerlink" title="排掉依赖，解决问题"></a>排掉依赖，解决问题</h2><p>从上面的排查里，可以确定</p><ul><li><code>AnnotationAwareOrderComparator</code>来自<code>spring-2.5.6.SEC03.jar</code>，的确没有<code>sort(Ljava/util/List;)V</code>函数。</li></ul><p>所以，可以检查maven依赖，把spring 2的jar包排掉，这样子就可以解决问题了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>仔细看<code>NoSuchMethodError</code>的异常信息，了解是什么类缺少了什么函数</li><li>利用arthas来查找类，反编绎源码，确认问题</li></ul><h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul><li><a href="https://alibaba.github.io/arthas" target="_blank" rel="noopener">Arthas–Alibaba开源Java诊断利器</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;有时spring boot应用会遇到&lt;code&gt;java.lang.NoSuchMethodError&lt;/code&gt;的问题，下面以具体的de
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="java" scheme="http://hengyunabc.github.io/tags/java/"/>
    
      <category term="arthas" scheme="http://hengyunabc.github.io/tags/arthas/"/>
    
      <category term="spring-boot" scheme="http://hengyunabc.github.io/tags/spring-boot/"/>
    
      <category term="spring" scheme="http://hengyunabc.github.io/tags/spring/"/>
    
  </entry>
  
  <entry>
    <title>深入Spring Boot：显式配置 @EnableWebMvc 导致静态资源访问失败</title>
    <link href="http://hengyunabc.github.io/spring-boot-enablewebmvc-static-404/"/>
    <id>http://hengyunabc.github.io/spring-boot-enablewebmvc-static-404/</id>
    <published>2018-08-08T20:19:38.000Z</published>
    <updated>2020-03-02T17:57:00.255Z</updated>
    
    <content type="html"><![CDATA[<h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>当用户在自己的spring boot main class上面显式使用了<code>@EnableWebMvc</code>，发现原来的放在 <code>src/main/resources/static</code> 目录下面的静态资源访问不到了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoApplication</span> </span>&#123;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">SpringApplication.run(DemoApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比如在用户代码目录<code>src/main/resources</code>里有一个<code>hello.txt</code>的资源。访问 <code>http://localhost:8080/hello.txt</code> 返回的结果是404：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Whitelabel Error Page</span><br><span class="line"></span><br><span class="line">This application has no explicit mapping for /error, so you are seeing this as a fallback.</span><br><span class="line"></span><br><span class="line">Thu Jun 01 11:39:41 CST 2017</span><br><span class="line">There was an unexpected error (type=Not Found, status=404).</span><br><span class="line">No message available</span><br></pre></td></tr></table></figure><h2 id="静态资源访问失败原因"><a href="#静态资源访问失败原因" class="headerlink" title="静态资源访问失败原因"></a>静态资源访问失败原因</h2><h3 id="EnableWebMvc的实现"><a href="#EnableWebMvc的实现" class="headerlink" title="@EnableWebMvc的实现"></a><code>@EnableWebMvc</code>的实现</h3><p>那么为什么用户显式配置了<code>@EnableWebMvc</code>，spring boot访问静态资源会失败？</p><p>我们先来看下<code>@EnableWebMvc</code>的实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Import</span>(DelegatingWebMvcConfiguration<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> @<span class="title">interface</span> <span class="title">EnableWebMvc</span> </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A subclass of &#123;<span class="doctag">@code</span> WebMvcConfigurationSupport&#125; that detects and delegates</span></span><br><span class="line"><span class="comment"> * to all beans of type &#123;<span class="doctag">@link</span> WebMvcConfigurer&#125; allowing them to customize the</span></span><br><span class="line"><span class="comment"> * configuration provided by &#123;<span class="doctag">@code</span> WebMvcConfigurationSupport&#125;. This is the</span></span><br><span class="line"><span class="comment"> * class actually imported by &#123;<span class="doctag">@link</span> EnableWebMvc <span class="doctag">@EnableWebMvc</span>&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Rossen Stoyanchev</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 3.1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DelegatingWebMvcConfiguration</span> <span class="keyword">extends</span> <span class="title">WebMvcConfigurationSupport</span> </span>&#123;</span><br></pre></td></tr></table></figure><p>可以看到<code>@EnableWebMvc</code> 引入了 <code>WebMvcConfigurationSupport</code>，是spring mvc 3.1里引入的一个自动初始化配置的<code>@Configuration</code> 类。</p><h3 id="spring-boot里的静态资源访问的实现"><a href="#spring-boot里的静态资源访问的实现" class="headerlink" title="spring boot里的静态资源访问的实现"></a>spring boot里的静态资源访问的实现</h3><p>再来看下spring boot里是怎么实现对<code>src/main/resources/static</code>这些目录的支持。</p><p>主要是通过<code>org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration</code>来实现的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnWebApplication</span></span><br><span class="line"><span class="meta">@ConditionalOnClass</span>(&#123; Servlet<span class="class">.<span class="keyword">class</span>, <span class="title">DispatcherServlet</span>.<span class="title">class</span>,</span></span><br><span class="line"><span class="class"><span class="title">WebMvcConfigurerAdapter</span>.<span class="title">class</span> &#125;)</span></span><br><span class="line"><span class="class">@<span class="title">ConditionalOnMissingBean</span>(<span class="title">WebMvcConfigurationSupport</span>.<span class="title">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">AutoConfigureOrder</span>(<span class="title">Ordered</span>.<span class="title">HIGHEST_PRECEDENCE</span> + 10)</span></span><br><span class="line"><span class="class">@<span class="title">AutoConfigureAfter</span>(</span>&#123; DispatcherServletAutoConfiguration<span class="class">.<span class="keyword">class</span>,</span></span><br><span class="line"><span class="class"><span class="title">ValidationAutoConfiguration</span>.<span class="title">class</span> &#125;)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">WebMvcAutoConfiguration</span> </span>&#123;</span><br></pre></td></tr></table></figure><p>可以看到 <code>@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)</code> ，这个条件导致spring boot的<code>WebMvcAutoConfiguration</code>不生效。</p><p>总结下具体的原因：</p><ol start="0"><li>用户配置了<code>@EnableWebMvc</code></li><li>Spring扫描所有的注解，再从注解上扫描到<code>@Import</code>，把这些<code>@Import</code>引入的bean信息都缓存起来</li><li>在扫描到<code>@EnableWebMvc</code>时，通过<code>@Import</code>加入了 <code>DelegatingWebMvcConfiguration</code>，也就是<code>WebMvcConfigurationSupport</code></li><li>spring再处理<code>@Conditional</code>相关的注解，判断发现已有<code>WebMvcConfigurationSupport</code>，就跳过了spring bootr的<code>WebMvcAutoConfiguration</code></li></ol><p>所以spring boot自己的静态资源配置不生效。</p><p>其实在spring boot的文档里也有提到这点： <a href="http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration" target="_blank" rel="noopener">http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration</a></p><ul><li>If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.</li></ul><h3 id="Spring-Boot-ResourceProperties的配置"><a href="#Spring-Boot-ResourceProperties的配置" class="headerlink" title="Spring Boot ResourceProperties的配置"></a>Spring Boot ResourceProperties的配置</h3><p>在spring boot里静态资源目录的配置是在<code>ResourceProperties</code>里。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.resources"</span>, ignoreUnknownFields = <span class="keyword">false</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceProperties</span> <span class="keyword">implements</span> <span class="title">ResourceLoaderAware</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] SERVLET_RESOURCE_LOCATIONS = &#123; <span class="string">"/"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] CLASSPATH_RESOURCE_LOCATIONS = &#123;</span><br><span class="line"><span class="string">"classpath:/META-INF/resources/"</span>, <span class="string">"classpath:/resources/"</span>,</span><br><span class="line"><span class="string">"classpath:/static/"</span>, <span class="string">"classpath:/public/"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String[] RESOURCE_LOCATIONS;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">RESOURCE_LOCATIONS = <span class="keyword">new</span> String[CLASSPATH_RESOURCE_LOCATIONS.length</span><br><span class="line">+ SERVLET_RESOURCE_LOCATIONS.length];</span><br><span class="line">System.arraycopy(SERVLET_RESOURCE_LOCATIONS, <span class="number">0</span>, RESOURCE_LOCATIONS, <span class="number">0</span>,</span><br><span class="line">SERVLET_RESOURCE_LOCATIONS.length);</span><br><span class="line">System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, <span class="number">0</span>, RESOURCE_LOCATIONS,</span><br><span class="line">SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在 <code>WebMvcAutoConfigurationAdapter</code>里会初始始化相关的ResourceHandler。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Import</span>(&#123; EnableWebMvcConfiguration<span class="class">.<span class="keyword">class</span>, <span class="title">MvcValidatorRegistrar</span>.<span class="title">class</span> &#125;)</span></span><br><span class="line"><span class="class">@<span class="title">EnableConfigurationProperties</span>(</span>&#123; WebMvcProperties<span class="class">.<span class="keyword">class</span>, <span class="title">ResourceProperties</span>.<span class="title">class</span> &#125;)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">static</span> <span class="title">class</span> <span class="title">WebMvcAutoConfigurationAdapter</span> <span class="keyword">extends</span> <span class="title">WebMvcConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Log logger = LogFactory</span><br><span class="line">      .getLog(WebMvcConfigurerAdapter<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> ResourceProperties resourceProperties;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>.resourceProperties.isAddMappings()) &#123;</span><br><span class="line">      logger.debug(<span class="string">"Default resource handling disabled"</span>);</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Integer cachePeriod = <span class="keyword">this</span>.resourceProperties.getCachePeriod();</span><br><span class="line">    <span class="keyword">if</span> (!registry.hasMappingForPattern(<span class="string">"/webjars/**"</span>)) &#123;</span><br><span class="line">      customizeResourceHandlerRegistration(</span><br><span class="line">          registry.addResourceHandler(<span class="string">"/webjars/**"</span>)</span><br><span class="line">              .addResourceLocations(</span><br><span class="line">                  <span class="string">"classpath:/META-INF/resources/webjars/"</span>)</span><br><span class="line">          .setCachePeriod(cachePeriod));</span><br><span class="line">    &#125;</span><br><span class="line">    String staticPathPattern = <span class="keyword">this</span>.mvcProperties.getStaticPathPattern();</span><br><span class="line">    <span class="keyword">if</span> (!registry.hasMappingForPattern(staticPathPattern)) &#123;</span><br><span class="line">      customizeResourceHandlerRegistration(</span><br><span class="line">          registry.addResourceHandler(staticPathPattern)</span><br><span class="line">              .addResourceLocations(</span><br><span class="line">                  <span class="keyword">this</span>.resourceProperties.getStaticLocations())</span><br><span class="line">          .setCachePeriod(cachePeriod));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>用户可以自己修改这个默认的静态资源目录，但是不建议，因为很容易引出奇怪的404问题。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;现象&quot;&gt;&lt;a href=&quot;#现象&quot; class=&quot;headerlink&quot; title=&quot;现象&quot;&gt;&lt;/a&gt;现象&lt;/h2&gt;&lt;p&gt;当用户在自己的spring boot main class上面显式使用了&lt;code&gt;@EnableWebMvc&lt;/code&gt;，发现原来的放在
      
    
    </summary>
    
      <category term="技术" scheme="http://hengyunabc.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="spring-boot" scheme="http://hengyunabc.github.io/tags/spring-boot/"/>
    
      <category term="spring" scheme="http://hengyunabc.github.io/tags/spring/"/>
    
      <category term="web" scheme="http://hengyunabc.github.io/tags/web/"/>
    
  </entry>
  
</feed>
