Repository navigation
Expand file tree
/
Copy pathatom.xml
More file actions
468 lines (260 loc) · 78 KB
/
Copy pathatom.xml
File metadata and controls
468 lines (260 loc) · 78 KB
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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ziggle</title>
<subtitle>Hail Hydra</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2021-05-09T18:27:16.079Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>ziggle</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>MySQL开发规范</title>
<link href="http://yoursite.com/2021/04/14/MySQL%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83/"/>
<id>http://yoursite.com/2021/04/14/MySQL开发规范/</id>
<published>2021-04-14T19:36:40.000Z</published>
<updated>2021-05-09T18:27:16.079Z</updated>
<content type="html"><![CDATA[<h2 id="MySQL开发规范"><a href="#MySQL开发规范" class="headerlink" title="MySQL开发规范"></a>MySQL开发规范</h2><h2 id="一-表设计"><a href="#一-表设计" class="headerlink" title="一 表设计"></a>一 表设计</h2><h3 id="1-1-命名"><a href="#1-1-命名" class="headerlink" title="1.1 命名"></a>1.1 命名</h3><p><strong>库名、表名、字段名必须使用英文单词(单数)表示表的意义,一个单词不足以表示时两者之间使用”_”(表与字段)分割。 原则上不能超过20个字符</strong></p><ul><li><p><strong>table命名:(单数,多个单词,”_”分隔)</strong></p><p>Bad Example: csw_creditcard_bills</p><p>Good Example: csw_creditcard_bill</p></li><li><p><strong>index命名:</strong></p></li></ul><p><strong>一般索引(Btree)命名:</strong></p><p>Bad Example: index_uid_accid_type(uid, type, acid)</p><p>Good Example: idx_uid_type_acid(uid, type, acid)(索引表包含索引的所有信息,顺序一致,准确无误)。</p><p><strong>唯一键索引命名:</strong></p><p>Bad Example: bill_no (bill_no)</p><p>Good Example: uk_bill_no (bill_no)。</p><ul><li><strong>字段的命名:</strong></li></ul><blockquote><p>定义意义完整的单词或组合单词作为字段名,”_”分隔不同意义单词 </p><p>Bad Example: 字段名:name</p><p>Good Example: 字段名:xxx_name 因为名称可以有多个,昵称,登录名等等。</p></blockquote><h3 id="1-2-字段类型使用"><a href="#1-2-字段类型使用" class="headerlink" title="1.2 字段类型使用"></a>1.2 字段类型使用</h3><p><strong>VARCHAR类型( 0-65535 Bytes)</strong></p><p>适用于动态长度字符串,尽量根据业务需求定义合适的字段长度,不要为了图省事,直接定义为varchar(1024)或更长等等。</p><p><strong>Char类型(字符数0-255)</strong></p><p>适合所有固定字符串,如UUID char(36).不要使用Varchar类型。</p><p><strong>Text 类型</strong></p><p>仅当字符数量可能超过 20000 个的时候,才可以使用 TEXT 类型来存放字符类数据 (原因在于varchar最多可存65535Bytes,utf8字符集下每字符占用3个字节,65535/3=20000+)所有使用 TEXT 类型的字段必须和原表进行分拆,与原表主键单独组成另外一个表进行存放.(原因在于,数据库按行扫描并获取数据,如果存在text字段,将大大增加行检索成本,分出去以后可按原表主键再获取text值。</p><p><strong>禁用VARBINARY、BLOB存储图片、文件等</strong></p><p><strong>Enum类型</strong></p><p>用 0, 1 ,2 等数字tinyint类型来代替Enum</p><p><strong>Date类型</strong></p><p>所有需要精确到时间(时分秒)的字段均使用TIMESTAMP</p><p>所有只需要精确到天的字段全部使用 DATE 类型,而不应该使用 TIMESTAMP 或者DATETIME 类型。</p><p><strong>Integer 类型</strong></p><p>所有整数类型的字段尽量使用合适的大小,且明确标识出为无符号型(UNSIGNED),除非确实会出现负数,仅仅当该字段数字取值会超过22亿,才使用 BIGINT 类型<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">类型 字节最小值 最大值</span><br><span class="line"> (带符号的/无符号的)(带符号的/无符号的)</span><br><span class="line">TINYINT 1 -128 127</span><br><span class="line"> 0 255</span><br><span class="line">SMALLINT 2-32768 32767</span><br><span class="line"> 0 65535</span><br><span class="line">MEDIUMINT 3-8388608 8388607</span><br><span class="line"> 0 16777215</span><br><span class="line">INT 4-2147483648 2147483647</span><br><span class="line"> 0 4294967295</span><br><span class="line">BIGINT 8-9223372036854770000 9223372036854770000</span><br><span class="line"> 0 18446744073709500000</span><br></pre></td></tr></table></figure></p><p><strong>浮点数类型</strong></p><p>存储精确浮点数必须使用DECIMAL替代FLOAT和DOUBLE或转为整形(推荐)字段</p><h3 id="1-3-设计"><a href="#1-3-设计" class="headerlink" title="1.3 设计"></a>1.3 设计</h3><ul><li>所有表必须包含三列,自增ID主键,unsigned数据类型,created_at(记录创建时间),updated_at(记录更新时间)</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Example:</span><br><span class="line">create table t1(</span><br><span class="line">id int unsigned NOT NULL AUTO_INCREMENT,</span><br><span class="line">created_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,</span><br><span class="line">updated_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,</span><br><span class="line">PRIMARY KEY (`id`)</span><br><span class="line">) ENGINE=InnoDB comment '表描述';</span><br></pre></td></tr></table></figure><ul><li>尽量做好适当的字段冗余(例:uid)</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Example:</span><br><span class="line">create table t1 (</span><br><span class="line"> id int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',</span><br><span class="line"> uid int unsigned NOT NULL COMMENT '用户ID',</span><br><span class="line"> created_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,</span><br><span class="line"> updated_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,</span><br><span class="line">PRIMARY KEY (id)</span><br><span class="line">) ENGINE=InnoDB comment 'table描述';</span><br></pre></td></tr></table></figure><p>MySQL5.6之后timestamp可以支持多列字段拥有自动插入时间和自动更新时间:DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP</p><p>MySQL5.6之之前timestamp仅支持一列字段拥有自动插入时间和自动更新时间:DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP</p><ul><li><p>索引字段属性设置为not null default 值(不能索引Null值)</p></li><li><p>添加字段时不可带after,before,新加字段在表末尾添加</p></li><li><p>表字符集采用utf8,不区分大小写(不使用utf8-bin)</p></li><li><p>表存储引擎Innodb</p></li><li><p>表及字段必须使用中文注释,简洁明了</p></li><li><p>线上涉及表结构变更需至少提前24小时通知dba,大表提前1周。(在开发阶段与dba讨论db设计)</p></li><li><p>状态型及类型列使用tinyint即可,原则上不能建索引(如status,type)</p></li><li><p>禁止使用force index 等加hint方式使用索引</p></li></ul><h2 id="二-索引设计"><a href="#二-索引设计" class="headerlink" title="二 索引设计"></a>二 索引设计</h2><ul><li><p>索引名称必须使用小写</p></li><li><p>非唯一索引必须按照“idx_字段名”进行命名</p></li><li><p>唯一索引必须按照“uk_字段名”进行命名</p></li><li><p>索引中的字段数建议不超过5个,单张表的索引数量控制在5个以内</p></li><li><p>ORDER BY,GROUP BY,DISTINCT的字段尽量使用索引</p></li><li><p>使用EXPLAIN判断SQL语句是否合理使用索引,尽量避免extra列出现:Using File Sort,Using Temporary</p></li><li><p>对长度过长的VARCHAR字段必须建立索引时,使用Hash字段建立索引或者使用前缀索引,例:(idx(clumn(8)))</p></li><li><p>合理创建联合索引,一定注意重复率低及高频查询字段作为前缀索引</p></li></ul><p><strong>Bad Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">create table t1(</span><br><span class="line">id int unsigned NOT NULL AUTO_INCREMENT,</span><br><span class="line">uid int unsigned NOT NULL comment '用户id'</span><br><span class="line">status tinyint not null default 0 comment '状态描述',</span><br><span class="line">type tinyint not null default 0 comment '字段类型',</span><br><span class="line">created_time NOT NULL DEFAULT CURRENT_TIMESTAMP,</span><br><span class="line">updated_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,</span><br><span class="line">PRIMARY KEY (`id`),</span><br><span class="line">unique key uk_status_type_uid(status,type,uid)</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '表描述';</span><br></pre></td></tr></table></figure><p><strong>Good Example:</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">create table t1(</span><br><span class="line">id int unsigned NOT NULL AUTO_INCREMENT,</span><br><span class="line">uid int unsigned NOT NULL comment '用户id'</span><br><span class="line">status tinyint not null default 0 comment '状态描述',</span><br><span class="line">type tinyint not null default 0 comment '字段类型',</span><br><span class="line">created_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,</span><br><span class="line">updated_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,</span><br><span class="line">PRIMARY KEY (`id`),z</span><br><span class="line">unique key uk_uid_status_type(uid,status,type)</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '表描述';</span><br></pre></td></tr></table></figure></p><ul><li><p>innodb 所有表必须建ID主键</p></li><li><p>索引设计尽可能够用就好,避免无效索引</p></li></ul><h2 id="三-SQL编写"><a href="#三-SQL编写" class="headerlink" title="三 SQL编写"></a>三 SQL编写</h2><ul><li><p>WHERE条件中必须使用合适的数据类型,避免MySQL进行隐式类型转化</p></li><li><p>SELECT、INSERT语句必须显式的指明业务所需的字段名称,如果取值不是全表,希望指定列名,不允许select</p></li><li><p>避免复杂sql,降低业务耦合,方便之后的scale out 和sharding(如之后查询多个数据库等并发进行)</p></li><li><p>对于结果较多的分页查询</p></li></ul><p><strong>Bad Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM relation where biz_type ='0' AND end_time >='2014-05-29' ORDER BY id asc LIMIT 149420 ,20;</span><br></pre></td></tr></table></figure><p><strong>Good Example:</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT a.* FROM relation a, (select id from relation where biz_type ='0' AND end_time >='2014-05-29' ORDER BY id asc LIMIT 149420 ,20 ) b where a.id=b.id;。</span><br></pre></td></tr></table></figure></p><ul><li><p>避免在SQL语句进行数学运算或者函数运算(应在程序端完成)</p></li><li><p>用in or join代替子查询,in包含的值不易超过2000(整形)</p></li><li><p>表连接规范避免使用JOIN</p></li></ul><p> 所有非外连接的 SQL 不要使用 Join … On … 方式进行连接,而直接通过普通的 Where条件关联方式。外连接的 SQL 语句,可以使用 Left Join … On 的 Join 方式,且所有外连接一律写成 Left Join,而不要使用 Right Join。</p><p><strong>Bad Example:</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select a.id,b.id from a join b on a.id = b.a_id where</span><br></pre></td></tr></table></figure></p><p><strong>Good Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select a.id,b.id from a,b where a.id = b.a_id and ...</span><br></pre></td></tr></table></figure><ul><li><p>禁止insert、update、delete中做跨库操作,例:insert … wac.tbl_user</p></li><li><p>业务逻辑中禁止使用存储过程、触发器、函数、外键等</p></li><li><p>禁止使用order by rand(),now()函数</p></li><li><p>如要insert … on duplicate key update需通知dba审核</p></li><li><p>禁止使用union,用union all代替</p></li><li><p>用union all 代替 or</p></li><li><p>以下查询不能索引(not,!=,<>,!<,!>,not exist,not in,not like)(%like)</p></li><li><p>可用explain观察sql执行计划,show profile 观察sql的性能损耗情况,目前我们环境中 5.6版本可对select,update,delete语句均支持执行计划</p></li></ul><hr>]]></content>
<summary type="html">
<h2 id="MySQL开发规范"><a href="#MySQL开发规范" class="headerlink" title="MySQL开发规范"></a>MySQL开发规范</h2><h2 id="一-表设计"><a href="#一-表设计" class="header
</summary>
</entry>
<entry>
<title>gradle-生命周期</title>
<link href="http://yoursite.com/2020/12/15/gradle-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
<id>http://yoursite.com/2020/12/15/gradle-生命周期/</id>
<published>2020-12-15T09:35:52.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<h3 id="构建的生命周期"><a href="#构建的生命周期" class="headerlink" title="构建的生命周期"></a>构建的生命周期</h3><p>任何Gradle的构建过程都分为三部分:初始化阶段、配置阶段和执行阶段。</p><p>通过如下代码向Gradle的构建过程添加监听:<br><figure class="highlight groovy"><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">gradle.addBuildListener(<span class="keyword">new</span> BuildListener() {</span><br><span class="line"> <span class="keyword">void</span> buildStarted(Gradle var1) {</span><br><span class="line"> println <span class="string">'开始构建'</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">void</span> settingsEvaluated(Settings var1) {</span><br><span class="line"> println <span class="string">'settings评估完成(settins.gradle中代码执行完毕)'</span></span><br><span class="line"> <span class="comment">// var1.gradle.rootProject 这里访问Project对象时会报错,还未完成Project的初始化</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">void</span> projectsLoaded(Gradle var1) {</span><br><span class="line"> println <span class="string">'项目结构加载完成(初始化阶段结束)'</span></span><br><span class="line"> println <span class="string">'初始化结束,可访问根项目:'</span> + var1.gradle.rootProject</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">void</span> projectsEvaluated(Gradle var1) {</span><br><span class="line"> println <span class="string">'所有项目评估完成(配置阶段结束)'</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">void</span> buildFinished(BuildResult var1) {</span><br><span class="line"> println <span class="string">'构建结束 '</span></span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure></p><p>hook 点</p><img src="/2020/12/15/gradle-生命周期/gradle-hook.png" title="Gradle构建周期中的Hook点"><p>Gradle在构建的各个阶段都提供了很多回调,我们在添加对应监听时要注意,监听器一定要在回调的生命周期之前添加,比如我们在根项目的build.gradle中添加下面的代码就是错误的:<br><figure class="highlight groovy"><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">gradle.settingsEvaluated { setting -></span><br><span class="line"> <span class="comment">// do something with setting</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">gradle.projectsLoaded { </span><br><span class="line"> gradle.rootProject.afterEvaluate {</span><br><span class="line"> println <span class="string">'rootProject evaluated'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>当构建走到build.gradle时说明初始化过程已经结束了,所以上面的回调都不会执行,把上述代码移动到settings.gradle中就正确了。</p><p>通过一些例子来解释如何Hook Gradle的构建过程。</p><h4 id="为所有子项目添加公共代码"><a href="#为所有子项目添加公共代码" class="headerlink" title="为所有子项目添加公共代码"></a>为所有子项目添加公共代码</h4><p> 在根项目的build.gradle中添加如下代码:</p><figure class="highlight groovy"><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"> gradle.beforeProject { project -></span><br><span class="line"> println <span class="string">'apply plugin java for '</span> + project</span><br><span class="line"> project.apply <span class="string">plugin:</span> <span class="string">'java'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 这段代码的作用是为所有子项目应用Java插件,因为代码是在根项目的配置阶段执行的,所以并不会应用到根项目中。<br>这里说明一下Gradle的beforeProject方法和Project的beforeEvaluate的执行时机是一样的,只是beforeProject应用于所有项目,而beforeEvaluate只应用于调用的Project,上面的代码等价于</p><figure class="highlight groovy"><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">allprojects {</span><br><span class="line"> beforeEvaluate { project -></span><br><span class="line"> println <span class="string">'apply plugin java for '</span> + project</span><br><span class="line"> project.apply <span class="string">plugin:</span> <span class="string">'java'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>after<em>*</em>也是同理的,但afterProject还有一点不一样,无论Project的配置过程是否出错,afterProject都会收到回调</p><h4 id="为指定Task动态添加Action"><a href="#为指定Task动态添加Action" class="headerlink" title="为指定Task动态添加Action"></a>为指定Task动态添加Action</h4><figure class="highlight groovy"><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"> gradle.taskGraph.beforeTask { task -></span><br><span class="line"> task << {</span><br><span class="line"> println <span class="string">'动态添加的Action'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">task Test {</span><br><span class="line"> doLast {</span><br><span class="line"> println <span class="string">'原始Action'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="获取构建各阶段耗时情况"><a href="#获取构建各阶段耗时情况" class="headerlink" title="获取构建各阶段耗时情况"></a>获取构建各阶段耗时情况</h5><figure class="highlight groovy"><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="keyword">long</span> beginOfSetting = System.currentTimeMillis()</span><br><span class="line"></span><br><span class="line">gradle.projectsLoaded {</span><br><span class="line"> println <span class="string">'初始化阶段,耗时:'</span> + (System.currentTimeMillis() - beginOfSetting) + <span class="string">'ms'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> beginOfConfig</span><br><span class="line"><span class="keyword">def</span> configHasBegin = <span class="literal">false</span></span><br><span class="line"><span class="keyword">def</span> beginOfProjectConfig = <span class="keyword">new</span> HashMap()</span><br><span class="line">gradle.beforeProject { project -></span><br><span class="line"> <span class="keyword">if</span> (!configHasBegin) {</span><br><span class="line"> configHasBegin = <span class="literal">true</span></span><br><span class="line"> beginOfConfig = System.currentTimeMillis()</span><br><span class="line"> }</span><br><span class="line"> beginOfProjectConfig.put(project, System.currentTimeMillis())</span><br><span class="line">}</span><br><span class="line">gradle.afterProject { project -></span><br><span class="line"> <span class="keyword">def</span> begin = beginOfProjectConfig.get(project)</span><br><span class="line"> println <span class="string">'配置阶段,'</span> + project + <span class="string">'耗时:'</span> + (System.currentTimeMillis() - begin) + <span class="string">'ms'</span></span><br><span class="line">}</span><br><span class="line"><span class="keyword">def</span> beginOfProjectExcute</span><br><span class="line">gradle.taskGraph.whenReady {</span><br><span class="line"> println <span class="string">'配置阶段,总共耗时:'</span> + (System.currentTimeMillis() - beginOfConfig) + <span class="string">'ms'</span></span><br><span class="line"> beginOfProjectExcute = System.currentTimeMillis()</span><br><span class="line">}</span><br><span class="line">gradle.taskGraph.beforeTask { task -></span><br><span class="line"> task.doFirst {</span><br><span class="line"> task.ext.beginOfTask = System.currentTimeMillis()</span><br><span class="line"> }</span><br><span class="line"> task.doLast {</span><br><span class="line"> println <span class="string">'执行阶段,'</span> + task + <span class="string">'耗时:'</span> + (System.currentTimeMillis() - task.beginOfTask) + <span class="string">'ms'</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">gradle.buildFinished {</span><br><span class="line"> println <span class="string">'执行阶段,耗时:'</span> + (System.currentTimeMillis() - beginOfProjectExcute) + <span class="string">'ms'</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将上述代码段添加到settings.gradle脚本文件的开头,再执行任意构建任务,你就可以看到各阶段、各任务的耗时情况。</p><h4 id="动态改变Task依赖关系"><a href="#动态改变Task依赖关系" class="headerlink" title="动态改变Task依赖关系"></a>动态改变Task依赖关系</h4><p>有时我们需要在一个已有的构建系统中插入我们自己的构建任务,比如在执行Java构建后我们想要删除构建过程中产生的临时文件,那么我们就可以自定义一个名叫cleanTemp的任务,让其依赖于build任务,然后调用cleanTemp任务即可。</p><ul><li>寻找插入点<br>如果你对一个构建的任务依赖关系不熟悉的话,可以使用一个插件来查看,在根项目的build.gradle中添加如下代码:<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">plugins{</span><br><span class="line"> id "com.dorongold.task-tree" version "1.5"</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>然后执行<code>gradle <任务名> taskTree --no-repeat</code>,即可看到指定Task的依赖关系,比如在Java构建中查看build任务的依赖关系:</p><ul><li>动态插入自定义任务</li></ul><p>我们先定义一个自定的任务cleanTemp,让其依赖于assemble。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">task cleanTemp(dependsOn: assemble) {</span><br><span class="line"> doLast {</span><br><span class="line"> println '清除所有临时文件'</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">afterEvaluate {</span><br><span class="line"> build.dependsOn cleanTemp</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<h3 id="构建的生命周期"><a href="#构建的生命周期" class="headerlink" title="构建的生命周期"></a>构建的生命周期</h3><p>任何Gradle的构建过程都分为三部分:初始化阶段、配置阶段和执行阶段。</p>
<p>通过如下代码
</summary>
</entry>
<entry>
<title>pwsh-刷新环境变量</title>
<link href="http://yoursite.com/2020/12/11/pwsh-%E5%88%B7%E6%96%B0%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F/"/>
<id>http://yoursite.com/2020/12/11/pwsh-刷新环境变量/</id>
<published>2020-12-11T09:30:00.000Z</published>
<updated>2021-05-09T18:27:16.119Z</updated>
<content type="html"><![CDATA[<h4 id="刷新环境变量"><a href="#刷新环境变量" class="headerlink" title="刷新环境变量"></a>刷新环境变量</h4><p><code>code $profile</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function RefreshEnv {</span><br><span class="line"> $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") </span><br><span class="line"> Write-host('RefreshEnv')</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h4 id="刷新环境变量"><a href="#刷新环境变量" class="headerlink" title="刷新环境变量"></a>刷新环境变量</h4><p><code>code $profile</code></p>
<figure class="highligh
</summary>
</entry>
<entry>
<title>jvm-cms垃圾回收</title>
<link href="http://yoursite.com/2020/10/27/jvm-cms%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
<id>http://yoursite.com/2020/10/27/jvm-cms垃圾回收/</id>
<published>2020-10-27T20:59:48.000Z</published>
<updated>2021-05-09T18:27:16.099Z</updated>
<content type="html"><![CDATA[<h4 id="浅谈CMS原理"><a href="#浅谈CMS原理" class="headerlink" title="浅谈CMS原理"></a>浅谈CMS原理</h4><p>CMS(Concurrent Mark Sweep)收集器以获取最短回收停顿时间为目标,是HotSpot虚拟机中第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。</p><p>过程</p><ul><li>初始标记(CMS initial mark):标记一下GC Roots能直接关联到的对象。需要STW,速度很快。</li><li>并发标记(CMS concurrent mark):进行GC Roots Tracing。不需要STW。会产生浮动垃圾。</li><li>重新标记(CMS remark):找到并发标记期间产生的浮动垃圾。需要STW,停顿时间一般会比初始标记稍长,但远比并发标记短。</li><li>并发清除(CMS concurrent sweep):清除已标记的垃圾。不需要STW。会产生浮动垃圾,只能等下一次GC清理。</li></ul><img src="/2020/10/27/jvm-cms垃圾回收/cms.png" title="cms"><p>重新标记的过程?为什么比并发标记的时间短?<br>不考虑标记对象年龄等操作,最容易想到的原因是:</p><p>对象数的区别:“并发标记”过程需要扫描所有对象,标记出不可达的对象(该清除)和可达的对象(不该清除);“重新标记”只需要扫描在“并发标记”过程中被标记为可达或新创建的对象,检查其是否在“并发标记”过程中被标记为可达之后,由于用户使用变的不可达了<br>并发环境的区别:“并发标记”是与用户线程一起工作的,并发瓶颈较窄(工作线程少+安全检查);“重新标记”需要stop the world,之后仅有“重新标记”的线程在工作,并发瓶颈宽的多<br>PS:关于对象数的区别要想清楚,在对象分配后,如果对象有一瞬间不可达,则该对象以后都将不可达,可对其清理。因此,重新标记时不需要检查这部分对象。</p><p>并发清除的过程?如何才能让用户边使用,边清除?<br>还是那句话:</p><p>在对象分配后,如果对象有一瞬间不可达,则该对象以后都将不可达,可对其清理。</p><p>所以,已经在“并发标记”和“重新标记”过程被标记为不可达的对象,以后都不会再被用户使用,清除这些对象对用户完全无影响。</p><p>唯一可能有影响的是整理内存的过程,不过也只需要同步使用对象和整理对象两个动作。</p><p>CMS的优点、缺点?<br>优点:</p><p>大部分时间可与用户线程并发工作<br>低停顿<br>缺点:</p><p>对CPU资源非常敏感。并发标记和并发清理与用户线程一起工作,如果用户线程也是CPU敏感的,那么必然影响用户线程。<br>无法处理浮动垃圾(Floating Garbage)。并发标记与并发清除过程会产生浮动垃圾,如果CMS之前预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将退化使用Serial Old收集器,重新进行老年代的垃圾收集,这样停顿时间就很长了。可使用XX:CMSInitiatingOccupancyFraction参数设置触发CMS时的老年代空间比例(剩余空间就是预留空间),在JDK1.6中默认为92%。<br>基于“标记-清除算法”,收集结束时会有大量空间碎片产生,导致明明剩余空间充足,却无法为大对象分配足够的连续内存。可打开-XX:+UseCMSCompactAtFullCollection开关参数(默认打开)在进行Full GC之前整理内存碎片(称为“压缩”);使用-XX:CMSFullGCsBeforeCompaction参数(默认0)设置多少次不带压缩的Full CG之后才进行一次带压缩的Full GC。内存整理无法并行,还需要STW,需要适当调整内存整理的频率,在GC性能与空间利用率之间平衡。</p><p>PS:</p><p>浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处 理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。在这期间用户可能创建新的对象。为了处理这部分浮动垃圾和对象,CMS在并发清理之前,需要预留出足够空间给并发清理期间的用户线程使用。一般会显示使用-XX:CMSInitiatingOccupancyFraction参数设置触发CMS时的老年代空间比例,如果老年代增长不是太快,可以适当提高比例,以减少Full GC的次数。<br>关于浮动垃圾和内存碎片的问题。HDFS namenode在堆内存达到100G规模时,通常设置75%触发Full GC,不开启压缩,优先考虑STW造成的延迟。</p>]]></content>
<summary type="html">
<h4 id="浅谈CMS原理"><a href="#浅谈CMS原理" class="headerlink" title="浅谈CMS原理"></a>浅谈CMS原理</h4><p>CMS(Concurrent Mark Sweep)收集器以获取最短回收停顿时间为目标,是HotSp
</summary>
</entry>
<entry>
<title>jvm-oom异常原因</title>
<link href="http://yoursite.com/2020/10/27/jvm-oom%E5%BC%82%E5%B8%B8%E5%8E%9F%E5%9B%A0/"/>
<id>http://yoursite.com/2020/10/27/jvm-oom异常原因/</id>
<published>2020-10-27T20:55:40.000Z</published>
<updated>2021-05-09T18:27:16.099Z</updated>
<content type="html"><![CDATA[<h4 id="java-lang-OutOfMemoryError-Java-heap-space"><a href="#java-lang-OutOfMemoryError-Java-heap-space" class="headerlink" title="java.lang.OutOfMemoryError:Java heap space"></a>java.lang.OutOfMemoryError:Java heap space</h4><p>这是最常见的OOM原因。</p><p>堆中主要存放各种对象实例,还有常量池等结构。当JVM发现堆中没有足够的空间分配给新对象时,抛出该异常。具体来讲,在刚发现空间不足时,会先进行一次Full GC,如果GC后还是空间不足,再抛出异常。</p><p>引起空间不足的原因主要有:</p><p>业务高峰,创建对象过多<br>内存泄露<br>内存碎片严重,无法分配给大对象</p><h4 id="java-lang-OutOfMemoryError-Metaspace"><a href="#java-lang-OutOfMemoryError-Metaspace" class="headerlink" title="java.lang.OutOfMemoryError:Metaspace"></a>java.lang.OutOfMemoryError:Metaspace</h4><p>方法区主要存储类的元信息,实现在元数据区。当JVM发现元数据区没有足够的空间分配给加载的类时,抛出该异常。</p><p>引起元数据区空间不足的原因主要有:</p><p>加载的类太多,常见于Tomcat等容器中<br>但是元数据区被实现在堆外,主要受到进程本身的内存限制,这种实现下很难溢出。</p><h4 id="java-lang-OutOfMemoryError-Unable-to-create-new-native-thread"><a href="#java-lang-OutOfMemoryError-Unable-to-create-new-native-thread" class="headerlink" title="java.lang.OutOfMemoryError:Unable to create new native thread"></a>java.lang.OutOfMemoryError:Unable to create new native thread</h4><p>以Linux系统为例,JVM创建的线程与操作系统中的线程一一对应,受到以下限制:</p><p>进程和操作系统的内存资源限制。其中,一个JVM线程至少要占用OS的线程栈+JVM的虚拟机栈 = 8MB + 1MB = 9MB(当然JVM实现可以选择不使用这1MB的JVM虚拟机栈)。<br>进程和操作系统的线程数限制。<br>Linux中的线程被实现为轻量级进程,因此,还受到pid数量的限制。<br>当无法在操作系统中继续创建线程时,抛出上述异常。</p><p>解决办法从原因中找:</p><p>内存资源:调小OS的线程栈、JVM的虚拟机栈。<br>线程数:增大线程数限制。<br>pid:增大pid范围。</p><p>java.lang.OutOfMemoryError:GC overhead limit exceeded<br>默认配置下,如果GC花费了98%的时间,回收的内存都不足2%的话,抛出该异常。</p><p>java.lang.OutOfMemoryError:Out of swap space<br>如果JVM申请的内存大于可用物理内存,操作系统会将内存中的数据交换到磁盘上去(交换区)。如果交换区空间不足,抛出该异常。</p>]]></content>
<summary type="html">
<h4 id="java-lang-OutOfMemoryError-Java-heap-space"><a href="#java-lang-OutOfMemoryError-Java-heap-space" class="headerlink" title="java.lan
</summary>
</entry>
<entry>
<title>mysql-sql优化</title>
<link href="http://yoursite.com/2020/10/09/mysql-sql%E4%BC%98%E5%8C%96/"/>
<id>http://yoursite.com/2020/10/09/mysql-sql优化/</id>
<published>2020-10-09T22:36:52.000Z</published>
<updated>2021-05-09T18:27:16.115Z</updated>
<content type="html"><![CDATA[<h4 id="问题"><a href="#问题" class="headerlink" title="问题:"></a>问题:</h4><p>对于小的偏移量,直接使用limit来查询没有什么问题,但随着数据量的增大,越往后分页,limit语句的偏移量就会越大,速度也会明显变慢。<br>优化思想:<br>避免数据量大时扫描过多的记录</p><h4 id="解决"><a href="#解决" class="headerlink" title="解决:"></a>解决:</h4><p>子查询的分页方式或者JOIN分页方式。<br>JOIN分页和子查询分页的效率基本在一个等级上,消耗的时间也基本一致。<br>下面举个例子。一般MySQL的主键是自增的数字类型,这种情况下可以使用下面的方式进行优化。<br>下面以真实的生产环境的80万条数据的一张表为例,比较一下优化前后的查询耗时:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 传统limit,文件扫描</span></span><br><span class="line">[SQL]<span class="keyword">SELECT</span> * <span class="keyword">FROM</span> tableName <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">id</span> <span class="keyword">LIMIT</span> <span class="number">500000</span>,<span class="number">2</span>;</span><br><span class="line">受影响的行: 0</span><br><span class="line">时间: 5.371s</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 子查询方式,索引扫描</span></span><br><span class="line">[SQL]</span><br><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> tableName</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">id</span> >= (<span class="keyword">SELECT</span> <span class="keyword">id</span> <span class="keyword">FROM</span> tableName <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">id</span> <span class="keyword">LIMIT</span> <span class="number">500000</span> , <span class="number">1</span>)</span><br><span class="line"><span class="keyword">LIMIT</span> <span class="number">2</span>;</span><br><span class="line">受影响的行: 0</span><br><span class="line">时间: 0.274s</span><br><span class="line"></span><br><span class="line"><span class="comment">-- JOIN分页方式</span></span><br><span class="line">[SQL]</span><br><span class="line"><span class="keyword">SELECT</span> *</span><br><span class="line"><span class="keyword">FROM</span> tableName <span class="keyword">AS</span> t1</span><br><span class="line"><span class="keyword">JOIN</span> (<span class="keyword">SELECT</span> <span class="keyword">id</span> <span class="keyword">FROM</span> tableName <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">id</span> <span class="keyword">desc</span> <span class="keyword">LIMIT</span> <span class="number">500000</span>, <span class="number">1</span>) <span class="keyword">AS</span> t2</span><br><span class="line"><span class="keyword">WHERE</span> t1.id <= t2.id <span class="keyword">ORDER</span> <span class="keyword">BY</span> t1.id <span class="keyword">desc</span> <span class="keyword">LIMIT</span> <span class="number">2</span>;</span><br><span class="line">受影响的行: 0</span><br><span class="line">时间: 0.278s</span><br></pre></td></tr></table></figure><p>复制代码可以看到经过优化性能提高了将近20倍。</p><h4 id="优化原理"><a href="#优化原理" class="headerlink" title="优化原理:"></a>优化原理:</h4><p>子查询是在索引上完成的,而普通的查询时在数据文件上完成的,通常来说,索引文件要比数据文件小得多,所以操作起来也会更有效率。因为要取出所有字段内容,第一种需要跨越大量数据块并取出,而第二种基本通过直接根据索引字段定位后,才取出相应内容,效率自然大大提升。<br>因此,对limit的优化,不是直接使用limit,而是首先获取到offset的id,然后直接使用limit size来获取数据。<br>在实际项目使用,可以利用类似策略模式的方式去处理分页,例如,每页100条数据,判断如果是100页以内,就使用最基本的分页方式,大于100,则使用子查询的分页方式。</p><h4 id="对uuid处理"><a href="#对uuid处理" class="headerlink" title="对uuid处理"></a>对uuid处理</h4><p>因为插入值会随机写到索引的不同位置,使得insert更慢,这会导致页分裂,磁盘随机访问,以及对聚集索引产生碎片,<br>逻辑上相邻的行被分布在磁盘和内存的不同位置<br>随机值导致缓存对所有类型的查询语句效果很差,使得缓存依赖以工作的访问局部性原理失效,</p><p>如果存储uuid应该移除 ‘-‘ 符号,或者更好的做法是,用UNHEX()函数转换uuid为16字节的数字,并且存储在一个binary(16)列中,</p><h4 id="物化视图"><a href="#物化视图" class="headerlink" title="物化视图"></a>物化视图</h4><p>物化视图是预先计算并存储在磁盘上的表,通过各种策略刷新和更新,mysql不支持(flexviews)</p><h4 id="alter-table"><a href="#alter-table" class="headerlink" title="alter table"></a>alter table</h4><p>mysql 执行大部分 <code>alter table</code>操作 使用新的结构创建按一个空表, 从旧表中查出所有的数据插入新表,然后删除旧表. 当内存不足而且表又很大时,而且有很多索引的情况下尤其如此, </p>]]></content>
<summary type="html">
<h4 id="问题"><a href="#问题" class="headerlink" title="问题:"></a>问题:</h4><p>对于小的偏移量,直接使用limit来查询没有什么问题,但随着数据量的增大,越往后分页,limit语句的偏移量就会越大,速度也会明显变慢。
</summary>
<category term="mysql" scheme="http://yoursite.com/tags/mysql/"/>
</entry>
<entry>
<title>spring-mvc</title>
<link href="http://yoursite.com/2020/10/07/spring-mvc/"/>
<id>http://yoursite.com/2020/10/07/spring-mvc/</id>
<published>2020-10-07T23:52:23.000Z</published>
<updated>2021-05-09T18:27:16.119Z</updated>
<content type="html"><![CDATA[<h4 id="SpringMVC-原理"><a href="#SpringMVC-原理" class="headerlink" title="SpringMVC 原理"></a>SpringMVC 原理</h4><p>Spring 的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,这个 Servlet<br>会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染<br>等,甚至还能支持文件上传。</p><img src="/2020/10/07/spring-mvc/springmvc流程.png" title="springmvc流程"><p>Http 请求到 DispatcherServlet<br>(1) 客户端请求提交到 DispatcherServlet。<br>HandlerMapping 寻找处理器<br>(2) 由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处理请求的<br>Controller。<br>调用处理器 Controller<br>(3) DispatcherServlet 将请求提交到 Controller。<br>Controller 调用业务逻辑处理后,返回 ModelAndView<br>(4)(5)调用业务处理和返回结果:Controller 调用业务逻辑处理后,返回 ModelAndView。<br>DispatcherServlet 查询 ModelAndView<br>(6)(7)处理视图映射并返回模型: DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,<br>找到 ModelAndView 指定的视图。<br>ModelAndView 反馈浏览器 HTTP<br>(8) Http 响应:视图负责将结果显示到客户端。</p>]]></content>
<summary type="html">
<h4 id="SpringMVC-原理"><a href="#SpringMVC-原理" class="headerlink" title="SpringMVC 原理"></a>SpringMVC 原理</h4><p>Spring 的模型-视图-控制器(MVC)框架是围绕一个
</summary>
</entry>
<entry>
<title>java-nio</title>
<link href="http://yoursite.com/2020/10/07/java-nio/"/>
<id>http://yoursite.com/2020/10/07/java-nio/</id>
<published>2020-10-07T22:06:16.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<h3 id="NIO-的非阻塞"><a href="#NIO-的非阻塞" class="headerlink" title="NIO 的非阻塞"></a>NIO 的非阻塞</h3><p>IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有<br>一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO 的非阻塞模式,<br>使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可<br>用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以<br>继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它<br>完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上<br>执行 IO 操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。</p><h4 id="Selector"><a href="#Selector" class="headerlink" title="Selector"></a>Selector</h4><p>Selector 能够检测多个注册的通道上是否有事件发生,如果事件发生,便获取事件然后针对每个事件进行相应的相应处理<br>这样一个单线程就可以处理多个通道,也就是管理多个连接,</p>]]></content>
<summary type="html">
<h3 id="NIO-的非阻塞"><a href="#NIO-的非阻塞" class="headerlink" title="NIO 的非阻塞"></a>NIO 的非阻塞</h3><p>IO 的各种流是阻塞的。这意味着,当一个线程调用 read() 或 write()时,该线程
</summary>
</entry>
<entry>
<title>jvm-内存回收</title>
<link href="http://yoursite.com/2020/10/06/jvm-%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6/"/>
<id>http://yoursite.com/2020/10/06/jvm-内存回收/</id>
<published>2020-10-06T23:18:26.000Z</published>
<updated>2021-05-09T18:27:16.099Z</updated>
<content type="html"><![CDATA[<p>对空间基本结构<br>eden s0 s1 tentired<br>新生代GC(MinorGC)<br>老年代GC(MajorGC)</p><h4 id="判断对象是否会被回收"><a href="#判断对象是否会被回收" class="headerlink" title="判断对象是否会被回收"></a>判断对象是否会被回收</h4><p>引用技术(循环引用)<br><code>GC Root</code></p><h4 id="引用类型"><a href="#引用类型" class="headerlink" title="引用类型"></a>引用类型</h4><ul><li>强引用 </li><li>软引用 内存不足会被回收,如果软引用对象被回收虚拟机会把这个软引用加入到与之相关联的队列中</li><li>弱引用</li><li>虚引用</li></ul>]]></content>
<summary type="html">
<p>对空间基本结构<br>eden s0 s1 tentired<br>新生代GC(MinorGC)<br>老年代GC(MajorGC)</p>
<h4 id="判断对象是否会被回收"><a href="#判断对象是否会被回收" class="headerlink" title
</summary>
</entry>
<entry>
<title>java-collection</title>
<link href="http://yoursite.com/2020/10/06/java-collection/"/>
<id>http://yoursite.com/2020/10/06/java-collection/</id>
<published>2020-10-06T22:30:03.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<h4 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h4><p>RandomAccess 空接口<br>数组是天然支持随机访问的, 时间复杂度为O(1) 所以称为快速随机访问,</p>]]></content>
<summary type="html">
<h4 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h4><p>RandomAccess 空接口<br>数组是天然支持随机访问的, 时间复杂度为O(1) 所以称为快速随机访问,</p>
</summary>
</entry>
<entry>
<title>java-exception</title>
<link href="http://yoursite.com/2020/10/06/java-exception/"/>
<id>http://yoursite.com/2020/10/06/java-exception/</id>
<published>2020-10-06T22:19:45.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<p>Throwable Error VirtualMachineeError<br> AWTError<br> Exception IOException<br> RunntimeException</p><p>异常处理</p><p>try: 用于捕获异常<br>catch: 处理try捕获到的异常<br>finally: </p><p>当try与finally 都有return语句时, 在方法返回之前, finally语句将被执行 并且finally语句的返回值会覆盖原始返回值</p>]]></content>
<summary type="html">
<p>Throwable Error VirtualMachineeError<br> AWTError<br> Exception IOException<br>
</summary>
</entry>
<entry>
<title>文件编码</title>
<link href="http://yoursite.com/2020/07/24/%E6%96%87%E4%BB%B6%E7%BC%96%E7%A0%81/"/>
<id>http://yoursite.com/2020/07/24/文件编码/</id>
<published>2020-07-24T10:24:06.000Z</published>
<updated>2021-05-09T18:27:16.127Z</updated>
<summary type="html">
</summary>
</entry>
<entry>
<title>android-xposed</title>
<link href="http://yoursite.com/2020/07/24/android-xposed/"/>
<id>http://yoursite.com/2020/07/24/android-xposed/</id>
<published>2020-07-24T07:19:56.000Z</published>
<updated>2021-05-09T18:27:16.079Z</updated>
<content type="html"><![CDATA[<p>[link]<a href="https://bbs.pediy.com/thread-257844.htm" target="_blank" rel="noopener">https://bbs.pediy.com/thread-257844.htm</a></p>]]></content>
<summary type="html">
<p>[link]<a href="https://bbs.pediy.com/thread-257844.htm" target="_blank" rel="noopener">https://bbs.pediy.com/thread-257844.htm</a></p>
</summary>
<category term="xposed" scheme="http://yoursite.com/tags/xposed/"/>
</entry>
<entry>
<title>android-magisk</title>
<link href="http://yoursite.com/2020/07/23/android-magisk/"/>
<id>http://yoursite.com/2020/07/23/android-magisk/</id>
<published>2020-07-23T19:04:12.000Z</published>
<updated>2021-05-09T18:27:16.079Z</updated>
<content type="html"><![CDATA[<p>magisk 获取root流程</p><ul><li>解锁bootloader或者通过特殊方式绕过AVB校验。<ul><li>解锁bootloader方法<ul><li>进入开发者模式,选择允许OEM解锁</li><li>重启进入fastboot,执行fastboot flashing unlock ,解锁后在启动过程如果出现镜像校验失败也会继续启动,因此就可以刷入非官方镜像</li></ul></li></ul></li><li>从官方的update包中提取boot.img, 通过magisk工具对boot.img打补丁,在ramdisk中植入新的init程序,使用改init引导系统启动。</li><li>将boot.img刷入手机中,即完成root,可以通过magisk APK管理root授权</li></ul>]]></content>
<summary type="html">
<p>magisk 获取root流程</p>
<ul>
<li>解锁bootloader或者通过特殊方式绕过AVB校验。<ul>
<li>解锁bootloader方法<ul>
<li>进入开发者模式,选择允许OEM解锁</li>
<li>重启进入fastboot,执行fastbo
</summary>
<category term="magisk" scheme="http://yoursite.com/tags/magisk/"/>
</entry>
<entry>
<title>ida-调试</title>
<link href="http://yoursite.com/2020/07/22/ida-%E8%B0%83%E8%AF%95/"/>
<id>http://yoursite.com/2020/07/22/ida-调试/</id>
<published>2020-07-22T18:12:06.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<p>### adb 调试步骤</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">adb shell</span><br><span class="line">su</span><br><span class="line">cd /data/local/tmp</span><br><span class="line">./android_server</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> </span></span><br><span class="line">adb forward tcp:23946 tcp:23946</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>### adb 调试步骤</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</
</summary>
</entry>
<entry>
<title>android-adb</title>
<link href="http://yoursite.com/2020/07/09/android-adb/"/>
<id>http://yoursite.com/2020/07/09/android-adb/</id>
<published>2020-07-09T21:58:43.000Z</published>
<updated>2021-05-09T18:27:16.079Z</updated>
<content type="html"><![CDATA[<p><code>adb install [-l] [-r] [-s] <file></code></p><ul><li>EN push this package file to the device and install it</li><li>CHS 给设备安装软件<ul><li>(<code>-l</code> means forward-lock the app) #锁定该程序</li><li>(<code>-r</code> means reinstall the app, keeping its data) #重新安装该程序,保存数据</li><li>(<code>-s</code> means install on SD card instead of internal storage) #安装在SD卡内,而不是设备内部存储</li></ul></li></ul><p><code>adb uninstall [-k] <package></code></p><ul><li>EN remove this app package from the device</li><li>CHS 从设备删除程序包<ul><li>(<code>-k</code> means keep the data and cache directories) #不删除程序运行所产生的数据和缓存目录(如软件的数据库文件)</li></ul></li></ul><h3 id="清除adb-logcat-缓存"><a href="#清除adb-logcat-缓存" class="headerlink" title="清除adb logcat 缓存"></a>清除adb logcat 缓存</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb logcat -c</span><br></pre></td></tr></table></figure><h3 id="adb-输出指定包的日志-并重定向"><a href="#adb-输出指定包的日志-并重定向" class="headerlink" title="adb 输出指定包的日志 并重定向"></a>adb 输出指定包的日志 并重定向</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb -d logcat com.xx.mm:I *:* | Tee-Object login0713-03.log</span><br></pre></td></tr></table></figure><h3 id="dx-bat"><a href="#dx-bat" class="headerlink" title="dx.bat"></a>dx.bat</h3><p>cd MyApplication\app\build\intermediates\javac\debug\classes<br>build-tools\29.0.3\dx.bat –dex –output=./classes.dex .\com\example\myapplication\ExampleFunction.class</p><p>### 链接指定设备</p><p>adb connect [ip]<br>adb disconnect [ip]<br>adb -s shell 设备id[ip:port]</p>]]></content>
<summary type="html">
<p><code>adb install [-l] [-r] [-s] &lt;file&gt;</code></p>
<ul>
<li>EN push this package file to the device and install it</li>
<li>CHS 给设备
</summary>
<category term="adb" scheme="http://yoursite.com/tags/adb/"/>
<category term="android" scheme="http://yoursite.com/tags/android/"/>
</entry>
<entry>
<title>python2.7 pip</title>
<link href="http://yoursite.com/2020/07/09/python2-7-pip/"/>
<id>http://yoursite.com/2020/07/09/python2-7-pip/</id>
<published>2020-07-09T01:02:31.000Z</published>
<updated>2021-05-09T18:27:16.119Z</updated>
<content type="html"><![CDATA[<h4 id="python2-pip-install"><a href="#python2-pip-install" class="headerlink" title="python2 pip install"></a>python2 pip install</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cd .\Scripts\</span><br><span class="line">.\easy_install.exe pip</span><br><span class="line">cd ..</span><br><span class="line"> .\python.exe -m pip install six</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h4 id="python2-pip-install"><a href="#python2-pip-install" class="headerlink" title="python2 pip install"></a>python2 pip install</h4><figu
</summary>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
</entry>
<entry>
<title>java-gradle 编译问题记录</title>
<link href="http://yoursite.com/2020/07/05/java-gradle-%E7%BC%96%E8%AF%91%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
<id>http://yoursite.com/2020/07/05/java-gradle-编译问题记录/</id>
<published>2020-07-05T04:08:00.000Z</published>
<updated>2021-05-09T18:27:16.087Z</updated>
<content type="html"><![CDATA[<p>gradle tookchain => jdk14<br>gradle config targetCompatibility=1.8<br>gradle config sourceCompatibility=1.8</p><p>ByteBuffer<br><code>NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer</code></p><p>link[<a href="https://github.com/eclipse/jetty.project/issues/3244" target="_blank" rel="noopener">https://github.com/eclipse/jetty.project/issues/3244</a>]</p><p>link[<a href="https://stackoverflow.com/questions/61267495/exception-in-thread-main-java-lang-nosuchmethoderror-java-nio-bytebuffer-flip" target="_blank" rel="noopener">https://stackoverflow.com/questions/61267495/exception-in-thread-main-java-lang-nosuchmethoderror-java-nio-bytebuffer-flip</a>]</p><h3 id="构建工具-运行环境-配置jdk版本要一致啊。"><a href="#构建工具-运行环境-配置jdk版本要一致啊。" class="headerlink" title="构建工具 运行环境 配置jdk版本要一致啊。"></a>构建工具 运行环境 配置jdk版本要一致啊。</h3>]]></content>
<summary type="html">
<p>gradle tookchain =&gt; jdk14<br>gradle config targetCompatibility=1.8<br>gradle config sourceCompatibility=1.8</p>
<p>ByteBuffer<br><code
</summary>
<category term="java" scheme="http://yoursite.com/tags/java/"/>
<category term="gradle" scheme="http://yoursite.com/tags/gradle/"/>
</entry>
<entry>
<title>go-交叉编译配置</title>
<link href="http://yoursite.com/2020/06/27/go-%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E9%85%8D%E7%BD%AE/"/>
<id>http://yoursite.com/2020/06/27/go-交叉编译配置/</id>
<published>2020-06-27T13:50:59.000Z</published>
<updated>2021-05-09T18:27:16.083Z</updated>
<content type="html"><![CDATA[<div id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look."> <div class="hbe-input-container"> <input type="password" id="hbePass" placeholder="" /> <label for="hbePass">输入密码,查看文章</label> <div class="bottom-line"></div> </div> <script id="hbeData" type="hbeData" data-hmacdigest="9312ee2f140805920ab6ec445f372c5c5eb838844c6176b081ce6adf6e63ba8b">ed4b4d08d2b999f4eccfa5a7ab6471520d96efcde2b1e713275603d559823485798494c72eab0fd65bbd6c9263733dff2daf1b2b629ceb2f329884ecc9811b60da8ea07d86141f453d1202a8296817ef4e5c9627c3b3fba1d428ec1a5469dcabeedac565b9f72d9ef2580cc1b09b0bbaaa3ff23fbd97b85f5c02fd559e67df297390109d6f6fa29c607c3c83d1b79e0db231785b51084ed29327ed22d0fa96d063009e4508247cba84be5e6faa43264eb87f3cc0c98afd61edea7f417cda7a4f2027d0d8a4d1dc0720a7a076c172100d2e828016b615044a53b3e2ddb86e4a41b9afa8158fff0d9d56c3486ba934a68c06e56b00e5a185ee46418ee409a525a2b5341f608e81b075c77a691408f298dbe8e0fdfd5ac33af041dcc625a37c5a1b73ea1dc802cbe976d27b8a60574cda2b2b7987e0ff2f027a7abb0ae2548b2604d4145e1ae860c8541218e327c3744cb30ea5031750b133a544aa74f439da2d75e013a8083420d5a35aaa3bf4d3b2f39ae86238132d155df274f23bbc72c362caa3ee5d888c4f53ef7e24729f87af2ab22fbad8d1e9d0070441dddd36ce0d9b7ffa15e3f7a714f0fd69bdcf6cc4b3bbaf0f8b85244386783146e8a2a2a18156f8ad4504339ace2ca8a3068284444df25fc6b21113219962918a276b132a43f4d45bc58c60acd599d256e1b17d77052443e0bed1b058a4a42adb95d3b1b64f77a1f43e67c629fb6775e6909267e87b2cfaca9b79a7b589bba91beac8324c9fceb0ac7d15e4cf874c1456e84dc2e498c8c457fdf9afe2cf8f7c269d7d1b5be4740cbba16e29b6ffc8b4924bca2aded0e263ed4a7477af8508866bd9950f369f48fb2c963f1c33591810751dcd3f5f2045c3e7cb453c5bdcc324182757b4a7d3fd8ad91e6f9f9220c9daa7d7dca263a18da85cf1cfe00f7e4c872574157ec0381b32a1b810880eb2eb63f519447015e5bf617215f0c8d48da33b76fa2048e922be2d24b5de5595d62c7a7c22125a9a27b3247a7e94e3cabb6c3571a22ced6cf846a7198a463098145ed056b7bb44e640459a74bb3d18ef69d9120e778dc002cacce0a67261f6cf1601030efdda379cfe9a689125a7fd7e76c5509f4453b7ca081a9f4d09b0e03f5082141d8a8927e27ee5a3c0576ee5442e3d1eaf234c220bfa0e50d428b53f5f0b1366e4075c2ec3fa55ad816d2c1cc50e499d89eb3645646d578c54c0f8c67b7e507ec2e41c8d98b7e54f5d7288d02afafb102609a1bedeb627fa313e88b127f45d646ce6ea5d26dda88517a3f7dc5ef1c4fd6f9a054637ae90765643ce3103e871dca5af9d66c22fd6dd9807d0fd9b963acb147b67a5b3283a82f94dfe9a84497e2d36b99ff2ac4d535c31933c83a7abff3e2b5912e96fe2f59c21551ea8e9bd9c44ed9a6f87347afd10de1582c4ef3adae4bbc23bc30a42ccd653286a73ac795fd647078eb36cc77a27f5cc5aeb02a4636c025052c8753894ba1d4bf363bef8e69b3da78e2a410ae66a1eec86c74c4b1fcef5e3323af4e3018ef20a1cb46bfaa750dea2d91be24464d64181532bf21d2af508b8393a430353bd1dc6cc38e74a79183ec5c87e3f19b4d8199e2427019fe51b128fd1753832a05036c16c06068330cc2cfc71ef02d94ac3ce9cdcb4ed5952274fa5c924b05c252108414e156522dd1a971431df8c572c40c634717dea35011726998e9f5b31c09e183ddbcbd00056c59db1b7e52c174190887865a0a10ce225767f0cfb16366d3b8d853d47e5bee7821f917c2a0cbfdeca68b8d53d60ec00d14324c1fa864d3e3c345a7d550fe46801ec62e4f958f34e6c1856ac46d1322596cc69743304f7532e49eef27ec5e41abab99ecab7c86144cd8695684e9f5b705ac6adaf66ddf3966f87b2342c44ec07a0950a01be7c89cd36fcf55dfe0e23718557719b406fd93d795f0f12090884b80bebed164f8201fca21ce4772833831112c387047f22f25b695e69a2028a00fe2933ef21e6711c314a59fd805252a5360ff2e5d2fa8e0e2279523b90b3c694501fc530c0a1fd7222e2d8fb10098481b462bbed5241b1c088472145a6f4cfd12050465c7fe5ae4df07e087aa07cc4ae6bb0ce5bd6e8c82398fe4b0685fa6cc6e8c17141e81258d718955a1ee904769a624c5e9d8db6a656340057dfb755b89bc7e9c062ebf0871263616bef38290ce0f5da09819333fe267f6449a1fcb914fb9978f37c79e790d4e265393306d47ac59651cdd5c5eaf6c5f346624a4d52c43a85e348ff15c22def3a0a6ddcfeadc4cc4084840f34041cfdf2d2477bf4885ed6dbf17c42d6a7f7004fa78ee95189d6c88718c335d064f67eedecba3a1b9fc6e5c6aab7f27b9501e480533b08587696a6868d8a13f8baec91a74cb23107c9425fe881b51362dfce65ec96d9cc6193318624e4ec0a2aef98684788e98a2894594fb4f2f7278381d77b89f1db1956bcb3619c218f84809a6573c9ef644253cf75fbe0c21cd2de31bdc5f2e7fe58618f6dfe4d756c891a9e123c9ea05221a031cd99e750fe0fb87fa42a641e93bd4b58661e4f07e68b0c9a1bb66c6e62d92281e7f0e238bd8ae27ab413d06a2da5e83cf0d23d4b3a67d634d0d07ec881ad1bb6025337697bd6e38487609af702e64ec095b715cbea160f617d6771415cf28401f9abb97611605e04c2a654ffd35de7180f2b4c3696d9b451f58a62690084337e5fa867f945784207cf451a22e3c1adb3f6d61a1ef9709d4eabc8f84b58324feed0b5b80783afb385a6a125ae2c2b556d99dc4847ee17e5a763974e23e436c7032e8a5ae1131eca2e9eb5f3ea0a45a653eacb5d466d1ede0aa5c88e7c9d1aaaa67d32cd6b276ae0bc0d265a7412b99a14dc7ee0dc14c50957bf01dba7c60c243f1e9d63fc4e0fd3152c9e1aaee450b80bcfb3bd8e1a6e0288b032cfebb1ef4739c8c7d65003425dab8f9600d90342a303c406797521e6cc074f226e8f2a5428d22fce7bfc773c01ec2614e0749be997e0a9e8a68ad2e498073f8b70a6f066ce39ecfff251c5ea5f38ac935eccc84b1ef934934a5c00a1e27196f9f757b15e67b0a7977e0ad6386b61fb024025bf0d50004f3c87c2323b0ea24da0d35a9f4b6ac49a47a3b8053ba0e449e2dce19b687970e4409289aa3169f960d1ab2d7b964e475e6a8ded51af11db23de51fa3d7882d7d26328aa4a43e2927777293917abb1c3e3182b1b1860caee6500461afd0d0e8fc3299d05240a27a8a102eb78efe1d478719d59799ec6ffeccd784c8883bbd184affc77ebb27392c6962a090eeb498a72ec33993995081bb45df76c53051c7d2c0416a451fca7ee3904cb444b2792fe8c3669aaa92acfdcf2b6674b231772739a4fbbf60fe340de33082741e72e4a25117099b79b0feeaf07d66c75eb9da322433d03e5e3a7e973c124ff7eb59a92515854589d4ff0240d2aadccdec2255017785c2cae7580bb1c63050796b24a795e56b90636079e7afe8cf8f8b0ae31bc9a390621cfff2b61f8a366eec06a0ecbca78d8052eb3444e8ec20dc9b9b36b83e10b1c75ca05f409dd39f9db79d53485f053e14ad9f4a7816228aceb2d37603d91bc4c17ad2acf9c7cdbc3acdc66631e53c1b0c6fc8607e389de84fb43dbed3b62f7612e60c0e73a586e37984d1dae254c159e8ed0065c693602f2b04ec0cb82178132209d1dad0ee1dd44796bef8663e41250d6115493e83ba8274452b901bb94e3bed337ca8cfa16621278b5a94a632f43234a223456998bb5dd475a21e9a5c70ada3b71d642d8ddc0945e07ee49a59718075208f7bf1cb598f905833f</script></div><script src="/lib/blog-encrypt.js"></script><link href="/css/blog-encrypt.css" rel="stylesheet" type="text/css">]]></content>
<summary type="html">
密码: 111111
</summary>
<category term="go" scheme="http://yoursite.com/tags/go/"/>
</entry>
<entry>
<title>android-Art虚拟机启动流程</title>
<link href="http://yoursite.com/2020/06/19/android-Art%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/"/>
<id>http://yoursite.com/2020/06/19/android-Art虚拟机启动流程/</id>
<published>2020-06-19T10:09:02.000Z</published>
<updated>2021-05-09T18:27:16.079Z</updated>
<content type="html"><![CDATA[<p>主要大致流程<br>①Linux <code>init</code>进程解析配置脚本->②<code>app_process</code>(<code>zygote</code>进程对应的程序)->③ZygoteInit</p><p>① 解析配置脚本</p><img src="/2020/06/19/android-Art虚拟机启动流程/init_android.png" title="init_android"><p><code>service zygote</code>:它告诉<code>init</code>进程,现在我们要配置一个名为<code>zygote</code>的服务.</p><p><code>/system/bin/app_process</code>: 声明zygote进程对应的文件路径.init创建服务的处理逻辑很简单,就是启动(fork)一个子进程来运行指定的程序.对zygote服务而言这个程序就是<code>/system/bin/app_process</code>.</p><p><code>-Xzygote/system/bin--zygote--start-system-server</code>:传递给<code>app_process</code>的启动参数.</p><a id="more"></a><p>②<code>app_process</code> 创建<br><code>frameworks\base\cmds\app_process.cpp->main</code>函数</p><img src="/2020/06/19/android-Art虚拟机启动流程/app_process.png" title="app_process"><img src="/2020/06/19/android-Art虚拟机启动流程/runtime_start.png" title="runtime_start"><p><code>frameworks\base\core\jni\AndroidRuntime.cpp->start</code>函数</p><p>核心函数为: <code>init</code>,<code>startVm</code><br>三个函数主要功能:</p><ol><li><code>JNI_GetDefaultJavaVMInitArgs</code> – 获取虚拟机的默认初始化参数</li><li><code>JNI_CreateJavaVM</code> – 在进程中创建虚拟机实例</li><li><code>JNI_GetCreatedJavaVMs</code> – 获取进程中创建的虚拟机实例</li></ol><p><code>ART</code>像<code>Dalvik</code>一样,都实现<code>Java</code>虚拟机接口,这三个接口也是<code>ART</code>虚拟机核心接口.</p><p><code>startVm</code>函数很复杂牵扯逻辑也很多,不逐一描述了.</p><p>③<code>ZygoteInit</code></p><p>继续查看 <code>frameworks\base\core\jni\AndroidRuntime.cpp->start</code>函数</p><p>参数<code>className</code>的值等于”<code>com.android.internal.os.ZygoteInit</code>“,本地变量<code>env</code>是从调用另外一个成员函数<code>startVm</code>创建的<code>ART</code>虚拟机获得的<code>JNI</code>接口.函数的目标就是要找到一个名称为<code>com.android.internal.os.ZygoteInit</code>的类,以及它的静态成员函数<code>main</code>,然后就以这个函数为入口,开始运行ART虚拟机.为此,函数执行了以下步骤:</p><p>① 调用JNI接口<code>FindClass</code>加载<code>com.android.internal.os.ZygoteInit</code>类.</p><p>② 调用JNI接口 <code>GetStaticMethodID</code> 找到<code>com.android.internal.os.ZygoteInit</code> 类的静态成员函数<code>main</code>.</p><p>③ 调用JNI接口<code>CallStaticVoidMethod</code>开始执行<code>com.android.internal.os.ZygoteInit</code>类的静态成员函数<code>main</code>.</p><p>下面看看 <code>Xposed</code> 是如何做拦截的</p><p>打开 <code>Xposed</code> 项目</p><img src="/2020/06/19/android-Art虚拟机启动流程/androidmk.png" title="androidmk"><p>大于21编译走的是 <code>app_main2.cpp</code> 看看 具体改动了哪些<br>经过查阅,被修改的 <code>main</code> 函数,一共有两个地方.</p><p>其一,红框的地方 是判断是否是 <code>Xposed</code>版本的虚拟机<br><img src="/2020/06/19/android-Art虚拟机启动流程/虚拟机判断.png" title="虚拟机判断"></p><p>在解析开启启动 <code>init</code> 脚本的时候 添加了 <code>--xposedversion</code> 版本号的命令</p><p>这块启动的已经是自定义的虚拟机了<br><img src="/2020/06/19/android-Art虚拟机启动流程/启动自定义虚拟机.png" title="启动自定义虚拟机"></p><p>handleOptions函数</p><p>第二个地方在 <code>start</code> 函数这块,先看看 原函数.</p><p>也是在这个地方 进行的初始化 判断是否初始化成功 .</p><p><code>initialize</code> 函数返回的是否加载成功的 一个全局变量 <code>isXposedLoaded</code></p><img src="/2020/06/19/android-Art虚拟机启动流程/initialize函数.png" title="initialize函数"><img src="/2020/06/19/android-Art虚拟机启动流程/xposed自定义的数据结构体.png" title="xposed自定义的数据结构体"><p>初始化完毕以后开始调用真正的 <code>start</code> 函数<br>下面看 <code>runtimeStart</code> 函数</p><p>这块很有趣 在 <code>libart.so</code> 里面根据符号表信息尝试拿到 <code>Android::start</code> 函数<br>上面这些只要有一步失败了,在刷入的时候就可能变砖.<br>如果获取到了,则可以直接通过函数指针调用,主要是针对一些特殊的安卓版本号.</p><p>如果都没有找到 可以看到 Log会打印 .</p><p>“app_process: could not locate AndroidRuntime::start() method.”<br><img src="/2020/06/19/android-Art虚拟机启动流程/runtimeStart函数.png" title="runtimeStart函数"></p><p>(这个地方有个小技巧,可以对so文件里面的全部函数名字进行逐一字符判断,<br>比如可以对这个字符串 判断 是否含有 <code>RuntimeStart</code> 这几个字符,来绕过因为编译优化字符串不同问题)</p><p>这样一来完美替换了原虚拟机.<br>在新的虚拟机里面会将 <code>XposedBridge.jar</code> 进行注入,这么一来,所有被 <code>Xposed fork</code> 的进程都具备了<code>XposedBridge.jar</code> 的代码 .</p><p>问题3:<br>当我们 <code>findAndHookMethod</code> 一个函数以后 <code>Xposed</code> 是怎么处理的?</p><p>打开 <code>XposedBridge</code> 项目</p><p>找到 <code>findAndHookMethod</code></p><p><code>link:https://bbs.pediy.com/thread-257844.htm</code></p>]]></content>
<summary type="html">
<p>主要大致流程<br>①Linux <code>init</code>进程解析配置脚本-&gt;②<code>app_process</code>(<code>zygote</code>进程对应的程序)-&gt;③ZygoteInit</p>
<p>① 解析配置脚本</p>
<img src="/2020/06/19/android-Art虚拟机启动流程/init_android.png" title="init_android">
<p><code>service zygote</code>:它告诉<code>init</code>进程,现在我们要配置一个名为<code>zygote</code>的服务.</p>
<p><code>/system/bin/app_process</code>: 声明zygote进程对应的文件路径.init创建服务的处理逻辑很简单,就是启动(fork)一个子进程来运行指定的程序.对zygote服务而言这个程序就是<code>/system/bin/app_process</code>.</p>
<p><code>-Xzygote/system/bin--zygote--start-system-server</code>:传递给<code>app_process</code>的启动参数.</p>
</summary>
<category term="android" scheme="http://yoursite.com/tags/android/"/>
<category term="xposed" scheme="http://yoursite.com/tags/xposed/"/>
</entry>
</feed>