-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
445 lines (267 loc) · 185 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Welcome to XHao's Home</title>
<link href="/atom.xml" rel="self"/>
<link href="https://xhao.io/"/>
<updated>2020-07-23T17:00:12.560Z</updated>
<id>https://xhao.io/</id>
<author>
<name>Xie Hao</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>javaagent的使用</title>
<link href="https://xhao.io/2019/05/javaagent/"/>
<id>https://xhao.io/2019/05/javaagent/</id>
<published>2019-05-04T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>在Java中,java agent和java反射调用一样,都是非常重要的特性,利用这种语言特性,可以做一些有意思的工作。比如说<a href="https://github.com/alibaba/transmittable-thread-local" target="_blank" rel="noopener">线程间传递threadlocal</a>,又或者<a href="https://github.com/alibaba/jvm-sandbox" target="_blank" rel="noopener">运行时AOP</a>。除了功能以外,另一点也很重要:java agent是使用java语言开发。对于java程序员来讲,门槛并不高。<br><a id="more"></a><br>java agent的原理网上也有很多介绍,主要是利用了<a href="https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/index.html" target="_blank" rel="noopener">JVMTI</a>的一个agent库:<strong>libinstrument</strong>。在使用上,一般有2种方式:</p><ul><li>启动时进行加载:-javaagent:myagent.jar</li><li>或者动态attach</li></ul><p>如果是动态attach的话,<a href="#jattach">一般</a>还需要依赖jdk目录下的<code>/lib/tools.jar</code>,做法是找到java home变量,然后去加载tools.jar。</p><p>针对2种不同场景,java agent的触发点略有不同。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 启动时</span></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">premain</span><span class="params">(String args, Instrumentation instrumentation)</span> </span>{};</span><br><span class="line"><span class="comment">// 动态attach</span></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">agentmain</span><span class="params">(String args, Instrumentation instrumentation)</span> </span>{};</span><br></pre></td></tr></table></figure><p>我们要做的工作就是利用这个<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html" target="_blank" rel="noopener"><code>Instrumentation</code></a>对象上提供的方法。</p><p>比如使用<code>addTransformer</code>,我们可以添加一个修改字节码的对象上去</p><ul><li>当加载一个class文件时,会进行拦截,对字节码做修改。</li><li>还可以在运行期对已加载类的字节码做变更</li></ul><p>因为Java里面的类、对象通常和classloader有关系,不同的classloader可以加载同一个类,所以会对<code>ClassFileTransformer</code>的归属有疑问?实际上呢,java agent总是由AppClassLoader进行加载的,<code>premain</code>和<code>agentmain</code>也都是在AppClassLoader里触发的,但是它修改类的效果可以超出当前的classloader。我的理解是,这个<code>ClassFileTransformer</code>只是输出一堆字节码、二进制文件,并不涉及到类加载的问题,所以2者不冲突。不过要注意的是,如果调用<code>retransformClasses(Class<?>... classes)</code>修改已经加载过的类,这个<code>class</code>对象不要用<code>Class.forname("A")</code>,因为<code>Class</code>对象是在当前AppClassLoader里创建的,所以<code>ClassFileTransformer</code>只会影响AppClassLoader里的<code>A</code>,对于其他ClassLoader加载的<code>A</code>,并不起作用。这个时候需要利用<code>Instrumentation</code>对象,简单点可以先<code>getAllLoadedClasses()</code>获得所有类集合,再通过比较class name的方式进行过滤,然后再retransform。又或者你将<code>Instrumentation</code>对象保存起来,将来需要用到的时候,再拿出来使用等等。花样很多,可以自由组合!</p><p>还有一个需要注意的是<a href="http://openjdk.java.net/jeps/159" target="_blank" rel="noopener">JEP 159: Enhanced Class Redefinition</a>。修改已经加载过的类是有限制条件的,虽然有jep在追踪这个问题,但感觉openjdk对这个需求热情不高,短时间内怕是不会有进展(它不在任何已经规划的发行版feature中)</p><p>另外有一个注意点是关于retransform(还有一个redefineClasses,这个用的倒是不多)。关于它的细节可以自行google,infoq上也有一篇<a href="https://www.infoq.cn/article/javaagent-illustrated" target="_blank" rel="noopener">解释</a>仅供参考。</p><p>我写了一个简单的<a href="https://github.com/XHao/java-agent-test/tree/master/javaagent" target="_blank" rel="noopener">Repo</a>,希望能帮助理解。</p><p>比如现在有1个ClassFileTransformer <code>A</code>,它会给一个类增加新的方法,那么</p><ul><li>如果A是在premain里加入到list里去的,则可以<code>canRetransform=true</code></li><li>如果是在agentmain里,不行!</li></ul><p>可以理解为第一次加载类C的时候,已经进行过A的transform变为C‘;进行A的retransform的时候,出去的还是C‘;这2个C’对比发现并没有破坏jep159里的内容,所以ok;但是agentmain里就不一样了,第一次加载的就是C本身,做完retransform变成C‘,C’对比C是有破坏性的改变的。</p><p>第2个例子,是有1个ClassFileTransformer <code>A</code>,在类C已经被加载过之后,先通过A进行一次retransform,增加一行打印</p><ol><li>如果再进行一次,打印只会有一行,不会有2行</li><li>如果删掉A之后再进行一次retransform,则打印会消失</li></ol><p>这个例子说的其实是,每次retransform进来的代码都是会回到原始的字节码,在原始字节码上进行修改。</p><p>针对上面这个例子扩展一下,如果有2个ClassFileTransformer <code>A</code>和<code>B</code>,对类C进行retransform,则A和B的改动都会保留!说明一个transformer拿到的是上一个transformer修改之后的代码。所以前一个例子也能说通了,因为只有一个transformer。</p><p>除了这个字节码这个功能经常被提及之外,<code>Instrumentation</code>还有其他一些我觉得不错的功能</p><ul><li>获取所有已经加载过的类</li><li>获取所有已经初始化过的类(执行过 clinit 方法,是上面的一个子集)</li><li>将某个jar加入到bootstrap classpath里作为高优先级被bootstrapClassloader加载</li><li>将某个jar加入到classpath里供AppClassloader去加载</li></ul><hr><p><a name="jattach" href="https://github.com/apangin/jattach" target="_blank" rel="noopener">apangin/jattach</a>可以不依赖jdk完成load agent功能</p>]]></content>
<summary type="html">
<p>在Java中,java agent和java反射调用一样,都是非常重要的特性,利用这种语言特性,可以做一些有意思的工作。比如说<a href="https://github.com/alibaba/transmittable-thread-local" target="_blank" rel="noopener">线程间传递threadlocal</a>,又或者<a href="https://github.com/alibaba/jvm-sandbox" target="_blank" rel="noopener">运行时AOP</a>。除了功能以外,另一点也很重要:java agent是使用java语言开发。对于java程序员来讲,门槛并不高。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>getaddrinfo探秘</title>
<link href="https://xhao.io/2018/09/getaddrinfo/"/>
<id>https://xhao.io/2018/09/getaddrinfo/</id>
<published>2018-09-26T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>在现在流行的服务化调用场景中,为了避免服务的单点问题,会将服务部署成一个集群,然后利用服务注册中心来解决服务发现问题。</p><p>为了解决服务注册中心的单点问题,有时候会选择古老的dns轮询。</p><p>通过将一个域名关联多条A记录,利用域名服务器每次的不同返回,达到软负载的效果。</p><p>关于jdk的dns解析,网上有很多文章介绍,有对的地方也有错的。<br><a id="more"></a><br>首先看看常用的jdk方法</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// netty Bootstrap Line 97</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ChannelFuture <span class="title">connect</span><span class="params">(String inetHost, <span class="keyword">int</span> inetPort)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> connect(<span class="keyword">new</span> InetSocketAddress(inetHost, inetPort));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码入参是hostname,获得socket连接,在<code>InetSocketAddress</code>中自动完成了dns的工作——<code>InetAddress.getByName(hostname)</code></p><p>jdk中dns查询分2步完成,首先是检查addressCache;如果没有发现,则触发一次网络dns查询。</p><p><code>InetAddressCachePolicy</code>是address的缓存,默认是30s的缓存时间,可以通过<code>networkaddress.cache.ttl</code>来修改。这个cache有这样几个特点:</p><ol><li>如果你通过security property的方式设了值,则在运行期不可以修改</li><li>如果运行期修改ttl,不能比以前小</li></ol><p>从代码上猜测,主要是因为dns查询本身损耗性能,加入这种安全限制,能避免三方库随意设置,导致应用出现瓶颈。</p><p>如果想在运行期自由控制dns缓存时间,就只能通过反射这种方式了</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span></span>{</span><br><span class="line"></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 class="keyword">throws</span> Exception</span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Field f = sun.net.InetAddressCachePolicy.class.getDeclaredField("cachePolicy");</span><br><span class="line"> f.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">int</span> cache = (<span class="keyword">int</span>) f.get(sun.net.InetAddressCachePolicy<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> System.err.println(<span class="string">"before ttl is "</span>+cache);</span><br><span class="line"> f.set(sun.net.InetAddressCachePolicy<span class="class">.<span class="keyword">class</span>, 0)</span>;</span><br><span class="line"> cache = (<span class="keyword">int</span>) f.get(sun.net.InetAddressCachePolicy<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> System.err.println(<span class="string">"after ttl is "</span>+cache);</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span>(<span class="keyword">true</span>){</span><br><span class="line"> Arrays.asList(InetAddress.getAllByName(<span class="string">"xhao.io"</span>)).forEach(System.out::println);</span><br><span class="line"> System.out.println(<span class="string">"-------------"</span>);</span><br><span class="line"> Thread.sleep(<span class="number">5000L</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>ok,当我们将ttl换成0之后,跑跑看,会发现在不同的机器上结果不一样。</p><p>macos high sierra</p><p><img src="/img/dns-mac.jpg" alt="mac dns 查询"></p><p>CentOS Linux release 7.5.1804</p><p><img src="/img/dns-centos.jpg" alt="centos dns 查询"></p><p>因为我们已经调整了dns缓存的时间,所以每次打印的结果都是通过dns查询得来的。</p><p>但是在mac上感觉ip顺序是固定的,而centos上却是无序的;换句话说,在mac上无法实现dns轮询,而centos上还可以。具体原因是什么呢?关于这个,查阅了一些资料,但都说的不清楚,有一个可能性是关于RFC 3484</p><blockquote><p><a href="https://www.systutorials.com/docs/linux/man/3-getaddrinfo/" target="_blank" rel="noopener">getaddrinfo</a>: The sorting function used within getaddrinfo() is defined in RFC 3484; the order can be tweaked for a particular system by editing /etc/gai.conf (available since glibc 2.5).</p></blockquote><p>网上一些关于RFC 3484的说明是:</p><ol><li>给ip排序的目的是想尽量使用ipv6,方便ipv6设备的推广</li><li>应该少依赖dns轮询,这不可靠</li><li>ip排序有一定的规则,会实现与否要看本地库(这只是建议,不强制)</li></ol><p>在mac上并不打算继续追查这个问题了,偷懒认为它是按照标准实现的。但是linux上,可以用trace工具看看<code>getaddrinfo</code>的逻辑。所以找了台机器跑一跑下面的程序:<code>strace ./a.out</code></p><figure class="highlight c"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><netdb.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * === FUNCTION</span></span><br><span class="line"><span class="comment"> * ====================================================================== Name:</span></span><br><span class="line"><span class="comment"> * main Description:</span></span><br><span class="line"><span class="comment"> * =====================================================================================</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">addrinfo</span> <span class="title">hints</span>, *<span class="title">res</span> = <span class="title">NULL</span>;</span></span><br><span class="line"> <span class="keyword">char</span> *host = <span class="string">"xhao.io"</span>;</span><br><span class="line"> <span class="built_in">memset</span>(&hints, <span class="number">0</span>, <span class="keyword">sizeof</span>(hints));</span><br><span class="line"> hints.ai_flags = AI_CANONNAME;</span><br><span class="line"> hints.ai_family = AF_UNSPEC;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> getaddrinfo_error = getaddrinfo(host, <span class="literal">NULL</span>, &hints, &res);</span><br><span class="line"> <span class="keyword">if</span> (getaddrinfo_error)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"fail %s"</span>, gai_strerror(getaddrinfo_error));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> struct addrinfo *iterator = res;</span><br><span class="line"> <span class="keyword">char</span> prev[<span class="number">100</span>], addrstr[<span class="number">100</span>];</span><br><span class="line"> <span class="keyword">void</span> *ptr;</span><br><span class="line"> <span class="keyword">while</span> (iterator != <span class="literal">NULL</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (iterator->ai_family == AF_INET)</span><br><span class="line"> { <span class="comment">/* AF_INET */</span></span><br><span class="line"> ptr = &((struct sockaddr_in *)iterator->ai_addr)->sin_addr;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> ptr = &((struct sockaddr_in6 *)iterator->ai_addr)->sin6_addr;</span><br><span class="line"> }</span><br><span class="line"> inet_ntop(iterator->ai_family, ptr, addrstr, <span class="number">100</span>);</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(prev, addrstr) != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Ipv%d is %s\n"</span>, iterator->ai_family == AF_INET ? <span class="number">4</span> : <span class="number">6</span>,</span><br><span class="line"> addrstr);</span><br><span class="line"> <span class="built_in">strncpy</span>(prev, addrstr, <span class="number">100</span>);</span><br><span class="line"> }</span><br><span class="line"> iterator = iterator->ai_next;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> EXIT_SUCCESS;</span><br><span class="line">} <span class="comment">/* ---------- end of function main ---------- */</span></span><br></pre></td></tr></table></figure><p>我们会发现结果和jdk中的结果相同,基本可以判断并没有之前所说的排序过程。</p><p>同时我们还发现,jdk是调用了系统命令来查询dns,这和我们在jdk源码中看到的差不多(jdk这部分代码有一大坨,看起来只是在整理和排序)</p><p>我们还发现getaddrinfo是一个很重的命令,因为它每次都会去发请求到dns服务器上,并没有任何缓存。这应该就是jdk自己做了缓存的原因,毕竟每次都查询dns服务器,对性能是有影响的。</p><p>关于java dns相关的,大概就是这些内容了。总之dns轮询不一定可靠,需要慎重对待。</p>]]></content>
<summary type="html">
<p>在现在流行的服务化调用场景中,为了避免服务的单点问题,会将服务部署成一个集群,然后利用服务注册中心来解决服务发现问题。</p>
<p>为了解决服务注册中心的单点问题,有时候会选择古老的dns轮询。</p>
<p>通过将一个域名关联多条A记录,利用域名服务器每次的不同返回,达到软负载的效果。</p>
<p>关于jdk的dns解析,网上有很多文章介绍,有对的地方也有错的。<br>
</summary>
<category term="linux" scheme="https://xhao.io/tags/linux/"/>
</entry>
<entry>
<title>一次StringIntern和ParNew GC</title>
<link href="https://xhao.io/2018/06/stringintern/"/>
<id>https://xhao.io/2018/06/stringintern/</id>
<published>2018-06-17T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>前段时间,同事遇到Java的GC问题,现象表现为:gc时间越来越长(也包括young gc);触发一次full gc之后,会变得好点,但是这次full gc长的不能忍受。他们使用的jdk 8,应用内存8G,尝试调整了很多参数,用了ParNew算法,也试过G1算法,都没有太大好转。困扰很久之后找我帮忙看一下,所以也就有了这篇文章^_^</p><p>GC对于我们来说相对黑盒,尤其是young gc的问题,幸运的是这次解决非常快且没依赖Google。对我而言,主要是解决问题过程中的思路,值得记录下来。至于gc的根本原因,事后我找到了<a href="http://lovestblog.cn/blog/2016/11/06/string-intern/" target="_blank" rel="noopener">笨神的博客</a><br><a id="more"></a></p><h3 id="先简单分析分析"><a href="#先简单分析分析" class="headerlink" title="先简单分析分析"></a>先简单分析分析</h3><p>从gc的现象来看,可以有一些简单的分析:</p><ul><li>程序跑了很久之后,开始young gc变长、full gc变长</li><li>感觉是每次full gc完成之后,young gc性能会短暂变好</li><li>full gc自身也很慢</li><li>full gc之后,会回收很多内存,有一个断崖式地下降,感觉有大量内存并不是程序所需要的</li><li>young gc问题应该和old区相关</li></ul><p>结合这些分析,第一反应是:进行root扫描的时候有点慢。由于并不熟悉业务代码,所以很难说是不是由于业务代码写的不好导致。但不管怎么说,我还是打算先看看gc日志。</p><h3 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h3><p>拿到gc的日志之后,发现日志太多了,一行行看是不现实的,必须要有目的性才行。由于CMS的日志每个阶段都挺详细的,可以重点关注。我又回到开始的分析上去,既然感觉full gc之后,ParNew GC会变快,那么是不是每次都这样呢?我开始先过滤full gc触发的时间点,得到一个重要的信息:程序刚开始的时候,full gc前后ParNew GC没有太大时间上的区别,也就是说程序一开始是正常的。我继续往下翻,直到找到full gc成为分水岭————前后的gc时间显著不一样。</p><p>我在2个full gc周围进行对比,发现了一些异常:在CMS的Final Remark阶段,出现了一个<code>scrub string table</code>的项</p><ol><li>有时候很短<br><img src="/img/stringintern-1.png"></li><li>有时候很长<br><img src="/img/stringintern-2.png"></li></ol><p>由于这个阶段是STW的,换句话说<code>scrub string table</code>的长短直接影响了gc暂停的时间。</p><p>看到<code>String Table</code>就很开心了,因为它和jdk的<code>String.intern</code>方法是有关系的,而<code>intern</code>方法在很多有关性能的博客里都有提到,是一个有争议的动作(有利有弊)。虽然不知道和gc有多大关系,但是对于我们解决问题,有了清晰的方向:<strong>优先找到谁在使用这个方法</strong>。因为是服务器程序,所以不可避免会使用Jackson序列化,序列化通常都是和String打交道的,所以我第一时间去看了Jackson的代码,果然默认Jackson默认是使用intern方法的。本着“在不懂原理的情况下,优先尝试的原则”,我们紧急发了一个版本,通过API关闭了Jackson的这项功能,然后在线上灰度测试。</p><p>结果很不错:不仅应用占用的内存变少了,而且gc变得非常的快速且稳定————问题顺利解决。</p><h3 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h3><p>到这里,本文就结束了,至于后续分析产生的根本原因,这些都不是本文的重点。总结一下:这次的case并不算复杂,解决的时间也很短,但却具有代表性。我觉得解决问题就是:首先要尽量“从现象中分析出”大致的方向,“通过技术手段”找到怀疑的点,“快速尝试”(主要是我们掌握的知识有限,有时候碰碰运气效果会更好)来解决问题,最后再辅以“分析根因和总结”就好了。</p>]]></content>
<summary type="html">
<p>前段时间,同事遇到Java的GC问题,现象表现为:gc时间越来越长(也包括young gc);触发一次full gc之后,会变得好点,但是这次full gc长的不能忍受。他们使用的jdk 8,应用内存8G,尝试调整了很多参数,用了ParNew算法,也试过G1算法,都没有太大好转。困扰很久之后找我帮忙看一下,所以也就有了这篇文章^_^</p>
<p>GC对于我们来说相对黑盒,尤其是young gc的问题,幸运的是这次解决非常快且没依赖Google。对我而言,主要是解决问题过程中的思路,值得记录下来。至于gc的根本原因,事后我找到了<a href="http://lovestblog.cn/blog/2016/11/06/string-intern/" target="_blank" rel="noopener">笨神的博客</a><br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="performance" scheme="https://xhao.io/tags/performance/"/>
</entry>
<entry>
<title>Hotspot的safe point</title>
<link href="https://xhao.io/2018/03/safepoint-2/"/>
<id>https://xhao.io/2018/03/safepoint-2/</id>
<published>2018-03-24T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>在<a href="/2018/02/safepoint-1">上一篇</a>中已经提到了safe point,必须强调这是一个很重要的概念,不仅对Jvm,也对所有运行在jvm上的Java应用。从Jvm的角度来看,到达safe point之后,Jvm会开始做一些有意义的事(比如gc);从应用的角度来看,达到safe point意味着工作线程会产生停顿,即常说的“stop the world”。当下所有Jvm的具体实现都有这样的概念(比如Hotspot、Zing),了解一点相关的知识对我们的编程以及调优都很有帮助。<br><a id="more"></a></p><h2 id="safe-point的定义"><a href="#safe-point的定义" class="headerlink" title="safe point的定义"></a>safe point的定义</h2><p>在JavaOne大会上,<a href="#ps1">Azul</a>的一位工程师是这样描述safe point相关概念的:</p><blockquote><p>safe point<br>A known point in execution where state is known, can be examined, and updated.</p></blockquote><blockquote><p>safe point operation<br>an operation that can take place when application threads are at a safe point</p></blockquote><blockquote><p>global safe point and local safe point<br>difference: all threads</p></blockquote><blockquote><p>why do we have safe points<br>operations that are atomic to all application threads</p></blockquote><p>从事jvm性能研究的<a href="#ps2">Nitsan</a>说的更具体一点</p><blockquote><p>A safepoint is a range of execution where the state of the executing thread is well described. Mutator threads are threads which manipulate the JVM heap (all your Java Threads are mutators. Non-Java threads may also be regarded as mutators when they call into JVM APIs which interact with the heap).</p></blockquote><blockquote><p>At a safepoint the mutator thread is at a known and well defined point in it’s interaction with the heap. This means that all the references on the stack are mapped (at known locations) and the JVM can account for all of them. As long as the thread remains at a safepoint we can safely manipulate the heap + stack such that the thread’s view of the world remains consistent when it leaves the safepoint.</p></blockquote><h3 id="我觉得…"><a href="#我觉得…" class="headerlink" title="我觉得…"></a>我觉得…</h3><p>safe point是从jvm的角度来看的“安全”:</p><ul><li>Jvm对进程能够完全的“掌控”,比如堆内存、调用栈、寄存器等;</li><li>所有操作java heap的java线程以及<strong>调用java API的非java线程</strong>都需要safe point;</li><li>进入和离开safe point,程序应该是一致的,所以线程会被锁住;</li><li>Jvm会做影响所有线程的事,比如回收内存,所以Jvm的实现一定有global safe point;</li><li>global safe point会STW,local safe point只影响部分threads;</li><li>一些gc并发算法,threads会进入safe point,虽然规范里没有说一定是global safe point,不过Hotspot当前只有global safe point,所以stop threads = STW;对所有的jvm实现而言,full gc一定是进入global safe point</li><li>safe point并非只是gc时候才触发,实际上触发点挺多的,不过大部分都不需要担心</li><li>需要担心的比如gc,比如<a href="https://blog.csdn.net/hsuxu/article/details/9472381" target="_blank" rel="noopener">偏向锁问题</a>,比如JIT的逆优化(代码逆优化时,会对性能产生一些小而短暂的影响),比如java profile工具(它们通常会通过调用jvmti导致触发safe point)……<code>--XX:+TraceSafepointCleanupTime</code>可以帮助查看safe point的细节</li><li><code>-XX:+PrintGCApplicationStoppedTime</code>和<code>-XX:+PrintGCApplicationConcurrentTime</code>这2个参数我认为java应用都应该使用</li></ul><h2 id="如何进入safe-point"><a href="#如何进入safe-point" class="headerlink" title="如何进入safe point"></a>如何进入safe point</h2><p>这个我建议看看<a href="http://psy-lob-saw.blogspot.com/2015/12/safepoints.html" target="_blank" rel="noopener">Bringing a Java Thread to a Safepoint章节</a></p><p>摘录一下就是:</p><ul><li>Between any 2 bytecodes while running in the interpreter (effectively)</li><li>On ‘non-counted’ loop back edge in C1/C2 compiled code</li><li>Method entry/exit (entry for Zing, exit for OpenJDK) in C1/C2 compiled code. Note that the compiler will remove these safepoint polls when methods are inlined.</li><li>On Oracle/OpenJDK a blind TEST of an address on a special memory page is issued</li></ul><h3 id="换句话说……"><a href="#换句话说……" class="headerlink" title="换句话说……"></a>换句话说……</h3><p>不是每一行java指令后面都会有<code>{poll}</code>的,如果某一刻Jvm发起了global safe point,大部分线程都进入safe point(block),而某个线程迟迟无法进入……尴尬的很,此时的jvm就是不工作状态,包括jstack、kill等命令都是无效的</p><p>对于一个线程来说,遇到<code>{poll}</code>之前所要执行的指令是不可预知的,也就是说时间是不可预知的,这段时间我们通常叫做Time To Safe Point(TTSP),这个时间的长短有时候会产生意想不到的影响,可以回头再看看我们在文章开头提到的现象……</p><h2 id="更进一步"><a href="#更进一步" class="headerlink" title="更进一步"></a>更进一步</h2><p>如果我们的java程序遇到了一些很诡异的暂停,可以考虑分析看看safe point,这个时候参数<code>-XX:+PrintSafepointStatistics –XX:PrintSafepointStatisticsCount=X</code>就能派上用场。</p><p>经验上来看,“禁止偏向锁”、“禁止部分代码内联”会对TTSP有所改善,当然这些东西都不能一概而论,具体情况具体分析吧。</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p><a name="ps1"><a href="https://www.youtube.com/watch?v=Y39kllzX1P8" target="_blank" rel="noopener">With GC Solved, What Else Makes a JVM Pause?</a></a></p><p><a name="ps2"><a href="http://psy-lob-saw.blogspot.com/Psy-Lob-Saw" target="_blank" rel="noopener">Nitsan Wakart</a></a></p>]]></content>
<summary type="html">
<p>在<a href="/2018/02/safepoint-1">上一篇</a>中已经提到了safe point,必须强调这是一个很重要的概念,不仅对Jvm,也对所有运行在jvm上的Java应用。从Jvm的角度来看,到达safe point之后,Jvm会开始做一些有意义的事(比如gc);从应用的角度来看,达到safe point意味着工作线程会产生停顿,即常说的“stop the world”。当下所有Jvm的具体实现都有这样的概念(比如Hotspot、Zing),了解一点相关的知识对我们的编程以及调优都很有帮助。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="performance" scheme="https://xhao.io/tags/performance/"/>
</entry>
<entry>
<title>Java中和SafePoint相关的故事</title>
<link href="https://xhao.io/2018/02/safepoint-1/"/>
<id>https://xhao.io/2018/02/safepoint-1/</id>
<published>2018-02-13T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>事情缘起一段<a href="#ps1">性能测试代码</a>,主要是探讨在循环中索引键类型的选择,int vs long 哪个更好?本文和应用层面无关,只是探讨2种类型的迭代在某些场景对性能的影响。希望经过分析之后,能对我们平时的编程带来一些帮助;或者提供一些对safe point的认识。下面开始进入真正的主题……<br><a id="more"></a></p><h4 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h4><p>这个issue的讨论很长,背景知识很多,关键其实是<a href="https://plus.google.com/107269247235043577368" target="_blank" rel="noopener">nitsanw</a>的贡献(nitsanw对jvm、performance都有很深的功力,他的博客也值得推荐👍):</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><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><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@State</span>(Scope.Thread)</span><br><span class="line"><span class="meta">@BenchmarkMode</span>(Mode.AverageTime)</span><br><span class="line"><span class="meta">@OutputTimeUnit</span>(TimeUnit.NANOSECONDS)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ArrayWrapperInterfaceBenchmark</span> </span>{</span><br><span class="line"> <span class="meta">@Param</span>({ <span class="string">"100"</span>, <span class="string">"1000"</span>, <span class="string">"10000"</span> })</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span> size;</span><br><span class="line"> <span class="keyword">private</span> DataSet datasetA;</span><br><span class="line"> <span class="keyword">private</span> DataSet datasetB;</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> <span class="class"><span class="keyword">class</span> <span class="title">DataSet</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span>[] data;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DataSet</span><span class="params">(DataSet ds)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.data = Arrays.copyOf(ds.data, ds.data.length);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DataSet</span><span class="params">(<span class="keyword">int</span> size)</span> </span>{</span><br><span class="line"> Random r = <span class="keyword">new</span> Random();</span><br><span class="line"> data = <span class="keyword">new</span> <span class="keyword">int</span>[size];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < size; ++i) {</span><br><span class="line"> data[i] = r.nextInt();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">intSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> data.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">intGet</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> data[index];</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">intSet</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> v)</span> </span>{</span><br><span class="line"> data[index] = v;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">long</span> <span class="title">longSize</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> data.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">longGet</span><span class="params">(<span class="keyword">long</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> data[(<span class="keyword">int</span>) index];</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">longSet</span><span class="params">(<span class="keyword">long</span> index, <span class="keyword">int</span> v)</span> </span>{</span><br><span class="line"> data[(<span class="keyword">int</span>) index] = v;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Setup</span>(Level.Trial)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setup</span><span class="params">()</span> </span>{</span><br><span class="line"> datasetA = <span class="keyword">new</span> DataSet(size);</span><br><span class="line"> datasetB = <span class="keyword">new</span> DataSet(datasetA);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">sumInt</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < datasetA.intSize(); ++index) {</span><br><span class="line"> sum += datasetA.intGet(index);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">sumLong</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">long</span> index = <span class="number">0</span>; index < datasetA.longSize(); ++index) {</span><br><span class="line"> sum += datasetA.longGet(index);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equalsInt</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < datasetA.intSize(); ++index) {</span><br><span class="line"> <span class="keyword">if</span>(datasetA.intGet(index) != datasetB.intGet(index))</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equalsLong</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">long</span> index = <span class="number">0</span>; index < datasetA.longSize(); ++index) {</span><br><span class="line"> <span class="keyword">if</span>(datasetA.longGet(index) != datasetB.longGet(index))</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fillInt</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < datasetA.intSize(); ++index) {</span><br><span class="line"> datasetA.intSet(index, size);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fillLong</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">long</span> index = <span class="number">0</span>; index < datasetA.longSize(); ++index) {</span><br><span class="line"> datasetA.longSet(index, size);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">copyInt</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < datasetA.intSize(); ++index) {</span><br><span class="line"> datasetA.intSet(index, datasetB.intGet(index));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Benchmark</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">copyLong</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">long</span> index = <span class="number">0</span>; index < datasetA.longSize(); ++index) {</span><br><span class="line"> datasetA.longSet(index, datasetB.longGet(index));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>首先解释一下这段代码的含义:</p><ol><li>主要是比较在循环中,索引类型是int和long,分别对循环调用产生的性能影响,所以每个类型的调用都是一模一样</li><li>循环体内的实现都很简单(加、复制、比较、赋值),这些方法调用会被JIT优化成内联函数</li><li>分别测试了100、1000、10000次循环的比较,取的是单次调用的平均时间</li><li>循环使用的是for循环,后面也会介绍while循环也有类似的现象</li></ol><p>这段代码写起来很简单,不过有几点需要注意:</p><ol><li>必须使用<a href="http://openjdk.java.net/projects/code-tools/jmh" target="_blank" rel="noopener">JMH</a>性能测试框架:<ul><li>对Method级别的测试,jmh精度可以达到微秒级;</li><li>jmh可以对局部代码进行性能测试,非常可靠</li><li>不过即便使用jmh,在类似微秒级的观察上,也会受到系统的影响,这个要小心。通常我们还会查看bytecode,甚至使用<code>-XX:+PrintCompilation</code>来检查不同的方法是由那一层compiler来处理的</li></ul></li><li>合理地设计测试用例,比如BlackHole的consume是一个比较重的方法,会影响JIT的一些优化,在这里就需要考虑到</li><li>需要选择好运行环境,比如CPU相对空闲的系统、标注运行jvm版本等</li></ol><h4 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h4><p>我在一台24核56G的centos虚拟机上进行测试,得到了令人惊讶的<a href="/data/perf_out">结果</a>。同样的循环次数,同样的调用逻辑,long的版本比int慢了许多。为什么呢?</p><p>这里的核心就是safe point检查:在当前jvm的实现下,每次迭代结束都会有一个safe point检查,但是在int版本迭代中,JIT优化掉了safe point的调用。这是因为当循环次数有限时,JIT会认为没有必要每一次迭代都增加一个safe point检查点,而等整个循环结束,才做一次safe point检查,利于提升性能;但这样就导致JIT在有限循环中会删去safe point,而有限循环(counted loop)是指索引(index)是int类型的for循环。结果在这种微妙级别的benchmark上,会出现long和int的性能区别。(这里要非常小心循环体内的调用开销,因为safe point检查是非常非常轻量级的,一旦真实的调用变得开销很大,我们将再也看不出任何的区别。)</p><p>nitsanw还为我们总结了一些常见的counted loop示例,见下面的代码</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. counted = reps is int/short/byte</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < reps; i++) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Not counted</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < int_reps; i+=<span class="number">2</span>) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Not counted</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">long</span> l = <span class="number">0</span>; l < int_reps; i++) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. Should be counted, but treated as uncounted</span></span><br><span class="line"><span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">while</span> (++i < reps) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5. Should be counted, but treated as uncounted</span></span><br><span class="line"><span class="keyword">while</span> (i++ < reps) {</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 6. Should be counted, and is!</span></span><br><span class="line"><span class="keyword">while</span> (i < reps) {</span><br><span class="line"> i++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h4><p>其实这篇文章只是一个引子,引出我们对safe point的一些简单认识,知道它是到处存在、影响性能且是可能被优化的,在下一篇文章中我才会重点讲述一下safe point。</p><h3 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h3><p><a name="ps1"><a href="https://github.com/netty/netty/pull/3969" target="_blank" rel="noopener">循环中使用long还是int作为index的讨论</a></a></p>]]></content>
<summary type="html">
<p>事情缘起一段<a href="#ps1">性能测试代码</a>,主要是探讨在循环中索引键类型的选择,int vs long 哪个更好?本文和应用层面无关,只是探讨2种类型的迭代在某些场景对性能的影响。希望经过分析之后,能对我们平时的编程带来一些帮助;或者提供一些对safe point的认识。下面开始进入真正的主题……<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="performance" scheme="https://xhao.io/tags/performance/"/>
</entry>
<entry>
<title>sneakyThrow和template</title>
<link href="https://xhao.io/2017/11/sneakyThrow/"/>
<id>https://xhao.io/2017/11/sneakyThrow/</id>
<published>2017-11-11T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>这次是从java的受检异常说起,一直会分析到泛型、lambda以及java8类型推断的一些问题<br><a id="more"></a><br>每一个写java程序的朋友都知道,java语言规范对异常有一个分类:受检异常和非受检异常;通常在开发中,我们只需要注意:受检异常需要<code>try catch</code>来处理,需要在方法参数上通过<code>throws XXXException</code>来标示。对于这2种异常的区分,可以<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.1.1" target="_blank" rel="noopener">参考</a></p><figure class="highlight armasm"><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="symbol">The</span> unchecked exception classes are the run-time exception classes <span class="keyword">and </span>the error classes.</span><br><span class="line"></span><br><span class="line"><span class="symbol">The</span> checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable <span class="keyword">and </span>all <span class="keyword">its </span><span class="keyword">subclasses </span>other than RuntimeException <span class="keyword">and </span><span class="keyword">its </span><span class="keyword">subclasses </span><span class="keyword">and </span>Error <span class="keyword">and </span><span class="keyword">its </span><span class="keyword">subclasses.</span></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><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"><span class="keyword">import</span> java.io.IOException;</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">ExceptionTest</span> </span>{</span><br><span class="line"></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>{</span><br><span class="line"> <span class="keyword">new</span> ExceptionTest().test(); <span class="comment">// 非受检异常。# line 6</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IOException(); <span class="comment">// 受检异常</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> close(); <span class="comment">// 受检异常 => 非受检异常 # line 15</span></span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> ExceptionTest.<NullPointerException> sneakyThrow0(e); <span class="comment">// # line 17</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <T extends Throwable> <span class="function"><span class="keyword">void</span> <span class="title">sneakyThrow0</span><span class="params">(Throwable t)</span> <span class="keyword">throws</span> T </span>{ <span class="comment">// #line 22</span></span><br><span class="line"> <span class="keyword">throw</span> (T) t;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>初次看到会有几个疑问:</p><ol><li>编译能通过?能运行?</li><li>#line 6抛出的异常是什么?</li><li>#line 17会不会出现cast异常?</li></ol><p>实验之后,我们发现编译是没有问题的,运行会抛出<code>java.io.IOException</code>异常,表明在#line15成功的将”受检异常变成了非受检异常”。</p><p>我们逐个分析一下:</p><ul><li>首先line6这里不再需要<code>try catch</code>,尽管最终抛出的异常<code>IOException</code>是一个受检异常。这是因为受检异常强制检查的规范是java语言规范并不是jvm的规范,所以它的检查是在编译阶段,也就是由javac的实现来<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.2" target="_blank" rel="noopener">决定</a>。</li><li>将受检异常变成非受检异常,是通过<code>sneakyThrow0</code>方法。这个方法申明抛出的异常是T(<T extends Throwable>),而在#line 17定义的T是<code>NullPointerException</code>,所以ok,这是一个非受检异常,我们不需要<code>try catch</code>。同样,我们在#line 23是直接<code>throw e</code>,所以异常是没有经过任何处理的,原样输出即<code>IOException</code>。至于<code>(T) t</code>这里的T虽然和t的异常类型无法匹配,但是没关系,因为根本不会触发这一层转化……泛型在编译完成之后就会被擦除。</li><li>经过上面的分析,我们可以知道#line17的异常可以是任意的,只要是满足<T extends Throwable>。</li></ul><p>其实这里涉及如下几个问题:异常类型检查在编译阶段、泛型中的有界类型、泛型检查在编译阶段。我们都知道java8提供了完备的类型推断和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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.IOException;</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">ExceptionTest</span> </span>{</span><br><span class="line"></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>{</span><br><span class="line"> <span class="keyword">new</span> ExceptionTest().test2();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IOException();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> close();</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> ExceptionTest.<NullPointerException> sneakyThrow0(e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@SuppressWarnings</span>(<span class="string">"unchecked"</span>)</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <T extends Throwable> <span class="function"><span class="keyword">void</span> <span class="title">sneakyThrow0</span><span class="params">(Throwable t)</span> <span class="keyword">throws</span> T </span>{</span><br><span class="line"> <span class="keyword">throw</span> (T) t;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <T extends Throwable> <span class="function"><span class="keyword">void</span> <span class="title">invoke</span><span class="params">(Action<T> action)</span> <span class="keyword">throws</span> T </span>{</span><br><span class="line"> action.doIt(); <span class="comment">// throws T</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">test1</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> invoke(() -> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Exception();</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">test2</span><span class="params">()</span> </span>{</span><br><span class="line"> invoke(() -> {</span><br><span class="line"> });</span><br><span class="line"> invoke(() -> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException();</span><br><span class="line"> });</span><br><span class="line"> invoke(() -> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> Error();</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Action</span><<span class="title">T</span> <span class="keyword">extends</span> <span class="title">Throwable</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">doIt</span><span class="params">()</span> <span class="keyword">throws</span> T</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们定义了一个接口<code>Action</code>,它和前面的<code>sneakyThrow0</code>一样,只不过是一个<code>FunctionInterface</code>。这里除了上面说到的几个问题之外,多引入了类型推断的话题。因为lambda表达式需要编译器推断出正确的类型。</p><p>这段代码能够编译通过,说明不管throw的是受检异常还是非受检异常,javac都能正确地推断出来,根据就是<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.4" target="_blank" rel="noopener">这里</a>。</p>]]></content>
<summary type="html">
<p>这次是从java的受检异常说起,一直会分析到泛型、lambda以及java8类型推断的一些问题<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>使用java profile</title>
<link href="https://xhao.io/2017/11/jvm-profile/"/>
<id>https://xhao.io/2017/11/jvm-profile/</id>
<published>2017-11-07T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>这次是学习使用一个java sample工具,顺便了解一些linux、jvm的知识<br><a id="more"></a><br>浏览github的时候,发现了一个不错的java工具——<a href="https://github.com/jvm-profiling-tools/async-profiler" target="_blank" rel="noopener">async-profile</a>,这和平时使用的JMC差不多,不过它是开源的,对于我们不算是黑盒。时间充裕的话,还可以看看它的源码,因为它使用了Hotspot的一些回调,顺道还能加深对Hotspot的了解。本文会从4个方面总结一下心得体会。</p><h2 id="async-profile的使用"><a href="#async-profile的使用" class="headerlink" title="async profile的使用"></a>async profile的使用</h2><p>工具的使用还是很简单的,官方也有很详细的<a href="https://github.com/jvm-profiling-tools/async-profiler#profiler-options" target="_blank" rel="noopener">说明</a>(源码下载后记得<code>make</code>编译一下)</p><figure class="highlight sh"><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">$ jps</span><br><span class="line">9234 Jps</span><br><span class="line">8983 Computey</span><br><span class="line">$ ./profiler.sh start 8983</span><br></pre></td></tr></table></figure><p>当你<code>./profiler.sh stop 8983</code>的时候,会将之前sample的数据打印在控制台,不过通常数据都很多,我会选择<code>-f /tmp/sample.txt</code>用文件保存起来,方便使用</p><p>除此之外,<code>-e event</code>也比较有用,不过使用之前需要<code>list pid</code>来看看你的机器上支持哪些events,比如我的机器上返回的是</p><figure class="highlight sh"><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">Perf events:</span><br><span class="line"> cpu</span><br><span class="line">Java events:</span><br><span class="line"> alloc</span><br><span class="line"> lock</span><br></pre></td></tr></table></figure><p>可以看到在我的mac上,支持的类型比较少,主要是perf_events是linux上的工具。</p><p>我在centos7上看到这样的结果</p><figure class="highlight sh"><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">Perf events:</span><br><span class="line"> cpu</span><br><span class="line"> page-faults</span><br><span class="line"> context-switches</span><br><span class="line"> cycles</span><br><span class="line"> instructions</span><br><span class="line"> cache-references</span><br><span class="line"> cache-misses</span><br><span class="line"> branches</span><br><span class="line"> branch-misses</span><br><span class="line"> bus-cycles</span><br><span class="line"> L1-dcache-load-misses</span><br><span class="line"> LLC-load-misses</span><br><span class="line"> dTLB-load-misses</span><br><span class="line">Java events:</span><br><span class="line"> alloc</span><br><span class="line"> lock</span><br></pre></td></tr></table></figure><p><code>-t pid</code>也很常用,它是根据线程来sample的,这样对我们后续的分析会带来好处</p><h3 id="限制条件"><a href="#限制条件" class="headerlink" title="限制条件"></a>限制条件</h3><ol><li><p>工具最大的限制大都和<code>perf</code><a href="https://github.com/jvm-profiling-tools/async-profiler#restrictionslimitations" target="_blank" rel="noopener">相关</a> </p></li><li><p>不能打开<code>-XX:+DisableAttachMechanism</code>,因为它也是使用jvm的attach<a href="http://lovestblog.cn/blog/2014/06/18/jvm-attach/" target="_blank" rel="noopener">机制</a></p></li></ol><h2 id="async-profile特点"><a href="#async-profile特点" class="headerlink" title="async profile特点"></a>async profile特点</h2><p>一句话概括,就是很小的额外开销,非常适合于生产环境。cpu profiling使用的是perf_events和java code address的match来追踪;memory allocation使用的是Hotspot上的回调,不需要字节码修改等复杂的技术。</p><h2 id="jvm-safePoint-bias"><a href="#jvm-safePoint-bias" class="headerlink" title="jvm safePoint bias"></a>jvm safePoint bias</h2><p>这里要说的是safePoint和jvm profile的关系</p><p>我看到有一位高手写了2篇文章,算是通俗易懂地解释了其他profile的问题所在</p><p><a href="http://psy-lob-saw.blogspot.ru/2016/02/why-most-sampling-java-profilers-are.html" target="_blank" rel="noopener">why-most-sampling-java-profilers-are</a></p><p><a href="http://psy-lob-saw.blogspot.co.za/2016/06/the-pros-and-cons-of-agct.html" target="_blank" rel="noopener">the-pros-and-cons-of-agct</a></p><h2 id="perf工具"><a href="#perf工具" class="headerlink" title="perf工具"></a>perf工具</h2><p>这是linux内核自带的性能分析工具。从前面2段<code>$ ./profiler.sh list pid</code>结果也能看到,有了perf工具之后,可以追踪到硬件或者内核级别的软件事件。</p><p>在centos上的安装方式<code>sudo yum install perf</code></p><p>举个例子,<code>perf stat</code>命令可以得到一个全局的统计信息,能在第一时间帮助我们分析问题</p><figure class="highlight sh"><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">$ perf <span class="built_in">stat</span> <span class="built_in">echo</span> <span class="variable">$JAVA_HOME</span></span><br><span class="line">/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64</span><br><span class="line"></span><br><span class="line"> Performance counter stats <span class="keyword">for</span> <span class="string">'echo /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64'</span>:</span><br><span class="line"></span><br><span class="line"> 0,505282 task-clock (msec) <span class="comment"># 0,195 CPUs utilized CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO</span></span><br><span class="line"> 1 context-switches <span class="comment"># 0,002 M/sec 进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的</span></span><br><span class="line"> 0 cpu-migrations <span class="comment"># 0,000 K/sec 表示进程运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行</span></span><br><span class="line"> 168 page-faults <span class="comment"># 0,332 M/sec </span></span><br><span class="line"> <not supported> cycles <span class="comment"># 处理器时钟,一条机器指令可能需要多个 cycles </span></span><br><span class="line"> <not supported> instructions <span class="comment"># 机器指令数目 </span></span><br><span class="line"> <not supported> branches <span class="comment"># </span></span><br><span class="line"> <not supported> branch-misses <span class="comment"># </span></span><br><span class="line"></span><br><span class="line"> 0,002588576 seconds time elapsed</span><br></pre></td></tr></table></figure><p>其他的命令包括:<code>perf top</code>用于实时显示当前系统的性能统计信息。该命令主要用来观察整个系统当前的状态,比如可以通过查看该命令的输出来查看当前系统最耗时的内核函数或某个用户进程……等等</p><p>详细去找手册就好</p>]]></content>
<summary type="html">
<p>这次是学习使用一个java sample工具,顺便了解一些linux、jvm的知识<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="performance" scheme="https://xhao.io/tags/performance/"/>
</entry>
<entry>
<title>ubuntu16.04LTS遇到的dns解析问题</title>
<link href="https://xhao.io/2017/03/ubuntu-dns-problem/"/>
<id>https://xhao.io/2017/03/ubuntu-dns-problem/</id>
<published>2017-03-24T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>最近遇到一个ubuntu系统的问题:无法使用dns解析服务。找到原因,居然是和<a href="https://wiki.archlinux.org/index.php/NetworkManager_\(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87\" target="_blank" rel="noopener">NetworkManager</a>)相关<br><a id="more"></a></p><h3 id="我遇到问题时的场景是:"><a href="#我遇到问题时的场景是:" class="headerlink" title="我遇到问题时的场景是:"></a>我遇到问题时的场景是:</h3><ul><li>操作系统ubuntu 16.04LTS</li><li>路由器是梅林系统,使用了shadesocks插件</li><li>运行一段时间后,dns无法正常工作(我的路由器端并没有问题,依然可以google)</li><li>同一时间,我的ubuntu服务器是可以正常访问网络的</li></ul><h3 id="解决问题的方案如下:"><a href="#解决问题的方案如下:" class="headerlink" title="解决问题的方案如下:"></a>解决问题的方案如下:</h3><ul><li><p>sudo vi /etc/NetworkManager/NetworkManager.conf</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">[main]</span><br><span class="line">plugins=ifupdown,keyfile,ofono</span><br><span class="line"><span class="comment">#dns=dnsmasq 注释掉它</span></span><br></pre></td></tr></table></figure></li><li><p>sudo service network-manager restart</p></li></ul><p>重启networkmanager之后,会发现<code>/etc/resolv.conf</code>不再有127.0.1.1这个nameserver,取而代之的是我的路由器地址</p><h3 id="reason"><a href="#reason" class="headerlink" title="reason"></a>reason</h3><p>其实这是从120.4lts之后,新加入的实现,对于vpn的用户来说,稍有不同,<a href="https://stgraber.org/2012/02/24/dns-in-ubuntu-12-04/" target="_blank" rel="noopener">这篇文章做了一个简单地介绍</a></p>]]></content>
<summary type="html">
<p>最近遇到一个ubuntu系统的问题:无法使用dns解析服务。找到原因,居然是和<a href="https://wiki.archlinux.org/index.php/NetworkManager_\(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87\" target="_blank" rel="noopener">NetworkManager</a>)相关<br>
</summary>
<category term="linux" scheme="https://xhao.io/tags/linux/"/>
</entry>
<entry>
<title>mac上fd报错有点奇怪</title>
<link href="https://xhao.io/2017/01/nio-open-file/"/>
<id>https://xhao.io/2017/01/nio-open-file/</id>
<published>2017-01-10T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>最近在使用rabbitmq java client时,有小伙伴在mac上写了一段代码,发现当创建一个连接到rabbitmq-server时就会报错<code>bad file descriptor</code>,虽然在linux服务器上没有出现这个问题,但为了安全,还是花了点时间进行调查。<br><a id="more"></a><br>从现象来看是rabbitmq连接的问题,我们赶紧检查rabbitmq-server的日志。但结果很尴尬,并没有发现异常……日志里记载的是由于心跳异常,连接被关闭。这让人很困惑,说明tcp连接是成功的,但之后很快,客户端就报错了。开始有点不信,特意看了下连接数,确实发现了一个tcp连接由established变成closed,通过tcpdump发现tcp四次挥手的时候也并没有发送任何奇怪的数据,那么可以确认tcp连接没有问题,是其他原因造成了客户端的报错。</p><p>重新看一下java里的堆栈消息:</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">Caused by: java.net.SocketException: Bad file descriptor</span><br><span class="line"> at java.net.SocketOutputStream.socketWrite0(Native Method)</span><br><span class="line"> at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:<span class="number">109</span>)</span><br><span class="line"> at java.net.SocketOutputStream.write(SocketOutputStream.java:<span class="number">153</span>)</span><br><span class="line"> at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:<span class="number">82</span>)</span><br><span class="line"> at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:<span class="number">140</span>)</span><br><span class="line"> at java.io.DataOutputStream.flush(DataOutputStream.java:<span class="number">123</span>)</span><br><span class="line"> at com.rabbitmq.client.impl.SocketFrameHandler.sendHeader(SocketFrameHandler.java:<span class="number">129</span>)</span><br><span class="line"> at com.rabbitmq.client.impl.SocketFrameHandler.sendHeader(SocketFrameHandler.java:<span class="number">134</span>)</span><br><span class="line"> at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:<span class="number">277</span>)</span><br><span class="line"> at com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectionFactory.newConnection(RecoveryAwareAMQConnectionFactory.java:<span class="number">37</span>)</span><br></pre></td></tr></table></figure><p>问题是socket flush的时候发现fd is bad,由此可见应该是和fd相关。</p><p>在运行时,通过lsof命令,我们发现进程占用了很多的fd,它们都是netty的eventloopGroup创建出来的(项目中使用了netty),有一点是明确的,java nio的selector会占用2个PIPE,而且初始化eventloopGroup的时候,netty就会把它们都创建出来了。比如设置n个线程,就会消耗2n+的fd(2n个PIPE+其他)。我又查看了一下ulimit设置,还发现一个奇怪的地方,就是<code>ulimit -Hn</code>虽然显示的<code>unlimited</code>,但实际上超过4096就会报错。这个是写在jdk的native里,当发现是hard limit是“unlimited”的时候,会将它设置成4096的上限(估计是一种保护)。</p><p>以上是发现代码里消耗非常多的fd原因,这里触发错误的情形猜测应该是:socket创建成功,但是fd创建失败,然后flush的时候报错。但按照linux上的正确显示,应该是socket没有创建成功,而不是等到使用fd的时候才报错。用dtruss抓取了os的调用情况,发现这和mac以及timeout相关。</p><p>当socket创建的时候,没有带超时时间(timeout),mac上报错的方式和linux上相同,也是在创建socket的时候失败,报错很明确;但如果带了超时时间(timeout),这种情况下的socket创建是一个异步操作,并不是立即返回,它会等待fd的获取,神奇的是这个地方应该是没有获得fd,但居然没有报错,socket创建成功并且返回。当你使用这个没有完全初始化好的socket时,报错信息就如上文这样。我看到了这部分代码,但目前还没有想明白为什么报错(需要对jdk更多的了解,这个留待以后再来解决吧……)</p>]]></content>
<summary type="html">
<p>最近在使用rabbitmq java client时,有小伙伴在mac上写了一段代码,发现当创建一个连接到rabbitmq-server时就会报错<code>bad file descriptor</code>,虽然在linux服务器上没有出现这个问题,但为了安全,还是花了点时间进行调查。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>hostname相关</title>
<link href="https://xhao.io/2016/09/hostname/"/>
<id>https://xhao.io/2016/09/hostname/</id>
<published>2016-09-10T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>之前有<a href="/2016/04/host-ip">讨论</a>hostname在jdk中是怎么使用的,这回想说的是,在linux机器上,关于hostname相关的一些tips,都是很简单的常识,但了解它们有时候会给我们查问题带来帮助。<br><a id="more"></a></p><h3 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h3><p>nslookup、dig、host都被用来查询域名。但是需要注意的是这3个工具都是向DNS服务器发起请求的,也就是会忽略<code>/etc/hosts</code>下的内容。那么如果想更通用,可以选择<code>getent</code>这个命令(貌似是linux下有效,osx可能换了命令)</p><h3 id="etc-hosts"><a href="#etc-hosts" class="headerlink" title="/etc/hosts"></a>/etc/hosts</h3><p>刚刚我们提到的hosts,是一个用来记录ip和域名的映射关系的文件,linux系统有时候会在向DNS发出域名解析请求之前先查询这个文件,如果能找到对应的记录则直接使用。</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"><span class="comment"># Do not remove the following line, or various programs</span></span><br><span class="line"><span class="comment"># that require network functionality will fail.</span></span><br><span class="line"><span class="comment"># ipv4回环</span></span><br><span class="line">127.0.0.1 localhost.localdomain localhost</span><br><span class="line"><span class="comment"># ipv6回环</span></span><br><span class="line">::1 localhost6.localdomain6 localhost6</span><br></pre></td></tr></table></figure><p>说有时候,是因为linux先查询dns还是file也是可以配置的。这就涉及到后面的几个文件</p><h3 id="首先是-etc-host-conf"><a href="#首先是-etc-host-conf" class="headerlink" title="首先是/etc/host.conf"></a>首先是/etc/host.conf</h3><p>老一点的linux系统就是通过这个配置文件决定查询域名的顺序</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"><span class="comment"># /etc/host.conf</span></span><br><span class="line"><span class="comment"># We have named running, but no NIS (yet)</span></span><br><span class="line">order <span class="built_in">bind</span>,hosts <span class="comment"># bind -> dns</span></span><br><span class="line"><span class="comment"># Allow multiple addrs</span></span><br><span class="line">multi on</span><br><span class="line"><span class="comment"># Guard against spoof attempts</span></span><br><span class="line">nospoof on</span><br><span class="line"><span class="comment"># Trim local domain (not really necessary).</span></span><br><span class="line">trim vbrew.com.</span><br></pre></td></tr></table></figure><p>该文件通常会被一些系统参数覆盖,比如以<code>RESOLV_</code>开头的一些参数</p><h3 id="nsswitch-conf"><a href="#nsswitch-conf" class="headerlink" title="nsswitch.conf"></a>nsswitch.conf</h3><p>后来,GNU standard library 2.x提供了取代host.conf机制的lib,配置文件就是nsswitch.conf</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"><span class="comment"># /etc/nsswitch.conf</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Example configuration of GNU Name Service Switch functionality.</span></span><br><span class="line"><span class="comment"># Information about this file is available in the `libc6-doc' package.</span></span><br><span class="line"></span><br><span class="line">hosts: dns files</span><br><span class="line">networks: files</span><br></pre></td></tr></table></figure><p>这个例子表明:the system to look up hosts first in the Domain Name System, and then /etc/hosts file, if that can’t find them. Network name lookups would be attempted using only the /etc/networks file</p><p>最后要提到的是resolv.conf</p><h3 id="resolv-conf"><a href="#resolv-conf" class="headerlink" title="resolv.conf"></a>resolv.conf</h3><p>很简单,这个文件配置了dns server对应的ip</p><p>至此,dns相关的流程差不多就完了,<b><em>更多的内容可以参考linux相关<a href="http://www.oreilly.com/openbook/linag2/book/ch06.html" target="_blank" rel="noopener">书籍</a></em></b></p>]]></content>
<summary type="html">
<p>之前有<a href="/2016/04/host-ip">讨论</a>hostname在jdk中是怎么使用的,这回想说的是,在linux机器上,关于hostname相关的一些tips,都是很简单的常识,但了解它们有时候会给我们查问题带来帮助。<br>
</summary>
<category term="linux" scheme="https://xhao.io/tags/linux/"/>
</entry>
<entry>
<title>802.11n-wifi信道</title>
<link href="https://xhao.io/2016/06/802-11/"/>
<id>https://xhao.io/2016/06/802-11/</id>
<published>2016-06-17T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>周末下午想玩xbox,结果遇到了囧事,无论如何都连不上自家的wifi……<br><a id="more"></a><br>所谓的连不上,首先是机器根本看不见wifi信号。以前从没遇到过类似问题,我以为是xbox没有收到路由器的ssid广播,通过输入ssid和password的方式,还是不行。我感觉应该是无线路由器设置出了问题。</p><p>登陆tplink之后,我看见选择的信道固定为了13,换成自动之后,一切OK。难道是xbox不支持802.11n的13信道吗?</p><p>带着疑问Google了一下,我擦…原来可用的802.11n的信道划分,全世界不是统一的,天朝可以使用12/13,但北美却只能1-11,而我买的xbox恰好是美版。</p><p>这回还发现了一个不错的命令,可以检查周围的信道使用情况,在mac上测试有效</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="regexp">/System/</span>Library<span class="regexp">/PrivateFrameworks/</span>Apple80211.framework<span class="regexp">/Versions/</span>Current<span class="regexp">/Resources/</span>airport -s</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>周末下午想玩xbox,结果遇到了囧事,无论如何都连不上自家的wifi……<br>
</summary>
<category term="linux" scheme="https://xhao.io/tags/linux/"/>
</entry>
<entry>
<title>java中的getHostname</title>
<link href="https://xhao.io/2016/04/host-ip/"/>
<id>https://xhao.io/2016/04/host-ip/</id>
<published>2016-04-15T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>在linux服务器上,java程序有时会遇到<code>unknown host name</code>的问题。通过解决这些问题,我发现搞清楚jdk获取ip和hostname的方式以及linux机器的真实ip、hostname是关键。</p><p>linux上有很多常用命令会涉及,诸如:hostnamectl、nslookup、dig、ifconfig……下面我们先来看jdk如何获取hostname的<br><a id="more"></a></p><h3 id="通过InetAddress-getLocalHost来获得Address"><a href="#通过InetAddress-getLocalHost来获得Address" class="headerlink" title="通过InetAddress.getLocalHost来获得Address"></a>通过<code>InetAddress.getLocalHost</code>来获得Address</h3><figure class="highlight routeros"><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">Returns the<span class="built_in"> address </span>of the local host. This is achieved by retrieving the name of the host <span class="keyword">from</span> the system, then resolving that name into an InetAddress.</span><br><span class="line"></span><br><span class="line">Note: The resolved<span class="built_in"> address </span>may be cached <span class="keyword">for</span> a short period of time.</span><br></pre></td></tr></table></figure><p>它的实现分3个部分:</p><ol><li>先是<code>impl.getLocalHostName()</code>取得hostname。这是一个native方法,在linux内核上是通过调用内核函数<code>gethostname</code>完成。而hostname是和下面有关的</li></ol><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">[hao.xie@hao-xie-vm ~]$ hostname <span class="comment">#hostname返回当前值</span></span><br><span class="line">hao-xie-vm</span><br><span class="line">[hao.xie@vhao-xie-vm ~]$ more /proc/sys/kernel/hostname <span class="comment">#当前值,或者说修改后生效</span></span><br><span class="line">hao-xie-vm</span><br><span class="line">[hao.xie@hao-xie-vm ~]$ more /etc/sysconfig/network <span class="comment">#这个文件需要重点说下,reboot的时候,系统会读取它,并给hostname赋值,但是在运行过程中修改它不会立即生效,需要配合hostname命令生效</span></span><br><span class="line"><span class="comment"># Created by anaconda</span></span><br></pre></td></tr></table></figure><ol start="2"><li>接着,jdk又去查了缓存的localhost信息(5s有效的cache),如果没有缓存才开始取address</li><li>取Address的方式是<code>InetAddress.getAddressesFromNameService</code>,按字面意思就是向dns来反向查询自己</li></ol><h3 id="InetAddress-getLocalHost-getHostName获取hostname"><a href="#InetAddress-getLocalHost-getHostName获取hostname" class="headerlink" title="InetAddress.getLocalHost.getHostName获取hostname"></a><code>InetAddress.getLocalHost.getHostName</code>获取hostname</h3><figure class="highlight routeros"><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">Gets the host name <span class="keyword">for</span> this<span class="built_in"> IP </span>address.</span><br><span class="line"></span><br><span class="line"><span class="keyword">If</span> this InetAddress was created with a host name, this host name will be remembered <span class="keyword">and</span> returned; otherwise, a reverse name lookup will be performed <span class="keyword">and</span> the result will be returned based on the<span class="built_in"> system </span>configured name lookup service. <span class="keyword">If</span> a lookup of the name<span class="built_in"> service </span>is required, call getCanonicalHostName.</span><br></pre></td></tr></table></figure><p>描述很清楚,就是再通过ip反向来查hostname,原因和上面一样。</p><h3 id="我遇到过的问题"><a href="#我遇到过的问题" class="headerlink" title="我遇到过的问题"></a>我遇到过的问题</h3><p>我之前在服务器上发现hostname返回的是<code>A</code>,但是<code>InetAddress.getLocalHost.getHostName</code>返回异常<code>unknown host name : host name B</code>。原因就是2次dns的结果差异造成<code>hostname A -> ip1;ip1 -> B</code>这种尴尬的事情。这个时候就需要nslookup工具来检查,最后在dns服务器上进行修改了。</p><h3 id="如何在java代码中获取本机ip(非127-0-0-1)"><a href="#如何在java代码中获取本机ip(非127-0-0-1)" class="headerlink" title="如何在java代码中获取本机ip(非127.0.0.1)"></a>如何在java代码中获取本机ip(非127.0.0.1)</h3><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> InetAddress <span class="title">getLocalHostLANAddress</span><span class="params">()</span> <span class="keyword">throws</span> UnknownHostException, SocketException </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">InetAddress candidateAddress = <span class="keyword">null</span>;</span><br><span class="line"><span class="comment">// Iterate all NICs (network interface cards)...</span></span><br><span class="line">Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();</span><br><span class="line"><span class="keyword">while</span> (ifaces.hasMoreElements()) {</span><br><span class="line">NetworkInterface iface = (NetworkInterface) ifaces.nextElement();</span><br><span class="line"><span class="keyword">for</span> (<span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>)</span><br><span class="line">Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) {</span><br><span class="line">InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();</span><br><span class="line"><span class="keyword">if</span> (!inetAddr.isLoopbackAddress()) {</span><br><span class="line"><span class="keyword">if</span> (inetAddr.isSiteLocalAddress()) {</span><br><span class="line"><span class="keyword">return</span> inetAddr;</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (candidateAddress == <span class="keyword">null</span>) {</span><br><span class="line">candidateAddress = inetAddr;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (candidateAddress != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">return</span> candidateAddress;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// At this point, we did not find a non-loopback address.</span></span><br><span class="line"><span class="comment">// Fall back to returning whatever InetAddress.getLocalHost()</span></span><br><span class="line"><span class="comment">// returns...</span></span><br><span class="line">InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();</span><br><span class="line"><span class="keyword">if</span> (jdkSuppliedAddress == <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> UnknownHostException(<span class="string">"The JDK InetAddress.getLocalHost() method unexpectedly returned null."</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> jdkSuppliedAddress;</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">UnknownHostException unknownHostException = <span class="keyword">new</span> UnknownHostException(</span><br><span class="line"><span class="string">"Failed to determine LAN address: "</span> + e);</span><br><span class="line">unknownHostException.initCause(e);</span><br><span class="line"><span class="keyword">throw</span> unknownHostException;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>或者通过建立tcp socket来反向获取本机ip</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></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">trickyIp</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">try</span> (<span class="keyword">final</span> Socket socket = <span class="keyword">new</span> Socket()) {</span><br><span class="line"> socket.connect(<span class="keyword">new</span> InetSocketAddress(<span class="string">"www.google.com"</span>, <span class="number">80</span>));</span><br><span class="line"> <span class="keyword">return</span> socket.getLocalAddress().getHostAddress();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="在java代码中获得hostname"><a href="#在java代码中获得hostname" class="headerlink" title="在java代码中获得hostname"></a>在java代码中获得<a href="/2016/09/hostname">hostname</a></h3><p><code>hostname(1)</code>应该是唯一的选择(错误的网络环境配置导致上面的例子),所以在java代码中就变成了如何调用系统调用了,通过jni或者jna都是可以的,比如</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="comment">// 通过jna</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">CLibrary</span> <span class="keyword">extends</span> <span class="title">Library</span> </span>{</span><br><span class="line"> CLibrary INSTANCE = (CLibrary) Native.loadLibrary(<span class="string">"c"</span>, CLibrary<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">gethostname</span><span class="params">(<span class="keyword">byte</span>[] hostname, <span class="keyword">int</span> bufferSize)</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">hostname</span><span class="params">()</span> <span class="keyword">throws</span> UnknownHostException </span>{</span><br><span class="line"> <span class="keyword">if</span> (Platform.isWindows()) {</span><br><span class="line"> <span class="keyword">return</span> Kernel32Util.getComputerName();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">byte</span>[] hostnameBuffer = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">255</span>];</span><br><span class="line"> <span class="keyword">int</span> result = CLibrary.INSTANCE.gethostname(hostnameBuffer, hostnameBuffer.length);</span><br><span class="line"> <span class="keyword">if</span> (result != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> InetAddress.getLocalHost().getHostName();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Native.toString(hostnameBuffer);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>在linux服务器上,java程序有时会遇到<code>unknown host name</code>的问题。通过解决这些问题,我发现搞清楚jdk获取ip和hostname的方式以及linux机器的真实ip、hostname是关键。</p>
<p>linux上有很多常用命令会涉及,诸如:hostnamectl、nslookup、dig、ifconfig……下面我们先来看jdk如何获取hostname的<br>
</summary>
<category term="linux" scheme="https://xhao.io/tags/linux/"/>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>maven插件开发</title>
<link href="https://xhao.io/2016/02/maven-plugin/"/>
<id>https://xhao.io/2016/02/maven-plugin/</id>
<published>2016-02-15T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>参考<a href="http://maven.apache.org/guides/plugin/guide-java-plugin-development.html" target="_blank" rel="noopener">maven官方文档</a><br><a id="more"></a><br>以我之前写maven插件的经历,简单聊聊这过程中遇到的一些问题和注意点。</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><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mojo</span>(name = <span class="string">"genThrift"</span>, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThriftGenPlugin</span> <span class="keyword">extends</span> <span class="title">AbstractMojo</span> </span>{</span><br><span class="line"> <span class="meta">@Parameter</span>(defaultValue = <span class="string">"${project}"</span>, readonly = <span class="keyword">true</span>)</span><br><span class="line"> <span class="keyword">private</span> MavenProject project;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Parameter</span>(property = <span class="string">"genThrift.outputDirectory"</span>, defaultValue = <span class="string">"./"</span>)</span><br><span class="line"> <span class="keyword">private</span> String outputDirectory;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Parameter</span>(property = <span class="string">"genThrift.services"</span>, required = <span class="keyword">true</span>)</span><br><span class="line"> <span class="keyword">private</span> List<String> services;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">loadProjectClass</span><span class="params">()</span> <span class="keyword">throws</span> MojoExecutionException </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();</span><br><span class="line"> Class<?> sysclass = URLClassLoader<span class="class">.<span class="keyword">class</span></span>;</span><br><span class="line"> Method method = sysclass.getDeclaredMethod(<span class="string">"addURL"</span>, <span class="keyword">new</span> Class[] { URL<span class="class">.<span class="keyword">class</span> })</span>;</span><br><span class="line"> method.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> List<String> classpathElements = project.getCompileClasspathElements();</span><br><span class="line"> getLog().debug(<span class="string">"----------start laod projct classes----------"</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < classpathElements.size(); ++i) {</span><br><span class="line"> getLog().debug(classpathElements.get(i));</span><br><span class="line"> method.invoke(sysloader, <span class="keyword">new</span> File(classpathElements.get(i)).toURI().toURL());</span><br><span class="line"> }</span><br><span class="line"> getLog().debug(<span class="string">"----------end laod projct classes----------"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> MojoExecutionException(<span class="string">"Couldn't create a classloader."</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</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">execute</span><span class="params">()</span> <span class="keyword">throws</span> MojoExecutionException, MojoFailureException </span>{</span><br><span class="line"> loadProjectClass();</span><br><span class="line"><span class="comment">// TODO</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在开发插件的过程中,使用了<a href="http://maven.apache.org/plugin-tools/maven-plugin-plugin/examples/using-annotations.html" target="_blank" rel="noopener">annotation</a>,并没有用javadoc的方式。我的一些配置包括:</p><ul><li>@goal:这个是必须的,根据maven的机制life cycle是绑定了多个goal才能起作用;而且goal本身可以独立life cycle直接运行。</li><li>@phase:从goal的描述看出,它不是必须的。因为我的插件本身是一个独立的功能,不需要和compile或者package等phase来绑定。如果用户需要,就需要自己去pom中配置了。所以在这里,我忽略了。</li><li>@requiresDependencyResolution:这个是由插件的作用决定的,不是必须的选项。我的插件在运行期,需要加载当前project的代码及其依赖。</li><li>@Parameter:就是可用的参数。注意这里可以使用一些maven default的值,比如${project}。</li></ul><p>其他的配置,就参考maven官方提供的<a href="https://maven.apache.org/developers/mojo-api-specification.html#The_Descriptor_and_Annotations" target="_blank" rel="noopener">Annotations的解释</a>吧。</p><p>因为这个插件是用来将java代码转化为thrift的,所以插件本身需要读取当前转化project的代码。基于maven的<a href="http://maven.apache.org/guides/mini/guide-maven-classloading.html" target="_blank" rel="noopener">classloader机制</a>,classloader之间是相互隔离的。换句话说,插件运行期的classloader是不能天然使用project中代码的,所以需要一些手段了。目前我想到的是利用反射机制。首先,我利用MavenProject这个对象,获取它的classpath(其实就是本地maven仓库,jar包的地址),然后通过<code>URLClassLoader</code>的<code>addURL</code>这个方法,将jar包都添加到当前的<code>ClassLoader.getSystemClassLoader()</code>中来。完成这2步,就能达到读取project的目的。</p><p><code>execute</code>方法就是入口,在这里可以抛出2种exception,<code>MojoExecutionException</code>和<code>MojoFailureException</code>,它们的含义也不完全相同,前者是unexpected的异常,后者是执行中的异常(包括配置错误等等)。此外,插件还可以有log,使用方法是<code>getLog().debug("xxx")</code>;maven使用了自己的ioc框架,具体的配置都在${MAVEN_HOME}/conf下面能看到,log用的是slf4j。</p>]]></content>
<summary type="html">
<p>参考<a href="http://maven.apache.org/guides/plugin/guide-java-plugin-development.html" target="_blank" rel="noopener">maven官方文档</a><br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>java中容易忽略的问题(二)——File</title>
<link href="https://xhao.io/2016/01/java-file/"/>
<id>https://xhao.io/2016/01/java-file/</id>
<published>2016-01-01T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>近期因为误用java System Property,导致了一个file相关的bug。花了一点时间研究之后,终于找到了根源——一切都是user.dir惹的鬼。<br><a id="more"></a></p><h2 id="java-io-File"><a href="#java-io-File" class="headerlink" title="java.io.File"></a>java.io.File</h2><p>这个类应该是从jdk1.x版本就存在了,是jdk最早的模块之一。我们都知道的是<code>new File("path")</code>,path可以是绝对路径也可以是相对路径,相对路径是以working dir为起点的,或者更简单一点就是<code>System.getProperty("user.dir")</code>。这是最基本的理解,毋庸置疑的。但是呢,如果我们修改了user.dir呢?</p><p>为此,我写了一个测试代码:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</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">Main</span> </span>{</span><br><span class="line"></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 class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">// 反射获取file的一些属性</span></span><br><span class="line">Field field = File.class.getDeclaredField("fs");</span><br><span class="line">field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">Object fs = field.get(<span class="keyword">new</span> File(<span class="string">"."</span>).getParentFile());</span><br><span class="line">Method method = fs.getClass().getDeclaredMethod(<span class="string">"getBooleanAttributes"</span>, File<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">method.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 写了一个不存在的文件夹</span></span><br><span class="line">System.setProperty(<span class="string">"user.dir"</span>, <span class="string">"/Users/xiehao/Documents/workspace/tmp/tmp/11111111111111111"</span>);</span><br><span class="line">System.out.println(<span class="string">"user dir : "</span> + System.getProperty(<span class="string">"user.dir"</span>));</span><br><span class="line">System.out.println(<span class="string">"file(.) path "</span> + <span class="keyword">new</span> File(<span class="string">"."</span>).getAbsolutePath());</span><br><span class="line"></span><br><span class="line">System.out.println();</span><br><span class="line">System.out.println(<span class="string">"Now make dir"</span>);</span><br><span class="line">System.out.println(<span class="string">"----------------------------------"</span>);</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 一种使用mkdir</span></span><br><span class="line"><span class="comment">// new File("a").mkdir();</span></span><br><span class="line"><span class="comment">//File dir = new File("a/b");</span></span><br><span class="line"><span class="comment">//dir.mkdir();</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 一种使用mkdirs</span></span><br><span class="line">File dir = <span class="keyword">new</span> File(<span class="string">"a/b"</span>);</span><br><span class="line">dir.mkdirs();</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">"file unix system status : "</span> + method.invoke(fs, dir));</span><br><span class="line">System.out.println(<span class="string">"dir is exist after creating : "</span> + dir.exists() + <span class="string">", is Directory : "</span> + dir.isDirectory());</span><br><span class="line"></span><br><span class="line">System.out.println();</span><br><span class="line">System.out.println(<span class="string">"Now make file"</span>);</span><br><span class="line">System.out.println(<span class="string">"----------------------------------"</span>);</span><br><span class="line">File file = <span class="keyword">new</span> File(<span class="string">"a/b/c.txt"</span>);</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">"file absolute path : "</span> + file.getAbsolutePath());</span><br><span class="line">System.out.println(<span class="string">"file parent unix system status : "</span> + method.invoke(fs, file.getParentFile()));</span><br><span class="line">System.out.println(<span class="string">"file parent exists : "</span> + file.getParentFile().exists());</span><br><span class="line"></span><br><span class="line">file.createNewFile();</span><br><span class="line">System.out.println(<span class="string">"file unix system status : "</span> + method.invoke(fs, file));</span><br><span class="line">System.out.println(<span class="string">"file exists : "</span> + file.exists());</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">"file exists (new file with abs path) : "</span> + <span class="keyword">new</span> File(file.getAbsolutePath()).exists());</span><br><span class="line">System.out.println(<span class="string">"file exists (file getAbsoluteFile) : "</span> + file.getAbsoluteFile().exists());</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>jdk提供了2个创建文件夹的方法:<code>mkdir</code>和<code>mkdirs</code>,运行时不同的选择会导致不同的结果</p><ul><li><p><code>mkdir</code></p><ul><li>文件夹的相对位置和user.dir没有关系,只和java启动的位置相关。</li><li>但是<code>getAbsolutePath()</code>返回的却是<code>user.dir + relative_path</code>的结果,也就是最后2行可能返回相反的结果。</li></ul></li><li><p><code>mkdirs</code></p><ul><li>递归地创建文件夹</li><li>如果你的java启动位置<code>CWD</code>不是user.dir,那么可能<code>throw IOException</code></li></ul></li></ul><p>由此,我发现了2个以前忽略的问题</p><ol><li><p>如果System Property <code>user.dir</code>被修改,看起来位置变了,但是真正创建(java的io相当于在app和os之前加了一个中间层)文件的时候,os仍然使用了java应用真正启动的当前目录。这个问题在java的bug库中找到了<a href="http://bugs.java.com/view_bug.do?bug_id=4045688" target="_blank" rel="noopener">说明</a>,原来system properties中的<code>user.dir</code>或者说启动时传的<code>-Duser.dir</code>不应该被修改,他们应该是readonly属性(虽然jdk没有做强制的约束,只是口头上的规约)。这是因为jvm启动时候,对于os来说已经记录了一个<code>CWD</code>,后面简单地修改它,是不会影响os kernel的,并且也不应该影响(JNI可以,但是jdk官方也不建议这么做)。</p></li><li><p>另一个问题,mkdir和mkdirs不同。从字面意思看,mkdir只能创建一个文件夹,如果父目录还不存在,就会有<code>IOException</code>;mkdirs是递归地把父目录都创建。还有一个关键,mkdirs在创建父目录时候,生成了绝对路径(用到了我们的user.dir),并且把这个绝对路径所代表的file对象传给了native,所以mkdirs会按照我们设计的user.dir来创建文件夹。</p></li></ol><p>这里有个大坑,就是你以为java是按照相对路径(CWD)来创建的,并且父目录有了,子文件应该一定能生成吧??有可能失败,只要你user.dir和CWD不同,并且报错信息就是“你刚才创建的文件夹不存在”。我觉得这是jdk在设计上有不足之处,就是明明我生成了文件夹,为什么还提示文件夹不存在呢?</p><p>只能强行圆一下这个设计:<code>new File(path)</code>中的相对路径是path,os认为的父目录是CWD+path,这个目录有可能不存在,os会抛错到jdk层,jdk知道是由于父目录没有,但是他也不知道父目录的绝对路径,而是用user.dir+path。这2者之间产生了误差,会引发不一致性。Oh!MyGod!所以还是不要修改user.dir吧</p><p>PS: 最后还有一个问题,jvm可以主动触发产生另一个jvm进程,即<code>Runtime.getRuntime().exec(String[] cmdarray, String[] envp, File dir)</code>这里的dir就是子jvm所使用的CWD,如果null的话,他会尝试用当前jvm的CWD来代替。</p>]]></content>
<summary type="html">
<p>近期因为误用java System Property,导致了一个file相关的bug。花了一点时间研究之后,终于找到了根源——一切都是user.dir惹的鬼。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>jdk提供的工具类</title>
<link href="https://xhao.io/2015/09/java-performance/"/>
<id>https://xhao.io/2015/09/java-performance/</id>
<published>2015-09-04T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>生产环境中,有时候会遇到一些性能异常,通过重启等临时方案可以暂时让程序继续跑下去,直到下次问题再出现。想要解决这些问题,必须能定位它的位置。而性能相关的代码,通常可能是锁竞争,线程池/连接池不过,full GC,过度吃cpu资源,死锁等,这些问题通常发生在特定环境中(比如有对资源的竞争),分析代码不能快速定位。这时就需要利用jdk提供的性能监控工具。<br><a id="more"></a></p><h2 id="jps-Java-Virtual-Machine-Process-Status-Tool"><a href="#jps-Java-Virtual-Machine-Process-Status-Tool" class="headerlink" title="jps (Java Virtual Machine Process Status Tool)"></a>jps (Java Virtual Machine Process Status Tool)</h2><p>这个命令很简单,可以看到jvm中运行的进程及相关信息</p><ul><li>jps [options] [hostid]<br>hostid默认是本机,也可以是远程主机,完整的是这样<code>[protocol:][[//]hostname][:port][/servername]</code></li></ul><p>可选参数:</p><ul><li>-q 不输出类名、Jar名和传入main方法的参数</li><li>-m 输出传入main方法的参数</li><li>-l 输出main类或Jar的全限名</li><li>-v 输出传入JVM的参数</li></ul><h2 id="jstack-stack-trace"><a href="#jstack-stack-trace" class="headerlink" title="jstack (stack trace)"></a>jstack (stack trace)</h2><p>查看堆栈信息<br>Usage:</p><ul><li>jstack [-l] <pid></li><li>jstack -F [-m] [-l] <pid></li><li>jstack [-m] [-l] <executable> <core></li><li>jstack [-m] [-l] [server_id@]<remote server IP or hostname></li></ul><p>Options:</p><ul><li>-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)</li><li>-m to print both java and native frames (mixed mode)</li><li>-l long listing. Prints additional information about locks</li><li>-h or -help to print this help message<br>jstack -l 在发生死锁时,可以来查看锁的持有情况;-m 还会输出Native方法。jstack通常用于检查程序卡在什么地方</li></ul><h2 id="jmap-memory-map"><a href="#jmap-memory-map" class="headerlink" title="jmap (memory map)"></a>jmap (memory map)</h2><p>查看内存使用<br>Usage:</p><ul><li>jmap [option] <pid></li><li>jmap [option] <executable <core></li><li>jmap [option] [server_id@]<remote server IP or hostname><br>Options:</li><li>-heap to print java heap summary</li><li>-histo[:live] to print histogram of java object heap; if the “live” suboption is specified, only count live objects</li><li>-clstats to print class loader statistics</li><li>-finalizerinfo to print information on objects awaiting finalization</li><li>-dump:<dump-options> to dump java heap in hprof binary format<br>dump-options:</li></ul><ol><li>live dump only live objects; if not specified, all objects in the heap are dumped.</li><li>format=b binary format</li><li>file=<file> dump heap to <file><br>通常我们可以把内存信息dump下来,这个dump文件可以被visualVm来读取,也可以使用下面所说的jhat来查看</li></ol><h2 id="jhat-Java-Heap-Analysis-Tool"><a href="#jhat-Java-Heap-Analysis-Tool" class="headerlink" title="jhat (Java Heap Analysis Tool)"></a>jhat (Java Heap Analysis Tool)</h2><p>example:<code>jhat -J-Xmx512m -port 9998 /tmp/dump.dat</code>其中-J指定最大堆内存(如果dump文件过大的话);敲完这个命令,我们就可以在9998端口来分析了</p><h2 id="jstat-Java-Virtual-Machine-statistics-monitoring-tool"><a href="#jstat-Java-Virtual-Machine-statistics-monitoring-tool" class="headerlink" title="jstat (Java Virtual Machine statistics monitoring tool)"></a>jstat (Java Virtual Machine statistics monitoring tool)</h2><p>Usage:</p><ul><li>jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]<br>vmid是Java虚拟机ID,在Linux/Unix系统上一般就是进程ID。interval是采样时间间隔。count是采样数目。<br>jstat是jvm统计工具,比如<code>jstat -gc 21711 250 4</code>就是采样时间间隔为250ms,采样数为4,输出GC信息。</li></ul>]]></content>
<summary type="html">
<p>生产环境中,有时候会遇到一些性能异常,通过重启等临时方案可以暂时让程序继续跑下去,直到下次问题再出现。想要解决这些问题,必须能定位它的位置。而性能相关的代码,通常可能是锁竞争,线程池/连接池不过,full GC,过度吃cpu资源,死锁等,这些问题通常发生在特定环境中(比如有对资源的竞争),分析代码不能快速定位。这时就需要利用jdk提供的性能监控工具。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="performance" scheme="https://xhao.io/tags/performance/"/>
</entry>
<entry>
<title>扩展spring的xml</title>
<link href="https://xhao.io/2015/08/spring-extension/"/>
<id>https://xhao.io/2015/08/spring-extension/</id>
<published>2015-08-28T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>spring框架在java世界应用广泛,IOC、AOP,包括最近的spring boot等,体系庞大。平时spring应用很少,也没特别的经验要说。但在适配自己的框架和spring方面,也摸索了几天,有了一点心得。所有的适配都是基于spring框架对外开放的钩子…今天要说的比较简单,是关于xml的<br><a id="more"></a><br>开发参考了<a href="http://docs.spring.io/autorepo/docs/spring/4.2.x/spring-framework-reference/html/" target="_blank" rel="noopener">spring doc</a></p><h3 id="xml-extension"><a href="#xml-extension" class="headerlink" title="xml extension"></a>xml extension</h3><p>这部分可能是spring最简单和最常见的扩展了,基本包含了4个步骤:</p><ul><li>Authoring an XML schema to describe your custom element(s).</li><li>Coding a custom NamespaceHandler implementation (this is an easy step, don’t worry).</li><li>Coding one or more BeanDefinitionParser implementations (this is where the real work is done).</li><li>Registering the above artifacts with Spring (this too is an easy step).</li></ul><p>1.xml扩展首先要做的是定义xsd,这个可以参考<a href="http://www.w3schools.com/schema/" target="_blank" rel="noopener">语法</a>。你必须先了解xsd schema的规范,当然要玩的更好,还可以多学习下spring xsd的定义。spring的扩展是通过在META-INF下的2个文件起作用:spring.handlers和spring.schemas。</p><p>2.下一步就是注册解析器,在NamespaceHandler的init方法中<code>registerBeanDefinitionParser("common", new DefinitionParser());</code>。</p><p>3.BeanDefinitionParser的实现是真正需要下功夫的地方,不过spring也给我们提供了基本的实现AbstractSingleBeanDefinitionParser–>AbstractBeanDefinitionParser–>BeanDefinitionParser,这3个是我经常用到的。</p><p>4.当我们需要处理element下带有子节点时,通常的做法是增加一个<code>FactoryBean</code>,这个类是和beanFactory相关联的</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><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><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ComponentBeanDefinitionParser</span> <span class="keyword">extends</span> <span class="title">AbstractBeanDefinitionParser</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> AbstractBeanDefinition <span class="title">parseInternal</span><span class="params">(Element element, ParserContext parserContext)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> parseComponentElement(element);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> AbstractBeanDefinition <span class="title">parseComponentElement</span><span class="params">(Element element)</span> </span>{</span><br><span class="line">BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">factory.addPropertyValue(<span class="string">"parent"</span>, parseComponent(element));</span><br><span class="line"></span><br><span class="line">List<Element> childElements = DomUtils.getChildElementsByTagName(element, <span class="string">"component"</span>);</span><br><span class="line"><span class="keyword">if</span> (childElements != <span class="keyword">null</span> && childElements.size() > <span class="number">0</span>) {</span><br><span class="line">parseChildComponents(childElements, factory);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> factory.getBeanDefinition();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> BeanDefinition <span class="title">parseComponent</span><span class="params">(Element element)</span> </span>{</span><br><span class="line">BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">component.addPropertyValue(<span class="string">"name"</span>, element.getAttribute(<span class="string">"name"</span>));</span><br><span class="line"><span class="keyword">return</span> component.getBeanDefinition();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">parseChildComponents</span><span class="params">(List<Element> childElements, BeanDefinitionBuilder factory)</span> </span>{</span><br><span class="line">ManagedList<BeanDefinition> children = <span class="keyword">new</span> ManagedList<BeanDefinition>(childElements.size());</span><br><span class="line"><span class="keyword">for</span> (Element element : childElements) {</span><br><span class="line">children.add(parseComponentElement(element));</span><br><span class="line">}</span><br><span class="line">factory.addPropertyValue(<span class="string">"children"</span>, children);</span><br><span class="line">}</span><br><span class="line">}</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">ComponentFactoryBean</span> <span class="keyword">implements</span> <span class="title">FactoryBean</span><<span class="title">Component</span>> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Component parent;</span><br><span class="line"><span class="keyword">private</span> List<Component> children;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setParent</span><span class="params">(Component parent)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.parent = parent;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setChildren</span><span class="params">(List<Component> children)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.children = children;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Component <span class="title">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span>.children != <span class="keyword">null</span> && <span class="keyword">this</span>.children.size() > <span class="number">0</span>) {</span><br><span class="line"><span class="keyword">for</span> (Component child : children) {</span><br><span class="line"><span class="keyword">this</span>.parent.addComponent(child);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">this</span>.parent;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Class<Component> <span class="title">getObjectType</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> Component<span class="class">.<span class="keyword">class</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isSingleton</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这2段代码中还是很好理解的,<code>DomUtils.getChildElementsByTagName(element, "component")</code>能够获得子节点,然后再按照老方法去解析。</p><p>5.另一个问题是id的问题,我们虽然在xsd中没有定义id属性,但是当通过BeanFactory.getBean时,依然会提示我们top-level必须有id属性,这是为什么呢?显然是因为spring默认bean是要有id来区分的。但是我们又不愿意在自己的节点上加这个属性,解决办法是auto generate id。在<code>AbstractBeanDefinitionParser</code>中有一个<code>protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException</code>方法,只要重写就可以完成,但是必须要小心,这个id是不能出现重复的。</p><p>如果做到这些,基本就完成了spring xml的扩展,并不算难。</p>]]></content>
<summary type="html">
<p>spring框架在java世界应用广泛,IOC、AOP,包括最近的spring boot等,体系庞大。平时spring应用很少,也没特别的经验要说。但在适配自己的框架和spring方面,也摸索了几天,有了一点心得。所有的适配都是基于spring框架对外开放的钩子…今天要说的比较简单,是关于xml的<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
<category term="spring" scheme="https://xhao.io/tags/spring/"/>
</entry>
<entry>
<title>java中容易忽略的问题(一)</title>
<link href="https://xhao.io/2015/07/java-misuse/"/>
<id>https://xhao.io/2015/07/java-misuse/</id>
<published>2015-07-24T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<a id="more"></a><h3 id="hashmap"><a href="#hashmap" class="headerlink" title="hashmap"></a>hashmap</h3><p>java里的hashmap是单线程的,一般在多线程情况下,我们应该使用ConcurrentHashMap。因为在并发条件下,HashMap的误用可能不仅导致数据不一致性的问题,还有可能引发不可置信的死循环。当然,javadoc里已经明确告诫大家了:</p><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Note that this implementation <span class="literal">is</span><span class="built_in"> not</span> synchronized. If multiple threads access a hash <span class="keyword">map</span> concurrently, <span class="keyword">and</span> <span class="keyword">at</span> <span class="keyword">least</span> one <span class="keyword">of</span> the threads modifies the <span class="keyword">map</span> structurally, it must be synchronized externally. (A structural modification <span class="literal">is</span> any operation that adds <span class="keyword">or</span> deletes one <span class="keyword">or</span> more mappings; merely changing the <span class="keyword">value</span> associated with a<span class="built_in"> key</span> that an <span class="keyword">instance</span> already<span class="built_in"> contains</span> <span class="literal">is</span><span class="built_in"> not</span> a structural modification.) This <span class="literal">is</span> typically accomplished <span class="keyword">by</span> synchronizing on <span class="keyword">some</span> object that naturally encapsulates the <span class="keyword">map</span>. If no such object<span class="built_in"> exists</span>, the <span class="keyword">map</span> should be <span class="string">"wrapped"</span> using the Collections.synchronizedMap method. This <span class="literal">is</span> best done <span class="keyword">at</span> creation time, <span class="keyword">to</span> prevent accidental unsynchronized access <span class="keyword">to</span> the <span class="keyword">map</span></span><br></pre></td></tr></table></figure><p>这个问题是在rehash时候触发的循环链表造成的,之前sun对此的解释是<strong>请选择用ConcurrentHashMap</strong></p><h3 id="SimpleDateFormat"><a href="#SimpleDateFormat" class="headerlink" title="SimpleDateFormat"></a>SimpleDateFormat</h3><p>SimpleDateFormat是java中常用的时间format手段。它本身也不是线程安全的,在并发多线程条件下,比较好的做法应该是使用ThreadLocal或者每个线程创建属于自己的dateFormat。</p><figure class="highlight livecodeserver"><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">Synchronization</span><br><span class="line">Date formats are <span class="keyword">not</span> synchronized. It is recommended <span class="built_in">to</span> <span class="built_in">create</span> separate <span class="built_in">format</span> instances <span class="keyword">for</span> <span class="keyword">each</span> thread. If multiple threads access <span class="keyword">a</span> <span class="built_in">format</span> concurrently, <span class="keyword">it</span> must be synchronized externally.</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<a id="more"></a>
<h3 id="hashmap"><a href="#hashmap" class="headerlink" title="hashmap"></a>hashmap</h3><p>java里的hashmap是单线程的,一般在多线程情况下,我们应
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>java8中使用interface的default方法</title>
<link href="https://xhao.io/2015/07/methodHandles-lookup/"/>
<id>https://xhao.io/2015/07/methodHandles-lookup/</id>
<published>2015-07-06T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>最近遇到一个有趣的场景,简单描述如下:有一个interface <code>Hello</code>,带default实现的方法<code>hello()</code>;想调用这个<code>Hello.hello()</code>。</p><p>问题是调用实例方法是需要实体的,而仅有interface并不能构造一个实例。开始想通过字节码生成技术,发现困难挺多的,在不断尝试的过程中,发现了一个新的API:<code>MethodHandle</code>。<br><a id="more"></a></p><h3 id="Interface-default-method"><a href="#Interface-default-method" class="headerlink" title="Interface default method"></a>Interface default method</h3><p>java 8引入了一个很重要的语言特性:interface可以有方法的默认实现</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Hello</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">default</span> String <span class="title">hello</span><span class="params">(String meta)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello"</span> + meta;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="MethodHandle"><a href="#MethodHandle" class="headerlink" title="MethodHandle"></a>MethodHandle</h3><p>有了default实现,是不是可以在没有实例的情况下,调用这个类呢?</p><p><code>MethodHandle</code>要从jvm上的动态类型语言说起.<a href="http://www.infoq.com/cn/articles/jdk-dynamically-typed-language/" target="_blank" rel="noopener">infoq</a>的这篇文章介绍的不错。methodHandle来自于JSR 292,包名是java.lang.invoke。之前单纯依靠符号引用来确定调用的目标方法,jvm在底层又提供一种新的动态确定目标方法的机制,类似于C的函数指针的,在性能上是优于之前的反射。</p><figure class="highlight java"><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">MethodHandle sayHelloHandle = MethodHandles.lookup().findVirtual( Hello.class, "hello", MethodType.methodType(String.class, String.class));</span><br><span class="line">sayHelloHandle.bindTo(<span class="keyword">new</span> Hello()).invokeWithArguments(<span class="string">"World"</span>);</span><br></pre></td></tr></table></figure><p>MehodHandle的用法说实话,好麻烦,这个API设计的比较难用……言归正传,上面的这段代码离调用default实现不远了,问题在于new Hello()这个对于接口是不能直接用的,那么我们祭出第2个大招”Proxy”</p><h3 id="dynamic-proxy"><a href="#dynamic-proxy" class="headerlink" title="dynamic proxy"></a>dynamic proxy</h3><p>动态代理对于大家已经不陌生了,我直接贴出用法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello target = (Hello) Proxy.newProxyInstance(<span class="keyword">this</span>.getClass().getClassLoader(), <span class="keyword">new</span> Class[] { Hello<span class="class">.<span class="keyword">class</span> }, (<span class="title">proxy</span>, <span class="title">method</span>, <span class="title">args</span>) -> <span class="title">null</span>)</span>;</span><br></pre></td></tr></table></figure><p>是的,我们这里代理的对象其实什么也没有。这个时候如果直接去使用methodhandle会触发一个问题:”java.lang.IllegalAccessException: no private access for invokespecial”,非常头疼!</p><p>解决它的办法有2种</p><ol><li><p>在debug的过程中找到的:<code>field = Lookup.class.getDeclaredField("allowedModes");field.setAccessible(true);</code>,不错,就是这个check导致的,但是反射可以动态地修改(在我们invoke之前修改即可)。</p></li><li><p>最近刚看到的,看起来比上面的那个优雅了点(其实差不多…),请看代码,不再仔细分析…</p></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></pre></td><td class="code"><pre><span class="line">Hello target = (Hello) Proxy.newProxyInstance(<span class="keyword">this</span>.getClass().getClassLoader(), <span class="keyword">new</span> Class<?>[] { Hello<span class="class">.<span class="keyword">class</span> }, (<span class="title">proxy</span>, <span class="title">method</span>, <span class="title">args</span>) -> </span>{</span><br><span class="line"><span class="keyword">if</span> (method.isDefault()) {</span><br><span class="line"><span class="keyword">final</span> Class<?> declaringClass = method.getDeclaringClass();</span><br><span class="line">Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup<span class="class">.<span class="keyword">class</span>.<span class="title">getDeclaredConstructor</span>(<span class="title">Class</span>.<span class="title">class</span>, <span class="title">int</span>.<span class="title">class</span>)</span>;</span><br><span class="line">constructor.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"><span class="keyword">return</span> constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE).unreflectSpecial(method, declaringClass).bindTo(proxy)</span><br><span class="line">.invokeWithArguments(args);</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="keyword">return</span> <span class="string">"..."</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">);</span><br><span class="line">target.hello(<span class="string">",World!"</span>);</span><br></pre></td></tr></table></figure><p>从这个例子中,我们认识到2个问题:</p><ol><li>methodhandle的使用;</li><li>动态代理可以解析并调用interface中的default方法。这么看来,java还有很多神奇的地方值得学习!</li></ol>]]></content>
<summary type="html">
<p>最近遇到一个有趣的场景,简单描述如下:有一个interface <code>Hello</code>,带default实现的方法<code>hello()</code>;想调用这个<code>Hello.hello()</code>。</p>
<p>问题是调用实例方法是需要实体的,而仅有interface并不能构造一个实例。开始想通过字节码生成技术,发现困难挺多的,在不断尝试的过程中,发现了一个新的API:<code>MethodHandle</code>。<br>
</summary>
<category term="java" scheme="https://xhao.io/tags/java/"/>
</entry>
<entry>
<title>javascript作用域</title>
<link href="https://xhao.io/2014/07/javascript-scope/"/>
<id>https://xhao.io/2014/07/javascript-scope/</id>
<published>2014-07-30T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p>javascript的作用域和类C语言相比,有点特殊,对于初学者,容易搞错。所以要牢记<strong>javascript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里</strong><br><a id="more"></a><br>变量的作用域关系到它的可见性和生命周期,在编程的时候,超出作用域的使用范围,会带来可怕的结果。所以,每一门开发语言都会提供相应的服务。我们通常使用的类C语言,采用的是块级作用域(通常就是花括号),在一个代码块中定义的变量,在外部是无法使用的,并且在代码块的内部,变量还会覆盖其同名的外部变量,而一旦超出其作用域,变量就会失效(超出了其生命周期)。但是javascript(js)采用了另外的方式:<strong>在函数内部定义的参数和变量,在外部是看不到的;内部函数总是可以访问它们的外部参数和变量。还是那句话:javascript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里。</strong> </p><p>首先,函数规定了作用域的可见范围,在这一点上,函数很像C语言的花括号。但是,内部函数总是可以访问外部参数和变量,这一下子,就让变量的生命周期延长了(因为可以返回一个内部函数)。js的内部函数不仅包含了一组方法,还能绑定函数外部定义的一些参数和变量,而且它们还不是拷贝。这就是js的强大,它使用函数就非常简洁地完成了数据的封装、代码的复用、模块化的设计。对于用惯了C++、java的人,很容易陷入误区,经常用错变量。所以,我们应该去仔细地了解下,js是怎么处理的。 </p><h2 id="预解析"><a href="#预解析" class="headerlink" title="预解析"></a>预解析</h2><p>js是脚本语言,它是解释执行的,也就是从上到下逐行进行翻译并执行。但是它也会预解析,js引擎会处理var和function</p><figure class="highlight javascript"><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">add(<span class="number">1</span>);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">a</span>)</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(a);</span><br><span class="line">};</span><br><span class="line"><span class="comment">//1</span></span><br><span class="line"><span class="built_in">console</span>.log(a);</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="built_in">console</span>.log(b);</span><br><span class="line">b=<span class="number">2</span>;</span><br><span class="line"><span class="comment">//b is not defined</span></span><br></pre></td></tr></table></figure><p>但是预解析并不涉及赋值操作,所以局部变量会返回undefined。现在,不妨看看下面这个例子</p><figure class="highlight javascript"><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">var</span> name = <span class="string">'hello'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">echo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> alert(name);</span><br><span class="line"> <span class="keyword">var</span> name = <span class="string">'hi'</span>;</span><br><span class="line"> alert(name);</span><br><span class="line">}</span><br><span class="line">echo();</span><br></pre></td></tr></table></figure><p>首先name=”hello”是echo函数外部的局部变量,而在echo函数内部还存在一个局部变量name=”hi”,根据作用域的理解,我们可以想到内部定义会隐藏外部定义,这与我们的常识相符。同时echo函数内部的name在完成定义(赋值)前,就已经被使用,这在js里也是允许的,但它应该返回undefined。所以这里的结果应该是“undefined hi”,弹2次对话框。这个例子还是比较简单的,至少我们还能一眼看出其中的关系。如果再复杂呢?如何追溯到变量的定义,会变得棘手起来。</p><h2 id="作用域链"><a href="#作用域链" class="headerlink" title="作用域链"></a>作用域链</h2><p>js引擎在工作的时候,会维护一个scope chain的对象,是一个列表的形式。以函数为例,func在定义的时候,它的scope chain链接到func的scope属性上;func执行时,会创建一个active object并且加入到scope chain的最顶端,arguments、形参(这里形参会赋实参的值)、内部变量、内部函数都会作为这个AO的属性存在。拿一个简单的例子来说明,</p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">num1,num2</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> sum = num1 + num2;</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line">};</span><br><span class="line">add(<span class="number">1</span>,<span class="number">2</span>);</span><br></pre></td></tr></table></figure><p>在定义函数add的时候,add[scope]->[scope chain]->[global AO]:意思是add函数有一个属性是scope,它包含了add对象的作用域链,而这个作用域链其实是函数被创建时的作用域范围内的对象集合(在这里只有一个global对象window)。当add(1,2)运行时,又会引入一个执行上下文(函数运行的环境),它也有一个作用域链,它的作用域链在之前的基础上会加入一个active object(this、arguments、num1、num2、sum),并且被推到链表的顶端。作用域链指明了当前函数执行时所能访问的数据,顾名思义,函数会按照这个链接的顺序一路搜索直到global对象。这就是js构造作用域链的过程。 </p><p>js中的with和catch会破坏普通的作用域链,额外加入一个临时对象withObject或者是catchObject到链表头部,从而影响作用域链。 </p><h2 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h2><p>在前面已经说到func的[scope],它是在函数定义时被创建的,并且一直存在。所以闭包实际就是函数+[scope],它既拥有了函数的语句,也拥有函数定义时的可访问对象的集合。在执行闭包(函数)时,它的执行上下文=[scope]+AO。所以会出现下面的情况,</p><figure class="highlight javascript"><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="keyword">var</span> x = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> alert(x);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> x = <span class="number">20</span>; <span class="comment">//注意x=20并不在foo的AO里,请仔细读下AO所包含的内容</span></span><br><span class="line"> foo(); <span class="comment">// 10, 如果没有定义var x=10,会x is not defined即x还没有声明。</span></span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p>在这里还有一个例外,</p><figure class="highlight javascript"><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">var</span> x=<span class="number">10</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> y = <span class="number">20</span>;</span><br><span class="line"> <span class="keyword">var</span> foo = <span class="built_in">Function</span>(<span class="string">'alert(x);alert(y);'</span>);</span><br><span class="line"> foo();</span><br><span class="line">}</span><br><span class="line">fo();<span class="comment">//ReferenceError: y is not defined</span></span><br></pre></td></tr></table></figure><p>它告诉我们的意思是如果用了Function来构造函数,那么[scope]总是全局对象global,在这里全局对象只包含x=10。所以,如果改成</p><figure class="highlight javascript"><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">var</span> x=<span class="number">10</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fo</span>(<span class="params"></span>)</span>{</span><br><span class="line"> y = <span class="number">20</span>;<span class="comment">//y也成为了全局变量</span></span><br><span class="line"> <span class="keyword">var</span> foo = <span class="built_in">Function</span>(<span class="string">'alert(x);alert(y);'</span>);</span><br><span class="line"> foo();</span><br><span class="line">}</span><br><span class="line">fo();<span class="comment">//那么一切OK.</span></span><br></pre></td></tr></table></figure><p>在作用域链中如果没有找到对象,那么js会继续查找,这个时候它的目光放在了原型链上(AO应该是没有原型的),直到object.prototy。</p><figure class="highlight javascript"><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="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>) </span>{</span><br><span class="line"> alert(x);</span><br><span class="line">}</span><br><span class="line"><span class="built_in">Object</span>.prototype.x = <span class="number">10</span>;</span><br><span class="line">foo(); <span class="comment">// 10</span></span><br></pre></td></tr></table></figure><p>以上就是有关js作用域相关的话题,通过简短的描述,还是能清晰地看到它的原理。</p>]]></content>
<summary type="html">
<p>javascript的作用域和类C语言相比,有点特殊,对于初学者,容易搞错。所以要牢记<strong>javascript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里</strong><br>
</summary>
<category term="javascript" scheme="https://xhao.io/tags/javascript/"/>
</entry>
<entry>
<title>use liquibase</title>
<link href="https://xhao.io/2014/05/liquibase-usage/"/>
<id>https://xhao.io/2014/05/liquibase-usage/</id>
<published>2014-05-12T16:00:00.000Z</published>
<updated>2020-07-23T17:00:12.560Z</updated>
<content type="html"><![CDATA[<p><a href="http://www.liquibase.org/" target="_blank" rel="noopener">liquibase</a>是一个开源的数据库迁移工具,它和rails世界中的migration比较接近,这对大型的j2ee项目开发来说,可谓是优质的辅助。对于一个健壮的j2ee平台而言,稳定的数据库系统是不可或缺的。之前在项目中通过人工的方式审核,并且有专门的数据导入导出环境。但是这样有很多不足,比如:对于一个数据库,我们无法知道哪些脚本执行过,我们也无法搞清楚当前数据库所处的版本(对应于代码流)。liquibase正好提供了一个解决思路。为了应对搭建数据库的不便,我们目前采用了InitialDB+liquibase的机制,这将极大地方便我们在不同的开发产品线之间切换。<br><a id="more"></a><br>使用liquibase,首先看看简单的例子。</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">databaseChangeLog</span> <span class="attr">xmlns</span>=<span class="string">"http://www.liquibase.org/xml/ns/dbchangelog"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">changeSet</span> <span class="attr">author</span>=<span class="string">"xxxx (generated)"</span> <span class="attr">id</span>=<span class="string">"1399880011956-1"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">createTable</span> <span class="attr">remarks</span>=<span class="string">"fee_name"</span> <span class="attr">tableName</span>=<span class="string">"T_BCP_FEE_TYPE_NAME"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"ID"</span> <span class="attr">remarks</span>=<span class="string">"pk"</span> <span class="attr">type</span>=<span class="string">"NUMBER(10, 0)"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constraints</span> <span class="attr">nullable</span>=<span class="string">"false"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">column</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"FEE_TYPE"</span> <span class="attr">remarks</span>=<span class="string">"fee type"</span> <span class="attr">type</span>=<span class="string">"NUMBER(20, 0)"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"FEE_NAME"</span> <span class="attr">remarks</span>=<span class="string">"fee name"</span> <span class="attr">type</span>=<span class="string">"VARCHAR2(60)"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">createTable</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">changeSet</span>></span></span><br><span class="line"><span class="tag"></<span class="name">databaseChangeLog</span>></span></span><br></pre></td></tr></table></figure><p>我们在这个例子里新建了一张表T_BCP_FEE_TYPE_NAME,有3个列分别是ID、FEE_TYPE、FEE_NAME。它的基本元素是changeSet,代表着数据库的一次修改。liquibase通常会将每个changeSet放在一个transaction中执行,所以比较好的做法是每个changeSet尽量只含有一个数据库结构的改动。</p><p>liquibase还支持rollback,只需要在changeSet中加入rollback标签(某些change,liquibase可以自动rollback)。前面的例子可以改成</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">changeSet</span> <span class="attr">author</span>=<span class="string">"hao.xie (generated)"</span> <span class="attr">id</span>=<span class="string">"1399880011956-1"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">createTable</span> <span class="attr">remarks</span>=<span class="string">"fee_name"</span> <span class="attr">tableName</span>=<span class="string">"T_BCP_FEE_TYPE_NAME"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"ID"</span> <span class="attr">remarks</span>=<span class="string">"pk"</span> <span class="attr">type</span>=<span class="string">"NUMBER(10, 0)"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constraints</span> <span class="attr">nullable</span>=<span class="string">"false"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">column</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"FEE_TYPE"</span> <span class="attr">remarks</span>=<span class="string">"fee_type"</span> <span class="attr">type</span>=<span class="string">"NUMBER(20, 0)"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">column</span> <span class="attr">name</span>=<span class="string">"FEE_NAME"</span> <span class="attr">remarks</span>=<span class="string">"fee name"</span> <span class="attr">type</span>=<span class="string">"VARCHAR2(60)"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">createTable</span>></span></span><br><span class="line"><span class="tag"><<span class="name">rollback</span>></span></span><br><span class="line">drop table T_BCP_FEE_TYPE_NAME</span><br><span class="line"><span class="tag"></<span class="name">rollback</span>></span></span><br><span class="line"><span class="comment"><!--<rollback><dropTable tableName="T_BCP_FEE_TYPE_NAME"/></rollback> --></span></span><br><span class="line"><span class="comment"><!--<rollback changeSetId="xx" changeSetAuthor="xx"/> --></span></span><br><span class="line"> <span class="tag"></<span class="name">changeSet</span>></span></span><br></pre></td></tr></table></figure><p>这里面用了3种方式来写,都是可行的。加入了rollback的机制,从某种意义上讲,使得DB的迁移也有了版本的概念(不一定正确,但应该便于理解)。我们可以选择rollback到某一点,也可以再重新update到最新的数据库版本。以下是liquibase支持的几种“Roll Back To” Modes.</p><table border="1"><br> <tbody><br> <tr><th>Modes</th><th>Description</th></tr><br> <tr><td>Tag</td><td>Specifying a tag to rollback to will roll back all change-sets that were executed against the target database after the given tag was applied</td></tr><br> <tr><td>Number of Change Sets</td><td>You can specify the number of change-sets to rollback</td></tr><br> <tr><td>Date</td><td>You can specify the date to roll back to</td></tr><br> </tbody><br></table><p>下面要提的这个,我觉得也有版本的意味——Diff。在写代码的时候,我们经常会比较不同版本之间的代码。考虑到数据库的开发,liquibase也支持了这种compare。如果使用command,就是这样</p><pre><code>liquibase.sh --driver=oracle.jdbc.OracleDriver \ --url=jdbc:oracle:thin:@testdb:1521:test \ --username=bob \ --password=bob \diff \ --referenceUrl=jdbc:oracle:thin:@localhost/XE \ --referenceUsername=bob \ --referencePassword=bob</code></pre><p>liquibase通过diff的产出分为报表和changeLog这2种形式,前者便于阅读,后者便于liquibase直接执行(使得2个数据库merge到同一个版本)。我更倾向于后者。在这里它使用了默认的diffTypes来比较2个数据库,现在liquibase支持的比较类型是:</p><ul><li>tables [DEFAULT]</li><li>columns [DEFAULT]</li><li>views [DEFAULT]</li><li>primaryKeys [DEFAULT]</li><li>indexes [DEFAULT]</li><li>foreignKeys [DEFAULT]</li><li>sequences [DEFAULT]</li><li>data </li></ul><p><em>tip:我发现这个操作也是很消耗内存的,建议在执行前,手动设置jvm heap的大小。</em></p><p>总而言之,liquibase使数据库的开发变得像代码的版本管理。它最大的好处是方便了数据库的迁移和管理,并且还支持了command、ant、maven等多种格式,非常适合在java世界使用。正如我开头所说,它就是java世界的migration(rails)工具。</p>]]></content>
<summary type="html">
<p><a href="http://www.liquibase.org/" target="_blank" rel="noopener">liquibase</a>是一个开源的数据库迁移工具,它和rails世界中的migration比较接近,这对大型的j2ee项目开发来说,可谓是优质的辅助。对于一个健壮的j2ee平台而言,稳定的数据库系统是不可或缺的。之前在项目中通过人工的方式审核,并且有专门的数据导入导出环境。但是这样有很多不足,比如:对于一个数据库,我们无法知道哪些脚本执行过,我们也无法搞清楚当前数据库所处的版本(对应于代码流)。liquibase正好提供了一个解决思路。为了应对搭建数据库的不便,我们目前采用了InitialDB+liquibase的机制,这将极大地方便我们在不同的开发产品线之间切换。<br>
</summary>
<category term="liquibase" scheme="https://xhao.io/tags/liquibase/"/>
<category term="sql" scheme="https://xhao.io/tags/sql/"/>
</entry>
</feed>