|
| 1 | +<?xml version="1.0" encoding="utf-8"?> |
| 2 | +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
| 3 | +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
| 4 | +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> |
| 5 | +<head> |
| 6 | +<title>Javaclass文件结构说明</title> |
| 7 | +<!-- 2017-09-17 周日 11:05 --> |
| 8 | +<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
| 9 | +<meta name="generator" content="Org-mode" /> |
| 10 | +<meta name="author" content="王月阳" /> |
| 11 | +<meta name="description" content="对Java编译生成的class文件中,local,stack的含义和Java字节码的运行过程做一些说明。" |
| 12 | + /> |
| 13 | +<meta name="keywords" content="JAVA CLASS 运行时栈帧 操作数栈 编译器 解释运行 后缀表达式" /> |
| 14 | +<style type="text/css"> |
| 15 | + <!--/*--><![CDATA[/*><!--*/ |
| 16 | + .title { text-align: center; } |
| 17 | + .todo { font-family: monospace; color: red; } |
| 18 | + .done { color: green; } |
| 19 | + .tag { background-color: #eee; font-family: monospace; |
| 20 | + padding: 2px; font-size: 80%; font-weight: normal; } |
| 21 | + .timestamp { color: #bebebe; } |
| 22 | + .timestamp-kwd { color: #5f9ea0; } |
| 23 | + .right { margin-left: auto; margin-right: 0px; text-align: right; } |
| 24 | + .left { margin-left: 0px; margin-right: auto; text-align: left; } |
| 25 | + .center { margin-left: auto; margin-right: auto; text-align: center; } |
| 26 | + .underline { text-decoration: underline; } |
| 27 | + #postamble p, #preamble p { font-size: 90%; margin: .2em; } |
| 28 | + p.verse { margin-left: 3%; } |
| 29 | + pre { |
| 30 | + border: 1px solid #ccc; |
| 31 | + box-shadow: 3px 3px 3px #eee; |
| 32 | + padding: 8pt; |
| 33 | + font-family: monospace; |
| 34 | + overflow: auto; |
| 35 | + margin: 1.2em; |
| 36 | + } |
| 37 | + pre.src { |
| 38 | + position: relative; |
| 39 | + overflow: visible; |
| 40 | + padding-top: 1.2em; |
| 41 | + } |
| 42 | + pre.src:before { |
| 43 | + display: none; |
| 44 | + position: absolute; |
| 45 | + background-color: white; |
| 46 | + top: -10px; |
| 47 | + right: 10px; |
| 48 | + padding: 3px; |
| 49 | + border: 1px solid black; |
| 50 | + } |
| 51 | + pre.src:hover:before { display: inline;} |
| 52 | + pre.src-sh:before { content: 'sh'; } |
| 53 | + pre.src-bash:before { content: 'sh'; } |
| 54 | + pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } |
| 55 | + pre.src-R:before { content: 'R'; } |
| 56 | + pre.src-perl:before { content: 'Perl'; } |
| 57 | + pre.src-java:before { content: 'Java'; } |
| 58 | + pre.src-sql:before { content: 'SQL'; } |
| 59 | + |
| 60 | + table { border-collapse:collapse; } |
| 61 | + caption.t-above { caption-side: top; } |
| 62 | + caption.t-bottom { caption-side: bottom; } |
| 63 | + td, th { vertical-align:top; } |
| 64 | + th.right { text-align: center; } |
| 65 | + th.left { text-align: center; } |
| 66 | + th.center { text-align: center; } |
| 67 | + td.right { text-align: right; } |
| 68 | + td.left { text-align: left; } |
| 69 | + td.center { text-align: center; } |
| 70 | + dt { font-weight: bold; } |
| 71 | + .footpara:nth-child(2) { display: inline; } |
| 72 | + .footpara { display: block; } |
| 73 | + .footdef { margin-bottom: 1em; } |
| 74 | + .figure { padding: 1em; } |
| 75 | + .figure p { text-align: center; } |
| 76 | + .inlinetask { |
| 77 | + padding: 10px; |
| 78 | + border: 2px solid gray; |
| 79 | + margin: 10px; |
| 80 | + background: #ffffcc; |
| 81 | + } |
| 82 | + #org-div-home-and-up |
| 83 | + { text-align: right; font-size: 70%; white-space: nowrap; } |
| 84 | + textarea { overflow-x: auto; } |
| 85 | + .linenr { font-size: smaller } |
| 86 | + .code-highlighted { background-color: #ffff00; } |
| 87 | + .org-info-js_info-navigation { border-style: none; } |
| 88 | + #org-info-js_console-label |
| 89 | + { font-size: 10px; font-weight: bold; white-space: nowrap; } |
| 90 | + .org-info-js_search-highlight |
| 91 | + { background-color: #ffff00; color: #000000; font-weight: bold; } |
| 92 | + /*]]>*/--> |
| 93 | +</style> |
| 94 | +<script type="text/javascript"> |
| 95 | +/* |
| 96 | +@licstart The following is the entire license notice for the |
| 97 | +JavaScript code in this tag. |
| 98 | +
|
| 99 | +Copyright (C) 2012-2013 Free Software Foundation, Inc. |
| 100 | +
|
| 101 | +The JavaScript code in this tag is free software: you can |
| 102 | +redistribute it and/or modify it under the terms of the GNU |
| 103 | +General Public License (GNU GPL) as published by the Free Software |
| 104 | +Foundation, either version 3 of the License, or (at your option) |
| 105 | +any later version. The code is distributed WITHOUT ANY WARRANTY; |
| 106 | +without even the implied warranty of MERCHANTABILITY or FITNESS |
| 107 | +FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. |
| 108 | +
|
| 109 | +As additional permission under GNU GPL version 3 section 7, you |
| 110 | +may distribute non-source (e.g., minimized or compacted) forms of |
| 111 | +that code without the copy of the GNU GPL normally required by |
| 112 | +section 4, provided you include this license notice and a URL |
| 113 | +through which recipients can access the Corresponding Source. |
| 114 | +
|
| 115 | +
|
| 116 | +@licend The above is the entire license notice |
| 117 | +for the JavaScript code in this tag. |
| 118 | +*/ |
| 119 | +<!--/*--><![CDATA[/*><!--*/ |
| 120 | + function CodeHighlightOn(elem, id) |
| 121 | + { |
| 122 | + var target = document.getElementById(id); |
| 123 | + if(null != target) { |
| 124 | + elem.cacheClassElem = elem.className; |
| 125 | + elem.cacheClassTarget = target.className; |
| 126 | + target.className = "code-highlighted"; |
| 127 | + elem.className = "code-highlighted"; |
| 128 | + } |
| 129 | + } |
| 130 | + function CodeHighlightOff(elem, id) |
| 131 | + { |
| 132 | + var target = document.getElementById(id); |
| 133 | + if(elem.cacheClassElem) |
| 134 | + elem.className = elem.cacheClassElem; |
| 135 | + if(elem.cacheClassTarget) |
| 136 | + target.className = elem.cacheClassTarget; |
| 137 | + } |
| 138 | +/*]]>*///--> |
| 139 | +</script> |
| 140 | +</head> |
| 141 | +<body> |
| 142 | +<div id="content"> |
| 143 | +<h1 class="title">Javaclass文件结构说明</h1> |
| 144 | +<div id="table-of-contents"> |
| 145 | +<h2>Table of Contents</h2> |
| 146 | +<div id="text-table-of-contents"> |
| 147 | +<ul> |
| 148 | +<li><a href="#sec-1">1. 前言</a></li> |
| 149 | +<li><a href="#sec-2">2. JVM解释运行过程</a></li> |
| 150 | +<li><a href="#sec-3">3. class文件结构说明</a> |
| 151 | +<ul> |
| 152 | +<li><a href="#sec-3-1">3.1. stack</a></li> |
| 153 | +<li><a href="#sec-3-2">3.2. locals</a></li> |
| 154 | +<li><a href="#sec-3-3">3.3. arg_size</a></li> |
| 155 | +</ul> |
| 156 | +</li> |
| 157 | +<li><a href="#sec-4">4. 结论</a></li> |
| 158 | +</ul> |
| 159 | +</div> |
| 160 | +</div> |
| 161 | + |
| 162 | + |
| 163 | +<div id="outline-container-sec-1" class="outline-2"> |
| 164 | +<h2 id="sec-1"><span class="section-number-2">1</span> 前言</h2> |
| 165 | +<div class="outline-text-2" id="text-1"> |
| 166 | +<p> |
| 167 | +这篇文章是基于读者对Java,编译原理,jvm规范有一定了解后书写的。对上述知识缺乏了解的可参考<a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">jvm规范,第四章</a> 改章节对class文件结构,jvm字节码质量有详细描述。 |
| 168 | +</p> |
| 169 | +<hr /> |
| 170 | +<p> |
| 171 | ++本文中主要使用到的字节码指令有 |
| 172 | + -dup:复制栈顶数据并重新压入栈 |
| 173 | + -iload:将int型数值压入栈 |
| 174 | + -iadd:加法运算,需要两个参数 |
| 175 | + -iconst_1:将int类型1压入栈 |
| 176 | + -istore:将栈顶int值存到本地变量中 |
| 177 | + -new:创建一个对象,并将引用值压入栈 |
| 178 | + -invokervirtual:调用实例方法 |
| 179 | ++本文中使用的Java命令有 |
| 180 | + -javac:Java语言编译命令,将java源文件编译成虚拟机可执行的class文件 |
| 181 | + -javap:Java反编译命令,讲class文件反编译成Java文件,或通过-verbose参数查看class文件结构 |
| 182 | +</p> |
| 183 | +<hr /> |
| 184 | +</div> |
| 185 | +</div> |
| 186 | + |
| 187 | +<div id="outline-container-sec-2" class="outline-2"> |
| 188 | +<h2 id="sec-2"><span class="section-number-2">2</span> JVM解释运行过程</h2> |
| 189 | +<div class="outline-text-2" id="text-2"> |
| 190 | +<p> |
| 191 | +Java语言的运行,是将Java文件编译成class文件,然后加载到虚拟机上运行的。在不考虑JIT(<a href="https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/">Java及时编译</a>) 的情况下,jvm是解释执行的,而且是基于栈实现的运算。 |
| 192 | +这句话是什么意思呢?比如我们常见的一行代码: |
| 193 | +</p> |
| 194 | +<div class="org-src-container"> |
| 195 | + |
| 196 | +<pre class="src src-java">int a = 2 + 3; |
| 197 | +</pre> |
| 198 | +</div> |
| 199 | +<p> |
| 200 | +这是一个常见的赋值表达式,含义是声明一个变量a,将2+2的值赋值给变量a。其中2+3是生活中常见的数学表达式,我们称这种形式的表达式为中缀表达式。而经过javac编译后 |
| 201 | +将形成后缀表达式的形式:2 3 + 的形式。Java的解释执行器在读入该行代码的时候,会将2从内存加载到栈里面;然后读取3加载到栈里面,然后读取到+运算符,就会将+运算符需要 |
| 202 | +的两个参数,也就是栈里面的2和3弹出来,然后送给CPU做加法运算。CPU运算完成后,将运算结果4压入栈中。然后通过istore命令将4的值存到变量a指向的内存地址中。 |
| 203 | +关于前缀表达式,中缀表达和后缀表达式三种表达式求值的运算逻辑可参考<a href="http://blog.csdn.net/antineutrino/article/details/6763722/">csdn博客</a> 。 |
| 204 | +</p> |
| 205 | +</div> |
| 206 | +</div> |
| 207 | + |
| 208 | +<div id="outline-container-sec-3" class="outline-2"> |
| 209 | +<h2 id="sec-3"><span class="section-number-2">3</span> class文件结构说明</h2> |
| 210 | +<div class="outline-text-2" id="text-3"> |
| 211 | +<p> |
| 212 | +源文件T.java |
| 213 | +</p> |
| 214 | +<div class="org-src-container"> |
| 215 | + |
| 216 | +<pre class="src src-java">public class T { |
| 217 | + public void test() { |
| 218 | + int a=1,b=1,c=1,d=2; |
| 219 | + System.out.println(a + "s" + b + c + d); |
| 220 | + } |
| 221 | +} |
| 222 | +</pre> |
| 223 | +</div> |
| 224 | +<p> |
| 225 | +我们通过javac T.java 命令编译,然后再使用javap -verbose T命令查看编译后的class文件结构 |
| 226 | +</p> |
| 227 | +<hr /> |
| 228 | +<p> |
| 229 | +编译生成的T.class文件结构: |
| 230 | +</p> |
| 231 | +<div class="org-src-container"> |
| 232 | + |
| 233 | +<pre class="src src-java">public void test(); |
| 234 | + descriptor: ()V |
| 235 | + flags: ACC_PUBLIC |
| 236 | + Code: |
| 237 | + stack=3, locals=5, args_size=1 |
| 238 | + 0: iconst_1 |
| 239 | + 1: istore_1 |
| 240 | + 2: iconst_1 |
| 241 | + 3: istore_2 |
| 242 | + 4: iconst_1 |
| 243 | + 5: istore_3 |
| 244 | + 6: iconst_2 |
| 245 | + 7: istore 4 |
| 246 | + 9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; |
| 247 | + 12: new #3 // class java/lang/StringBuilder |
| 248 | + 15: dup |
| 249 | + 16: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V |
| 250 | + 19: iload_1 |
| 251 | + 20: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; |
| 252 | + 23: ldc #6 // String s |
| 253 | + 25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; |
| 254 | + 28: iload_2 |
| 255 | + 29: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; |
| 256 | + 32: iload_3 |
| 257 | + 33: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; |
| 258 | + 36: iload 4 |
| 259 | + 38: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; |
| 260 | + 41: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; |
| 261 | + 44: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V |
| 262 | + 47: return |
| 263 | + LineNumberTable: |
| 264 | + line 3: 0 |
| 265 | + line 4: 9 |
| 266 | + line 5: 47 |
| 267 | +</pre> |
| 268 | +</div> |
| 269 | +<p> |
| 270 | +上面的源码只截取了test方法的编译结果。我们重点关注Code部分。 |
| 271 | +</p> |
| 272 | +</div> |
| 273 | +<div id="outline-container-sec-3-1" class="outline-3"> |
| 274 | +<h3 id="sec-3-1"><span class="section-number-3">3.1</span> stack</h3> |
| 275 | +<div class="outline-text-3" id="text-3-1"> |
| 276 | +<p> |
| 277 | +我们首先关注的是stack=3,这句话的含义是test方法在执行的过程中需要的最大操作数栈深度为3。根据我们上一节解释的,jvm是基于栈进行解释执行的。 |
| 278 | +我们按照编译出来的jvm字节码一行一行的模拟jvm的运行过程 |
| 279 | +</p> |
| 280 | +<div class="org-src-container"> |
| 281 | + |
| 282 | +<pre class="src src-java"> 0: iconst_1 :将int1压入栈 |
| 283 | + 1: istore_1 :将栈顶的1弹出赋值给变量a |
| 284 | + 2: iconst_1 :将int1压入栈 |
| 285 | + 3: istore_2 :将栈顶的1弹出赋值给变量b |
| 286 | + 4: iconst_1 : |
| 287 | + 5: istore_3 : |
| 288 | + 6: iconst_2 :将int2压入栈 |
| 289 | + 7: istore 4 :将栈顶的2弹出赋值给变量d |
| 290 | + 9: getstatic #2 :获取PrintStream对象 |
| 291 | +12: new #3 :新建StringBuilder对象,并将对象地址值压入栈,stack=1 |
| 292 | +15: dup :复制栈顶值,并压入栈,stack=2 |
| 293 | +16: invokespecial #4 :执行SB对象的初始化方法 |
| 294 | +19: iload_1 :将变量a的值压入栈,stack=3 |
| 295 | +20: invokevirtual #5 :执行append方法,依次弹出栈顶的a和sb,stack=1,执行完成append方法后,将sb对象压入栈,stack=2。 |
| 296 | +23: ldc #6 :将字符s压入栈,stack=3 |
| 297 | +25: invokevirtual #7 :执行append方法,将s和sb弹出栈,stack=1,执行完成append方法,将sb压入栈,stack=2。 |
| 298 | +28: iload_2 :同19 |
| 299 | +29: invokevirtual #5 :同20 |
| 300 | +32: iload_3 :同19 |
| 301 | +33: invokevirtual #5 :同20 |
| 302 | +36: iload 4 :同19 |
| 303 | +38: invokevirtual #5 :同20 |
| 304 | +41: invokevirtual #8 :执行sb的toString方法,并将返回的字符串压入栈,stack=2 |
| 305 | +44: invokevirtual #9 :将栈顶的字符串弹出,执行println方法,stack=1 |
| 306 | +47: return :方法返回,无须返回值 |
| 307 | +</pre> |
| 308 | +</div> |
| 309 | +<p> |
| 310 | +观察整个执行过程后,可以得出,test在执行的过程中,使用到的最大操作数栈的深度为3。 |
| 311 | +</p> |
| 312 | +</div> |
| 313 | +</div> |
| 314 | +<div id="outline-container-sec-3-2" class="outline-3"> |
| 315 | +<h3 id="sec-3-2"><span class="section-number-3">3.2</span> locals</h3> |
| 316 | +<div class="outline-text-3" id="text-3-2"> |
| 317 | +<p> |
| 318 | +locals是本地变量的数量。本例中,共需要储存三个1,一个2和一个this指针,所以本地变量表中需要5个slot储存变量。主意long和double是64位的,所以需要两个slot存储。但本例中, |
| 319 | +5个变量都是32位的,不需要扩展slot。 |
| 320 | +</p> |
| 321 | +</div> |
| 322 | +</div> |
| 323 | +<div id="outline-container-sec-3-3" class="outline-3"> |
| 324 | +<h3 id="sec-3-3"><span class="section-number-3">3.3</span> arg_size</h3> |
| 325 | +<div class="outline-text-3" id="text-3-3"> |
| 326 | +<p> |
| 327 | +arg_size是方法参数的个数,因为该方法是实例方法,所以会默认传入this指针作为参数,所以需要占用一个本地变量表的位置和一个参数位。如果将test方法改为static的,则不需要传入 |
| 328 | +this指针,就不会占用了,locals将变成4,arg_size变成0。读者可自行实验验证。 |
| 329 | +</p> |
| 330 | +</div> |
| 331 | +</div> |
| 332 | +</div> |
| 333 | + |
| 334 | +<div id="outline-container-sec-4" class="outline-2"> |
| 335 | +<h2 id="sec-4"><span class="section-number-2">4</span> 结论</h2> |
| 336 | +<div class="outline-text-2" id="text-4"> |
| 337 | +<p> |
| 338 | +-javac编译的时候,会把我们常见的中缀表达式翻译成jvm使用的后缀表达式 |
| 339 | +-jvm是基于栈进行解释执行的 |
| 340 | +-class文件中stack是方法执行过程中用到的最大操作数栈深度 |
| 341 | +-class文件中locals是本地变量表中变量需要的slot的个数 |
| 342 | +-class文件中arg\_size是方法的参数个数 |
| 343 | +-static方法不需要传入this指针,但非static方法默认会传入this指针。this要占用本地变量表中slot的个数和方法参数的个数 |
| 344 | +</p> |
| 345 | +</div> |
| 346 | +</div> |
| 347 | +</div> |
| 348 | +<div id="postamble" class="status"> |
| 349 | +<p class="author">Author: 王月阳</p> |
| 350 | +<p class=" email" >Email: <a href=" mailto:[email protected]" >[email protected]</a></p> |
| 351 | +<p class="date">Created: 2017-09-17 周日 11:05</p> |
| 352 | +<p class="creator"><a href="http://www.gnu.org/software/emacs/">Emacs</a> 24.5.1 (<a href="http://orgmode.org">Org</a> mode 8.2.10)</p> |
| 353 | +<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p> |
| 354 | +</div> |
| 355 | +</body> |
| 356 | +</html> |
0 commit comments