1. <div id="f8mbs"></div>
        您好£¬欢迎来到源码搜藏网£¡分享精神£¬快乐你我£¡
        [加入VIP] 设为首页 | 收藏本站 | 网站地图 | Sitemap | TAG标签
      2. 首 页
      3. 在线工具
      4. jquery手册
      5. 当前位置£º首页 > 安卓源码 > 技术博客 >

        Android开发之史上最详细Java内存区域讲解

        时间£º2019-04-12 00:03 来源£º互联网 作者£º源码搜藏 浏览£º收藏 挑错 推荐 打印

        常见面试题 基本问题 介绍下 Java 内存区域£¨运行时数据区£© Java 对象的创建过程£¨五步£¬建议能默写出来并且要知道每一步虚拟机做了什么£© 对象的访问定位的两种方式£¨句柄和直接指针两种方式£© 拓展问题 String类和常量池 8种基本类型的包装类和常量池 一

        常见面试题

        基本问题

        • 介绍下 Java 内存区域£¨运行时数据区£©
        • Java 对象的创建过程£¨五步£¬建议能默写出来并且要知道每一步虚拟机做了什么£©
        • 对象的访问定位的两种方式£¨句柄和直接指针两种方式£©

        拓展问题

        • String类和常量池
        • 8种基本类型的包装类和常量池

        一¡¢概述

        对于 Java 程序员来说£¬在虚拟机自动内存管理机制下£¬不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作£¬不容易出现内存泄漏和内存溢出问题¡£正?#19988;?#20026; Java 程序员把内存控制权利交给 Java 虚拟机£¬一旦出现内存泄漏和溢出方面的问题£¬如果不了解虚拟机是怎样使用内存的£¬那么排查错误将会?#19988;?#20010;非常艰巨的任务¡£

        二¡¢运行时数据区域

        Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域¡£JDK. 1.8 和之前的版本略有不同£¬下面会介绍到¡£

        JDK 1.8之前£º

         

        Android开发之史上最详细Java内存区域讲解

         

         

        JDK 1.8 £º

         

        Android开发之史上最详细Java内存区域讲解

         

         

        线程?#25509;?#30340;£º

        • 程序计数器
        • 虚拟机栈
        • 本地方法栈

        线程共享的£º

        • 方法区
        • 直接内存(非运行时数据区的一部分)

        2.1 程序计数器

        程序计数器?#19988;?#22359;较小的内存空间£¬可以看作是当前线程所执行的字节码的行号指示器¡£字节码解?#25512;?#24037;作时通过改变这个计数器的值来选取下一条需要执行的字节码指令£¬?#31181;§¡?#24490;环¡¢跳转¡¢异常处理¡¢线程?#25351;?#31561;功能都需要?#35272;?#36825;个计数器来完¡£

        另外£¬为了线程切换后能?#25351;?#21040;正确的执行位置£¬每条线程都需要有一个独立的程序计数器£¬各线程之间计数器互不影响£¬独立存储£¬我们称这类内存区域为“线程?#25509;?rdquo;的内存¡£

        从上面的介绍中我们知道程序计数器主要有两个作用£º

        1. 字节码解?#25512;?#36890;过改变程序计数器来?#26469;?#35835;取指令£¬从而实现代码的流程控制£¬如£º顺序执行¡¢选择¡¢循环¡¢异常处理¡£
        2. 在多线程的情况下£¬程序计数器用于记录当前线程执行的位置£¬从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了¡£

        注意£º程序计数器是唯一一个?#25442;?#20986;现 OutOfMemoryError 的内存区域£¬它的生命周期随着线程的创建而创建£¬随着线程的结束而死亡¡£

        2.2 Java 虚拟机栈

        与程序计数器一样£¬Java虚拟机栈也是线程?#25509;?#30340;£¬它的生命周期和线程相同£¬描述的是 Java 方法执行的内存模型£¬每次方法调用的数据都是通过栈传递的¡£

        Java 内存可?#28304;?#31961;的区分为堆内存£¨Heap£©和栈内存(Stack),其中栈就是现在说的虚拟机栈£¬或者说是虚拟机栈中局部变量表部分¡£ £¨?#23548;?#19978;£¬Java虚拟机栈是由一个个栈帧组成£¬而每个栈帧中?#21152;?#26377;£º局部变量表¡¢操作数栈¡¢动态链接¡¢方法出口信息¡££©

        局部变量表主要存放了编译器可知的各种数据类型£¨boolean¡¢byte¡¢char¡¢short¡¢int¡¢float¡¢long¡¢double£©¡¢对象引用£¨reference类型£¬它不同于对象本身£¬可能?#19988;?#20010;指向对象起始地址的引用指针£¬?#37096;?#33021;是指向一个代表对象的句柄或其他与此对象相关的位置£©¡£

        Java 虚拟机?#25442;?#20986;现两种异常£ºStackOverFlowError 和 OutOfMemoryError¡£

        • StackOverFlowError£º 若Java虚拟机栈的内存大小不允许动态扩展£¬那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候£¬就抛出StackOverFlowError异常¡£
        • OutOfMemoryError£º 若 Java 虚拟机栈的内存大小允许动态扩展£¬且当线程请求栈时内存用完了£¬无法再动态扩展了£¬此时抛出OutOfMemoryError异常¡£

        Java 虚拟机栈也是线程?#25509;?#30340;£¬每个线程?#21152;?#21508;自的Java虚拟机栈£¬而且随着线程的创建而创建£¬随着线程的死亡而死亡¡£

        扩展£º那么方法/函数如何调用£¿

        Java 栈可用类比数据结构中栈£¬Java 栈中保存的主要内容是栈帧£¬每一次函数调用都会有一个对应的栈帧被压入Java栈£¬每一个函数调用结束后£¬都会有一个栈帧被弹出¡£

        Java方法有两种返回方式£º

        1. return 语句¡£
        2. 抛出异常¡£

        不管哪种返回方式都会导致栈帧被弹出¡£

        2.3 本地方法栈

        和虚拟机栈所发挥的作用非常相似£¬区别是£º 虚拟机栈为虚拟机执行 Java 方法 £¨也就是字节码£©服务£¬而本地方法栈则为虚拟机使用到的 Native 方法服务¡£ 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一¡£

        本地方法被执行的时候£¬在本地方法栈?#19981;?#21019;建一个栈帧£¬用于存放该本地方法的局部变量表¡¢操作数栈¡¢动态链接¡¢出口信息¡£

        方法执行完毕后相应的栈帧?#19981;?#20986;栈并释放内存空间£¬?#19981;?#20986;现 StackOverFlowError 和 OutOfMemoryError 两种异常¡£

        2.4 堆

        Java 虚拟机所管理的内存中最大的一块£¬Java 堆是所有线程共享的一块内存区域£¬在虚拟机启动时创建¡£此内存区域的唯一目的就是存放对象?#36947;ý£?#20960;乎所有的对象?#36947;?#20197;及数组都在这里分配内存¡£

        Java 堆是垃圾收集器管理的主要区域£¬因此也被称作GC堆£¨Garbage Collected Heap£©.从垃圾回收的角度£¬由于现在收集器基本都采用分代垃圾收集算法£¬所以Java堆还可以细分为£º新生代和老年代£º再细致一点有£ºEden空间¡¢From Survivor¡¢To Survivor空间?#21462;?#36827;一步划分的目的是更好地回收内存£¬或者更快地分配内存¡£

         

        Android开发之史上最详细Java内存区域讲解

         

        上图所示的 eden区¡¢s0区¡¢s1区都属于新生代£¬tentired 区属于老年代¡£大部分情况£¬对象都会首先在 Eden 区域分配£¬在一次新生代垃圾回收后£¬如果对象还存活£¬则会进入 s0 或者 s1£¬并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1)£¬当它的年龄增加到一定程度£¨默认为15岁£©£¬就会被晋升到老年代?#23567;?#23545;象晋升到老年代的年龄阈值£¬可以通过?#38382;?-XX:MaxTenuringThreshold 来设置¡£2.5 方法区

        方法区与 Java 堆一样£¬是各个线程共享的内存区域£¬它用于存储已被虚拟机加载的类信息¡¢常量¡¢静态变量¡¢即时编译器编译后的代码等数据¡£虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分£¬但是它却有一个别名叫做 Non-Heap£¨非堆£©£¬目的应该是与 Java 堆区分开来¡£

        方法区也被称为永?#20040;ú¡?#24456;多人都会分不清方法区和永?#20040;?#30340;关系£¬为此我也查阅了文献¡£

        方法区和永?#20040;?#30340;关系

        ¡¶Java虚拟机规范?#20998;?#26159;规定了有方法区这么个概念和它的作用£¬并?#25381;泄?#23450;如何去实现它¡£那么£¬在不同的 JVM 上方法区的实现肯定是不同的了¡£ 方法区和永?#20040;?#30340;关系很像Java中接口和类的关系£¬类实现了接口£¬而永?#20040;?#23601;是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式¡£ 也就是说£¬永?#20040;?#26159;HotSpot的概念£¬方法区是Java虚拟机规范中的定义£¬?#19988;?#31181;规范£¬而永?#20040;úÊ且?#31181;实现£¬一个是标准一个是实现£¬其他的虚拟机实现并没有永?#20040;?#36825;一说法¡£

        常用?#38382;?/p>

        JDK 1.8 之前永?#20040;?#36824;没被彻底移除的时候通常通过下面这些?#38382;?#26469;调节方法区大小

        -XX:PermSize=N //方法区(永?#20040;?初始大小
        -XX:MaxPermSize=N //方法区(永?#20040;?最大大小,超过这个?#21040;?#20250;抛出OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
        

        相对而言£¬垃圾收集行为在这个区域是比较少出现的£¬但并非数据进入方法区后就“永久存在”了¡£**

        JDK 1.8 的时候£¬方法区£¨HotSpot的永?#20040;ú£?#34987;彻底移除了£¨JDK1.7就已经开始了£©£¬取而代之是元空间£¬元空间使用的是直接内存¡£

        下面?#19988;?#20123;常用?#38382;ý£?/p>

        -XX:MetaspaceSize=N //设置Metaspace的初始£¨和最小大小£©
        -XX:MaxMetaspaceSize=N //设置Metaspace的最大大小
        

        与永?#20040;?#24456;大的不同就是£¬如果不指定大小的话£¬随着更多类的创建£¬虚拟机会耗尽所有可用的系统内存¡£

        为什么要将永?#20040;?PermGen)替换为元空间(MetaSpace)呢?

        整个永?#20040;?#26377;一个 JVM 本身设置固定大小上线£¬无法进行调整£¬而元空间使用的是直接内存£¬受本机可用内存的限制£¬并且永远?#25442;?#24471;到java.lang.OutOfMemoryError¡£你可以使用 -XX£ºMaxMetaspaceSize 标志设置最大元空间大小£¬默?#29616;?#20026; unlimited£¬这意味着它只受系统内存的限制¡£-XX£ºMetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志£¬则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小¡£

        当然这只是其中一个原因£¬还有很多底层的原因£¬这里就不提了¡£

        2.6 运行时常量池

        运行时常量池是方法区的一部分¡£Class 文件中除了有类的版本¡¢字段¡¢方法¡¢接口等描述信息外£¬还有常量池信息£¨用于存放编译期生成的各种字面量和符号引用£©

        既然运行时常量池时方法区的一部分£¬自然受到方法区内存的限制£¬当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常¡£

        JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来£¬在 Java 堆£¨Heap£©中开辟了一块区域存放运行时常量池¡£

         

        Android开发之史上最详细Java内存区域讲解

         

         

        2.7 直接内存

        直接内存并不是虚拟机运行时数据区的一部分£¬也不是虚拟机规范中定义的内存区域£¬但是这部分内存也被频繁地使用¡£而且?#37096;?#33021;导致 OutOfMemoryError 异常出现¡£

        JDK1.4 ?#34892;录?#20837;的 NIO(New Input/Output) 类£¬引入了一种基于通道£¨Channel£© 与缓存区£¨Buffer£© 的 I/O 方式£¬它可以直接使用 Native 函数库直接分配堆外内存£¬然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作¡£这样就能在一些场景中显著提高性能£¬因为避免了在 Java 堆和 Native 堆之间来回复制数据¡£

        本机直接内存的分配?#25442;?#25910;到 Java 堆的限制£¬但是£¬既然是内存就会受到本机总内存大小以?#25353;?#29702;器寻址空间的限制¡£

        三¡¢HotSpot 虚拟机对象探秘

        通过上面的介绍我们大概知道了虚拟机的内存情况£¬下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配¡¢布局和访问的全过程¡£

        3.1 对象的创建

        下图便是 Java 对象的创建过程£¬我建议最好是能默写出来£¬并且要掌握每一步在做什么¡£

        Android开发之史上最详细Java内存区域讲解

         

        ¢Ù类加载检查£º 虚拟机遇到一条 new 指令时£¬首先将去检查这个指令的?#38382;?#26159;否能在常量池中定位到这个类的符号引用£¬并且检查这个符号引?#20040;?#34920;的类是否已被加载过¡¢解析和初始化过¡£如果没有£¬那必须先执行相应的类加载过程¡£

        ¢Ú分配内存£º 在类加载检查通过后£¬接下来虚拟机将为新生对象分配内存¡£对象所需的内存大小在类加载完成后便可确定£¬为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来¡£分配方式有 “指针碰撞” 和 “空闲列表” 两种£¬选择那种分配方式由 Java 堆是否规整决定£¬而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定¡£

        内存分配的两种方式£º£¨补充内容£¬需要掌握£©

        选择以上两种方式中的哪一种£¬取决于 Java 堆内存是否规整¡£而 Java 堆内存是否规整£¬取决于 GC 收集器的算法是"标记-清除"£¬还是"标记-整理"£¨也称作"标记-压缩"£©£¬值得注意的是£¬复制算法内存也是规整的

         

        Android开发之史上最详细Java内存区域讲解

         

        内存分配并发问题£¨补充内容£¬需要掌握£©

        在创建对象的时候有一个很重要的问题£¬就是线程安全£¬因为在?#23548;?#24320;发过程中£¬创建对象是很频繁的事情£¬作为虚拟机来说£¬必须要保证线程是安全的£¬通常来讲£¬虚拟机采用两种方式来保证线程安全£º

        • CAS+失败重试£º CAS 是乐观锁的一种实现方式¡£所谓乐观锁就是£¬每次不加锁而是假设没有冲?#27426;?#21435;完成某项操作£¬如果因为冲突失败就重试£¬直到成功为止¡£虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性¡£
        • TLAB£º 为每一个线程预先在Eden区分配一块儿内存£¬JVM在给线程中的对象分配内存时£¬首先在TLAB分配£¬当对象大于TLAB中的剩余内存或TLAB的内存已用尽时£¬再采用上述的CAS进行内存分配

        ¢Û初始化零值£º 内存分配完成后£¬虚拟机需要将分配到的内存空间都初始化为零值£¨不包括对象头£©£¬这一?#35762;?#20316;保证了对象的?#36947;?#23383;段在 Java 代码中可以不赋初始值就直接使用£¬程序能访问到这些字段的数据类型所对应的零值¡£

        ¢Ü设置对象头£º 初始化零值完成之后£¬虚拟机要对对象进行必要的设置£¬例如这个对象是那个类的?#36947;ý¡?#22914;何才能找到类的元数据信息¡¢对象的哈希吗¡¢对象的 GC 分代年龄等信息¡£ 这些信息存放在对象?#20998;小?nbsp;另外£¬根据虚拟机当前运行状态的不同£¬如是否启用偏向锁等£¬对象头会有不同的设置方式¡£

        ¢Ý执行 init 方法£º 在上面工作都完成之后£¬从虚拟机的视角来看£¬一个新的对象已经产生了£¬但从 Java 程序的视角来看£¬对象创建才刚开始£¬<init> 方法还没有执行£¬所有的字段都还为零¡£所以一般来说£¬执行 new 指令之后会接着执行 <init>方法£¬把对象按照程序员的意愿进行初始化£¬这样一个真正可用的对象才算完全产生出来¡£

        3.2 对象的内存布局

        在 Hotspot 虚拟机中£¬对象在内存中的布局可以分为3块区域£º对象头¡¢?#36947;?#25968;据和对齐填充¡£

        Hotspot虚拟机的对象头包括?#35762;?#20998;信息£¬第一部分用于存储对象自身的自身运行时数据£¨哈希码¡¢GC分代年龄¡¢锁状态标志等等£©£¬另一部分是类型指针£¬即对象指向它的类元数据的指针£¬虚拟机通过这个指针来确定这个对象是那个类的?#36947;ý¡?/p>

        ?#36947;?#25968;据部分是对象真正存储的?#34892;?#20449;息£¬也是在程序中所定义的各种类型的字段内容¡£

        对齐填充部分不是必然存在的£¬也没有什么特别的含义£¬仅仅起占位作用¡£ 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍£¬换句话说就是对象的大小必须是8字节的整数倍¡£而对象头部分正好是8字节的倍数£¨1倍或2倍£©£¬因此£¬当对象?#36947;?#25968;据部分没有对齐时£¬就需要通过对齐填充来补全¡£

        3.3 对象的访问定位

        建立对象就是为了使用对象£¬我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象¡£对象的访?#21490;?#24335;?#34892;?#25311;机实现而定£¬目前主流的访?#21490;?#24335;有¢Ù使用句柄和¢Ú直接指针两种£º

        • 句柄£º 如果使用句柄的话£¬那么Java堆中将会划分出一块内存来作为句柄池£¬reference 中存储的就是对象的句柄地址£¬而句柄中包含了对象?#36947;?#25968;据与类型数据各自的具体地址信息£»
        Android开发之史上最详细Java内存区域讲解

         

         

        • 直接指针£º 如果使用直接指针访问£¬那么 Java 堆对象的布?#31181;?#23601;必须考虑如何放置访问类型数据的相关信息£¬而reference 中存储的直接就是对象的地址¡£

         

        Android开发之史上最详细Java内存区域讲解

         

        这两种对象访?#21490;?#24335;各有优势¡£使用句柄来访问的最大?#20040;?#26159; reference 中存储的是稳定的句柄地址£¬在对象被移动时?#25442;?#25913;变句柄中的?#36947;?#25968;据指针£¬而 reference 本身不需要修改¡£使用直接指针访?#21490;?#24335;最大的?#20040;?#23601;是速度快£¬它节省了一次指针定位的时间开销¡£

        四¡¢重点补充内容

        String 类和常量池

        1 String 对象的两种创建方式£º

         String str1 = "abcd";
         String str2 = new String("abcd");
         System.out.println(str1==str2);//false
        

        这两种不同的创建方法是有差别的£¬第一种方式是在常量池中拿对象£¬第二种方式是直接在堆内存空间创建一个新的对象¡£

         

        Android开发之史上最详细Java内存区域讲解

         

         

        记住£º只要使用new方法£¬便需要创建新的对象¡£2 String 类型的常量池比较特殊¡£它的主要使用方法有两种£º

        • 直接使用双引号声明出来的 String 对象会直接存储在常量池?#23567;?/li>
        • 如果不是用双引号声明的 String 对象£¬可以使用 String 提供的 intern 方法¡£String.intern() ?#19988;?#20010; Native 方法£¬它的作用是£º如果运行时常量池中已经包含一个等于此 String 对象内容的字符串£¬则返回常量池中该字符串的引用£»如果没有£¬则在常量池中创建与此 String 内容相同的字符串£¬并返回常量池中创建的字符串的引用¡£
         String s1 = new String("计算机");
         String s2 = s1.intern();
         String s3 = "计算机";
         System.out.println(s2);//计算机
         System.out.println(s1 == s2);//false£¬因为一个是堆内存中的String对象一个是常量池中的String对象£¬
         System.out.println(s3 == s2);//true£¬因为两个都是常量池中的String对象
        

        3 String 字符串拼接

         String str1 = "str";
         String str2 = "ing";
         String str3 = "str" + "ing";//常量池中的对象
         String str4 = str1 + str2; //在堆上创建的新的对象 
         String str5 = "string";//常量池中的对象
         System.out.println(str3 == str4);//false
         System.out.println(str3 == str5);//true
         System.out.println(str4 == str5);//false
        

         

        Android开发之史上最详细Java内存区域讲解

         

         

        尽量避免多个字符串拼接£¬因为这样会重新创建对象¡£如果需要改变字符串的话£¬可以使用 StringBuilder 或者 StringBuffer¡£

        String s1 = new String("abc");这句话创建了几个对象£¿

        创建了两个对象¡£

        验证£º

         String s1 = new String("abc");// 堆内存的地?#20998;?
         String s2 = "abc";
         System.out.println(s1 == s2);// 输出false,因为一个是堆内存£¬一个是常量池的内存£¬故两者是不同的¡£
         System.out.println(s1.equals(s2));// 输出true
        

        结果£º

        false
        true
        

        解释£º

        先有字符串"abc"放入常量池£¬然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池£¬而 Java 堆上的"abc"是在运行期初始化阶段才确定)£¬然后 Java 栈的 str1 指向Java堆上的"abc"¡£

        8种基本类型的包装类和常量池

        • Java 基本类型的包装类的大部分都实现了常量池技术£¬即Byte,Short,Integer,Long,Character,Boolean£»这5种包装类默认创建了数值[-128£¬127]的相应类型的缓存数据£¬但是超出此范围仍?#25442;?#21435;创建新的对象¡£
        • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术¡£
         Integer i1 = 33;
         Integer i2 = 33;
         System.out.println(i1 == i2);// 输出true
         Integer i11 = 333;
         Integer i22 = 333;
         System.out.println(i11 == i22);// 输出false
         Double i3 = 1.2;
         Double i4 = 1.2;
         System.out.println(i3 == i4);// 输出false
        

        Integer 缓存源代码£º

        /**
        *此方法将始终缓存-128到127£¨包括端点£©范围内的值£¬并可以缓存此范围之外的其他值¡£
        */
         public static Integer valueOf(int i) {
         if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
         return new Integer(i);
         }
        

        应用场景£º

        1. Integer i1=40£»Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);£¬从而使用常量池中的对象¡£
        2. Integer i1 = new Integer(40);这种情况下会创建新的对象¡£
         Integer i1 = 40;
         Integer i2 = new Integer(40);
         System.out.println(i1==i2);//输出false
        

        Integer比?#32454;?#20016;富的一个例子:

         Integer i1 = 40;
         Integer i2 = 40;
         Integer i3 = 0;
         Integer i4 = new Integer(40);
         Integer i5 = new Integer(40);
         Integer i6 = new Integer(0);
         System.out.println("i1=i2 " + (i1 == i2));
         System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
         System.out.println("i1=i4 " + (i1 == i4));
         System.out.println("i4=i5 " + (i4 == i5));
         System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); 
         System.out.println("40=i5+i6 " + (40 == i5 + i6)); 
        

        结果£º

        i1=i2 true
        i1=i2+i3 true
        i1=i4 false
        i4=i5 false
        i4=i5+i6 true
        40=i5+i6 true
        

        解释£º

        语句i4 == i5 + i6£¬因为+这个操作符不适用于Integer对象£¬首先i5和i6进行自动拆箱操作£¬进行数值相加£¬即i4 == 40¡£然后Integer对象无法与数?#21040;?#34892;直接比较£¬所以i4自动拆箱转为int值40£¬最终这条语句转为40 == 40进行数?#24403;?#36739;¡£

        Android开发之史上最详细Java内存区域讲解转载<\/script>' );
        ÁÉÄþʮһѡÎ嵥˫