-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
3525 lines (1945 loc) · 200 KB
/
index.html
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
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="theme-next mist use-motion" lang="zh-Hans">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<script src="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script>
<link href="//cdn.bootcss.com/pace/1.0.2/themes/pink/pace-theme-flash.css" rel="stylesheet">
<style>
.pace .pace-progress {
background: #1E92FB; /*进度条颜色*/
height: 3px;
}
.pace .pace-progress-inner {
box-shadow: 0 0 10px #1E92FB, 0 0 5px #1E92FB; /*阴影颜色*/
}
.pace .pace-activity {
border-top-color: #1E92FB; /*上边框颜色*/
border-left-color: #1E92FB; /*左边框颜色*/
}
</style>
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.3" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.3">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.3" color="#222">
<meta name="keywords" content="Hexo, NexT" />
<link rel="alternate" href="/atom.xml" title="林距离" type="application/atom+xml" />
<meta name="description" content="记录生活的点点滴滴">
<meta property="og:type" content="website">
<meta property="og:title" content="林距离">
<meta property="og:url" content="http://bryancrash.site/index.html">
<meta property="og:site_name" content="林距离">
<meta property="og:description" content="记录生活的点点滴滴">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="林距离">
<meta name="twitter:description" content="记录生活的点点滴滴">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Mist',
version: '5.1.3',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":true,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="http://bryancrash.site/"/>
<title>林距离</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<a href="https://github.com/bryancrash"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://camo.githubusercontent.com/567c3a48d796e2fc06ea80409cc9dd82bf714434/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_left_darkblue_121621.png"></a>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">林距离</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle">人生没有白走的路,每一步都算数</p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section">
<i class="menu-item-icon fa fa-fw fa-user"></i> <br />
关于
</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section">
<i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
标签
</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section">
<i class="menu-item-icon fa fa-fw fa-list"></i> <br />
分类
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
<li class="menu-item menu-item-photos">
<a href="/photos/" rel="section">
<i class="menu-item-icon fa fa-fw fa-photo"></i> <br />
相册
</a>
</li>
<li class="menu-item menu-item-search">
<a href="javascript:;" class="popup-trigger">
<i class="menu-item-icon fa fa-search fa-fw"></i> <br />
搜索
</a>
</li>
</ul>
<div class="site-search">
<div class="popup search-popup local-search-popup">
<div class="local-search-header clearfix">
<span class="search-icon">
<i class="fa fa-search"></i>
</span>
<span class="popup-btn-close">
<i class="fa fa-times-circle"></i>
</span>
<div class="local-search-input-wrapper">
<input autocomplete="off"
placeholder="搜索..." spellcheck="false"
type="text" id="local-search-input">
</div>
</div>
<div id="local-search-result"></div>
</div>
</div>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://bryancrash.site/2018/04/05/虚拟机类加载机制/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="bryan">
<meta itemprop="description" content="">
<meta itemprop="image" content="/image/bryan.jpg">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="林距离">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/04/05/虚拟机类加载机制/" itemprop="url">虚拟机类加载机制</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-04-05T12:22:11+08:00">
2018-04-05
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Java虚拟机/" itemprop="url" rel="index">
<span itemprop="name">-Java虚拟机</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/04/05/虚拟机类加载机制/#SOHUCS" itemprop="discussionUrl">
<span id="url::http://bryancrash.site/2018/04/05/虚拟机类加载机制/" class="cy_cmt_count" data-xid="2018/04/05/虚拟机类加载机制/" itemprop="commentsCount" ></span>
</a>
<div class="post-wordcount">
<span class="post-meta-item-icon">
<i class="fa fa-file-word-o"></i>
</span>
<span class="post-meta-item-text">字数统计:</span>
<span title="字数统计">
5,260
</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-clock-o"></i>
</span>
<span class="post-meta-item-text">阅读时长 ≈</span>
<span title="阅读时长">
18
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h4 id="类加载的时机"><a href="#类加载的时机" class="headerlink" title="类加载的时机"></a>类加载的时机</h4><p>类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用的卸载7个过程,其中验证,准备,解析3个部分统称为连接.<br>对于初始化阶段,虚拟机规范则是严格规定了有且仅有5种情况必须立即对类进行“初始化”:</p>
<ol>
<li>遇到new、getstatic、putstatic、或invokestatic这4个字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4跳指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的关键字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候</li>
<li>使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化</li>
<li>当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化</li>
<li>当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类</li>
<li>当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_invokestatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化</li>
</ol>
<p>有且仅有这5种场景种的行为称为对一个类进行主动引用,除此之外,所有引用类的方法都不会触发初始化,称为被动引用</p>
<h4 id="类加载的过程"><a href="#类加载的过程" class="headerlink" title="类加载的过程"></a>类加载的过程</h4><h5 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h5><p>加载” 是 “类加载”(Class Loading)过程的一个阶段,希望读者没有混淆这两个看起来很相似的名词。在加载阶段,虚拟机需要完成以下 3 件事情:</p>
<ol>
<li>通过一个类的全限定名来获取定义此类的二进制字节流。</li>
<li>将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构</li>
<li>在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。</li>
</ol>
<p>虚拟机规范的这 3 点要求其实并不算具体,因此虚拟机实现与具体应用的灵活度都是相当大的。例如 “通过一个类的全限定名来获取定义此类的二进制字节流” 这条,它没有指明二进制字节流要从一个 Class 文件中获取,准确地说是根本没有指明要从哪里获取、怎样获取。虚拟机设计团队在加载阶段搭建了一个相当开放的、广阔的 “舞台”,Java 发展历程中,充满创造力的开发人员则再这个 “舞台” 上玩出了各种花样,许多举足轻重的 Java 技术都建立在这一基础之上,例如:</p>
<ol>
<li>从 ZIP 包中读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。</li>
<li>从网络中获取,这种常见最典型的应用就是 Applet。</li>
<li>运行时计算生成,这种常见使用得最多的就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 “*$Proxy” 的代理类的二进制字节流。</li>
<li>由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。</li>
<li>从数据库中读取,这种场景相对少见些,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。</li>
</ol>
<p>相对于类加载过程的其他阶段,一个非数组类的加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的,因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式(即重写一个类加载器的loadClass() 方法)。</p>
<p>对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,它是由 Java 虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型(Element Type,指的是数组去掉所有纬度的类型)最终是要考类加载器去创建,一个数组类(下面简称为 C)创建过程要遵循以下规则:</p>
<ol>
<li>如果数组的组件类型(Component Type,指的是数组去掉一个纬度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组 C 将在加载该组件类型的类加载器的类名称空间上被标识(这点很重要,一个类必须与类加载器一起确定唯一性)。</li>
<li>如果数组的组件类型不是引用类型(例如 int[] 数组),Java 虚拟机将会把数组 C 标记为与引导类加载器关联。</li>
<li>数组类的可见性与它的组件类型的可见性一直,如果组件类型不是引用类型,那数组类的可见性将默认为 public。</li>
</ol>
<p>加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构。然后在内存中实例化一个 java.lang.Class 类的对象(并没有明确规定是在 Java 堆中,对于 HotSpot 虚拟机而言,Class 对象比较特殊,它虽然是对象,但是存放在方法区里面),这个对象将作为程序访问方法区中的这些类型数据的外部接口。</p>
<p>加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。</p>
<h5 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h5><p>验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。</p>
<p>Java 语言本身是相对安全的语言(依然是相对于 C/C++ 来说),使用纯粹的 Java 代码无法做到诸如访问数组边界以外的数据、将一个对象转型为它并未实现的类型、跳转到不存在的代码行之类的事情,如果这样做了,编译器将拒绝编译。但前面已经说过,Class 文件并不一定要求用 Java 源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编写来产生 Class 文件。在字节码语言层面上,上述 Java 代码无法做到的事情都是可以实现的,至少语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。</p>
<p>验证阶段是非常重要的,这个阶段是否严谨,直接决定了 Java 虚拟机是否能承受恶意代码的攻击,从执行性能的角度上讲,验证阶段的工作量在虚拟机的类加载子系统中又占了相当大的一部分。《Java 虚拟机规范(第 2 版)》对这个阶段的限制、知道还是比较笼统的,规范中列举了一些 Class 文件格式中的静态和结构化约束,如果验证到输入的字节流不符合 Class 文件格式的约束,虚拟机就应抛出一个 java.lang.VerifyError 异常或其子类异常,但具体应当检查哪些方面,如何检查,何时检查,都没有足够具体的要求和明确的说明。知道 2011 年发布的《Java 虚拟机规范(Java SE 7 版)》,大幅增加了描述验证过程的篇幅(从不到 10 页增加到 130 页),这时约束和验证规则才变得具体起来。受篇幅所限,无法逐条规则去讲解,但从整体上看,验证阶段大致上会完成下面 4 个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。</p>
<ol>
<li><strong>文件格式验证</strong></li>
</ol>
<p>第一阶段要验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。这一阶段可能包括下面这些验证点:</p>
<ul>
<li>是否以魔数 0xCAFEBABE 开头。</li>
<li>主、次版本号是否在当前虚拟机处理范围之内。</li>
<li>常量池的常量中是否有不被支持的常量类型(检查常量 tag 标志)。</li>
<li>CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据。</li>
<li>Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息。</li>
</ul>
<p>实际上,第一阶段的验证点还远不止这些,上面这些知识从 HotSpot 虚拟机中摘抄的一小部分内容,该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储,所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流。</p>
<ol>
<li><strong>元数据验证</strong></li>
</ol>
<p>第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求,这个阶段可能包括的验证点如下:</p>
<ul>
<li>这个类是否有父类(除了 java.lang.Object 之外,所有的类都应当有父类)。</li>
<li>这个类的父类是否继承了不允许被继承的类(被 final 修饰的类)。</li>
<li>如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。</li>
<li>类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的 final 字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。</li>
</ul>
<p>第二阶段的主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java 语言规范的元数据信息。</p>
<ol>
<li><strong>字节码验证</strong></li>
</ol>
<p>第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。例如:</p>
<ul>
<li>保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作栈放置了一个 int 类型的数据,使用时却按 long 类型来加载入本地变量表中。</li>
<li>保证跳转指令不会跳转到方法体以外的字节码指令上。</li>
<li>保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险和不合法的。</li>
</ul>
<p>如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。即使字节码验证之中进行了大量的检查,也不能保证这一点。这里涉及了离散数学中一个很著名的问题 “Halting Problem”(停机问题):通俗一点的说法就是,通过程序去校验程序逻辑是无法做到绝对准确的——不能通过程序准确地检查出程序是否能在有限的时间之内结束运行。</p>
<p>由于数据流验证的高复杂性,虚拟机设计团队为了避免过多的时间消耗在字节码验证阶段,在 JDK 1.6 之后的 javac 编译器和 Java 虚拟机中进行了一项优化,给方法体的 Code 属性的属性表中增加了一项名为 “StackMapTable” 的属性,这项属性描述了方法体中所有的基本快(Basic Block,按照控制流拆分的代码块)开始时本地变量表和操作栈应有的状态,在字节码验证期间,就不需要根据程序推导这些状态的合法性,只需要检查 StackMapTable 属性中的记录是否合法即可。这样讲字节码验证的类型推导转变为类型检查从而节省一些时间。</p>
<p>理论上 StackMapTable 属性也存在错误或被篡改的可能,所以是否有可能在恶意篡改了 Code 属性的同时,也生成相应的 StackMapTable 属性来骗过虚拟机的类型校验则是虚拟机设计者值得思考的问题。</p>
<p> 在 JDK 1.6 的 HotSpot 虚拟机中提供了 -XX:-UseSplitVerifier 选项来关闭这项优化,或者使用参数 -XX:+FailOverToOldVerifier 要求在类型校验失败的时候退回到旧的类型推导方式进行校验。而在 JDK 1.7 之后,对于主版本大于 50 的 Class 文件,使用类型检查来完成数据流分析则是唯一的选择,不允许再退回到类型推导的校验方式。</p>
<ol>
<li><p><strong>符号引用验证</strong></p>
<p>最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转换动作将在连接的第三个阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验下列内容:</p>
</li>
</ol>
<ul>
<li>符号引用中通过字符串描述的全限定名是否能找到对应的类。</li>
<li>在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段</li>
<li>符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。</li>
</ul>
<p>符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,那么将会抛出一个 java.lang.IncompatibleClassChangeError 异常的子类,如 java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等。</p>
<p>对于虚拟机的类加载机制来说,验证阶段是一个非常重要的、但不是一定必要(因为对程序运行期没有影响)的阶段。如果所运行的全部代码(包括自己编写的及第三方包中的代码)都已经被反复使用和验证过,那么在实施阶段就可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。</p>
<h5 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h5><p>准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:</p>
<p>public static int value=123;<br>那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。<br>至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.</p>
<h5 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h5><p>解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。</p>
<h5 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h5><p>类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备极端,变量已经付过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主管计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器<clinit>()方法的过程.</clinit></p>
<p><clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:</clinit></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">public class Test</div><div class="line">{</div><div class="line"> static</div><div class="line"> {</div><div class="line"> i=0;</div><div class="line"> System.out.println(i);//这句编译器会报错:Cannot reference a field before it is defined(非法向前应用)</div><div class="line"> }</div><div class="line"> static int i=1;</div><div class="line">}</div></pre></td></tr></table></figure>
<p><clinit>()方法与实例构造器<init>()方法不同,它不需要显示地调用父类构造器,虚拟机会保证在子类<init>()方法执行之前,父类的<clinit>()方法方法已经执行完毕,回到本文开篇的举例代码中,结果会打印输出:SSClass就是这个道理。<br>由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。</clinit></clinit></init></init></clinit></p>
<p><clinit>()方法对于类或者接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生产<clinit>()方法。<br>接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。<br>虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有好事很长的操作,就可能造成多个线程阻塞,在实际应用中这种阻塞往往是隐藏的。</clinit></clinit></clinit></clinit></clinit></clinit></clinit></clinit></clinit></clinit></p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://bryancrash.site/2018/04/05/字节码指令/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="bryan">
<meta itemprop="description" content="">
<meta itemprop="image" content="/image/bryan.jpg">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="林距离">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/04/05/字节码指令/" itemprop="url">字节码指令</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-04-05T12:21:53+08:00">
2018-04-05
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Java虚拟机/" itemprop="url" rel="index">
<span itemprop="name">-Java虚拟机</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/04/05/字节码指令/#SOHUCS" itemprop="discussionUrl">
<span id="url::http://bryancrash.site/2018/04/05/字节码指令/" class="cy_cmt_count" data-xid="2018/04/05/字节码指令/" itemprop="commentsCount" ></span>
</a>
<div class="post-wordcount">
<span class="post-meta-item-icon">
<i class="fa fa-file-word-o"></i>
</span>
<span class="post-meta-item-text">字数统计:</span>
<span title="字数统计">
1,370
</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-clock-o"></i>
</span>
<span class="post-meta-item-text">阅读时长 ≈</span>
<span title="阅读时长">
5
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>Java虚拟机的指令由一个字节长度的,代表着某种特定操作含义的数字以及跟随其后的零至多个代表此操作所需参数而构成,由于Java虚拟机采用面向操作数栈而不是寄存器的架构,所以大多数的指令都不包含操作数,只有一个操作码。在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息,例如,iload指令用于从局部变量表中加载int型的数据到操作数栈中,而float指令加载的则是float类型的数据,这两条指令的操作在虚拟机内部可能会是由同一段代码来实现的,但在class文件中它们必须拥有各自独立的操作码。</p>
<p>对于大部分为与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。</p>
<h4 id="加载和存储指令"><a href="#加载和存储指令" class="headerlink" title="加载和存储指令"></a>加载和存储指令</h4><ul>
<li>将一个布局变量加载到操作栈:iload、iload<em><n>、lload、lload</n></em><n>、float、float<em><n>、dloat、dloat</n></em><n>、aload、alod_<n></n></n></n></li>
<li>将一个数值从操作数栈存储到局部变量表:istore、istore<em><n>、lstore、lstore</n></em><n>、fstore、fstore<em><n>、dstore、dstore</n></em><n>、astore、astore_<n></n></n></n></li>
<li>将一个常量加载到操作数栈的指令包括有:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst<em>m1、iconst</em><i>、lconst<em><l>、fconst</l></em><f>、dconst_<d></d></f></i></li>
<li>扩充局部变量表的访问索引的指令:wide</li>
</ul>
<h4 id="运算指令"><a href="#运算指令" class="headerlink" title="运算指令"></a>运算指令</h4><ul>
<li>加法指令:iadd、ladd、fadd、dadd</li>
<li>减法指令:isub、lsub、fsub、dsub</li>
<li>乘法指令:imul、lmul、fmul、dmul</li>
<li>除法指令:idiv、ldiv、fdiv、ddiv</li>
<li>求余指令:irem、lrem、frem、drem</li>
<li>取反指令:ineg、lneg、fneg、dneg</li>
<li>位移指令:ishl、ishr、iushr、lshl、lshr、lushr</li>
<li>按位或指令:ior、lor</li>
<li>按位与指令:iand、land</li>
<li>按位异或指令:ixor、lxor</li>
<li>局部变量自增指令:iinc</li>
<li>比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp<h3 id="类型转换指令"><a href="#类型转换指令" class="headerlink" title="类型转换指令"></a>类型转换指令</h3>Java虚拟机支持以下数值类型的宽化类型转换:</li>
</ul>
<ol>
<li>int类型到long,float或者double类型</li>
<li>long类型到float,double类型</li>
<li>float类型到double类型</li>
</ol>
<p>相对的,处理窄化类型转化时,必须显示地使用转化指令来完成,这些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。但是窄化类型转换很可能会造成精度丢失</p>
<h4 id="对象创建与访问指令"><a href="#对象创建与访问指令" class="headerlink" title="对象创建与访问指令"></a>对象创建与访问指令</h4><ul>
<li>创建类实例的指令:new</li>
<li>创建数组的指令:newarray,anewarray,multianewarray</li>
<li>访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者成为实例变量)的指令:getfield、putfield、getstatic、putstatic</li>
<li>把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload</li>
<li>将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore</li>
<li>取数组长度的指令:arraylength</li>
<li>检查类实例类型的指令:instanceof、checkcast<h3 id="操作数栈管理指令"><a href="#操作数栈管理指令" class="headerlink" title="操作数栈管理指令"></a>操作数栈管理指令</h3>Java虚拟机提供了一些用于直接操作操作数栈的指令,包括:</li>
<li>将操作数栈的栈顶一个或者两个元素出栈:pop,pop2</li>
<li>复制栈顶一个或两个数值并将复制值或者双份的复制值重新压入栈顶:dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2</li>
<li>将栈最顶端的两个数值互换:swap<h3 id="控制转换指令"><a href="#控制转换指令" class="headerlink" title="控制转换指令"></a>控制转换指令</h3></li>
<li>条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt, if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。</li>
<li>复合条件分支:tableswitch、lookupswitch</li>
<li>无条件分支:goto、goto_w、jsr、jsr_w、ret<h4 id="方法调用和返回指令"><a href="#方法调用和返回指令" class="headerlink" title="方法调用和返回指令"></a>方法调用和返回指令</h4></li>
<li>invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式</li>
<li>invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。</li>
<li>invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(§2.9)、私有方法和父类方法。</li>
<li>invokestatic指令用于调用类方法(static方法)。</li>
</ul>
<p>而方法返回指令则是根据返回值的类型区分的,包括有ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用</p>
<h4 id="异常处理指令"><a href="#异常处理指令" class="headerlink" title="异常处理指令"></a>异常处理指令</h4><p>Java中显示抛出异常的操作都由athrow指令来实现,除了throw语句显示抛出异常情况外,Java虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常情况时自动抛出</p>
<h4 id="同步指令"><a href="#同步指令" class="headerlink" title="同步指令"></a>同步指令</h4><p>Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都使用管道来支持的。方法级的同步时隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中。同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集有monitorenter和monitorexit两条指令来支持synchronized关键字的语句,正确实现synchronized关键字需要javac编译器与Java虚拟机两者共同协作支持</p>
</div>
<div>
</div>
<div>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://bryancrash.site/2018/04/05/类文件结构/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="bryan">
<meta itemprop="description" content="">
<meta itemprop="image" content="/image/bryan.jpg">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="林距离">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2018/04/05/类文件结构/" itemprop="url">类文件结构</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2018-04-05T12:21:33+08:00">
2018-04-05
</time>
</span>
<span class="post-category" >
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Java虚拟机/" itemprop="url" rel="index">
<span itemprop="name">-Java虚拟机</span>
</a>
</span>
</span>
<span class="post-comments-count">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-comment-o"></i>
</span>
<a href="/2018/04/05/类文件结构/#SOHUCS" itemprop="discussionUrl">
<span id="url::http://bryancrash.site/2018/04/05/类文件结构/" class="cy_cmt_count" data-xid="2018/04/05/类文件结构/" itemprop="commentsCount" ></span>
</a>
<div class="post-wordcount">
<span class="post-meta-item-icon">
<i class="fa fa-file-word-o"></i>
</span>
<span class="post-meta-item-text">字数统计:</span>
<span title="字数统计">
1,745
</span>
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-clock-o"></i>
</span>
<span class="post-meta-item-text">阅读时长 ≈</span>
<span title="阅读时长">
6
</span>
</div>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>时至今日,商业机构和开源机构已经在Java语言之外发展出一大批Java虚拟机之上运行的语言,如Clojure,Groovy,JRuby,Jython,Scala等。作为一个通用的,机器无关的执行平台,任何其他语言的实现者都可以将Java虚拟机作为语言的产品交付媒介。例如,使用Java编译器可以把Java代码编译为字节码的class文件,使用JRuby等其他语言的编译一样可以把程序代码编译成class文件,虚拟机并不关心class的来源是何种语言。为了节省空间,类文件中没有任何分隔符,各个数据项都是一个挨着一个紧凑排列的,所以其中无论是顺序还是数量等都是严格规定的,哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变,其文件格式采用一种。下面我们先看一下类文件的整体结构:</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>字节数</th>
</tr>
</thead>
<tbody>
<tr>
<td>魔数</td>
<td>4</td>
</tr>
<tr>
<td>次版本号</td>
<td>2</td>
</tr>
<tr>
<td>主版本号</td>