🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**Java 为什么跨平台** 先将java文件编译成字节码(.class)文件, 在所有的平台上生成的字节码文件都是相同的。 再使用Java虚拟机运行字节码文件。因为不同操作系统有对应版本的jvm, 这使得同一个java代码文件可以在不同的平台上运行。 **.class 文件里是什么** .源代码经过编译器编译之后会生成一个字节码文件, 字节码是一种二进制的类文件, 它的内容是JVM的指令. **JVM 的生命周期** **虚拟机的启动** Java 虚拟机的启动是通过引导类加载器(bootstrap class loader) 场景一个初始类(initial class) 来完成的 , 这个类是由虚拟机的具体实现指定的. **虚拟机的退出有如下几种情况** * 某线程调用Runtime类或System类的exit方法, 或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作。 * 程序正常执行结束 * 程序在执行过程中遇到了异常或错误而异常终止 * 由于操作系统出现错误而导致Java虚拟机进程终止 **简述 HotSpot 虚拟机** * SUN的JDK版本从1.3.1开始运用HotSpot虚拟机, 2006年底开源,主要使用C++实现, JNI接口部·分用C实现, 用来**替换旧的解释型虚拟机 classic。** * HotSpot是较新的Java虚拟机, 使用JIT(Just in Time)编译器,可以大大提高Java运行的性能。 * Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢。而HotSpot将常用的部分代码**编译为本地(原生, native)代码**,这样显着**提高了性能**。 * HotSpot JVM参数可以分为规则参数(standard options)和非规则参数(non-standard options),规则参数相对稳定,在JDK未来的版本里不会有太大的改动。非规则参数则有因升级JDK而改动的可能。 **JVM 的组成? 虚拟机的体系结构?** 1. 类加载器 2. 运行时数据区 3. 执行引擎 ![](https://img.kancloud.cn/88/2e/882eb2e4b529aa785962f9e2888253f9_992x849.png) 这个架构可以分成三层看: * 最上层:javac编译器将编译好的字节码class文件,通过java 类装载器执行机制,把对象或class文件存放在 jvm划分内存区域。 * 中间层:称为Runtime Data Area,主要是在Java代码运行时用于存放数据的,从左至右为方法区(永久代、元数据区)、堆(共享,GC回收对象区域)、栈、程序计数器、寄存器、本地方法栈(私有)。 * 最下层:解释器、JIT(just in time)编译器和 GC(Garbage Collection,垃圾回收器) **什么是字节码的指令** Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。 **int i = 1; i = i++; i = ?** **字节码在赋值过程会在 操作数栈 存一份 i=1, 而自增只是在局部变量表自增, 不会影响操作数栈的数据, 后面从操作数栈读取 i 覆盖了局部变量表的数据** 变形 int i = 2; i \*= i++; // 4 和上面原因一样 int i =10; i = i + (i++) + (++i); // 32 // 第一个括号先读取出 10 , 然后自增了1, 就是 10 +10 但是这时候 i = 11了, 第二个括号在计算的时候是 11 进行自增成 12 10 + 10 + 12 **包装类的缓存** Integer -128 到 127 之内(包含这俩) 都是用的同一个对象, 不在这个区间就是 new 了个新对象 125 == 125, 255 != 255 valueOf 方法里判断的 Boolean 有俩常量 true 和 false valueOf 方法里判断的 ![](https://img.kancloud.cn/19/c7/19c798ceeb166f39e01a9577c3916ceb_688x343.png) **字符串** // string声明的字面量数据都放在字符串常量池中 // jdk 6中宇符串常量池存放在方法区(即永久代中) // jdk 7及以后字符串常量池存放在堆空间 ``` String str = new String("hello") + new String("world"); String str1 = "helloworld"; System.out.println(str == str1); // false, str 实际在字节码中 是使用 StringBuilder 拼接的, 最后通过 StringBuilder 的 toString() 方法返回了一个 new String() ``` ``` String str = new String("hello") + new String("world"); str.intern(); // 返回 String str1 = "helloworld"; System.out.println(str == str1); // 1.6 是false, 1.7 是ture, intern 方法之后会返回常量池中这个值(没有会创建) // 1.6 时 常量池在方法区和 intern 返回的不在一起(堆空间), 所以是俩变量 // 1.7 时 常量池也在堆空间, 这时候如果先 intern 就会把堆空间的引用地址 // 赋值给常量池, 这时候就用到同一个了. 如果后 intern 就直接从常量池中取值返回 System.out.println(str.intern() == str1); // true ``` // new StringBuilder("hellonihao").toString() 俩字符串, // 一个 "hellonihao" 常量, 一个 new String 的 "hellonihao" ``` // 先 有常量, 后 intern() 则这个 toString() 后的值和 intern 的不相等 // new StringBuilder("hellonihao").toString() 俩字符串, // 一个 "hellonihao" 常量, 一个 new String 的 "hellonihao" String str2 = new StringBuilder("hellonihao").toString(); // false System.out.println(str2 == str2.intern()); // jdk 中 本身已经有了 "Java(TM) SE Runtime Environment" 常量 // 组成 新字符串时, 原来的词组组合在一起的字符串出现过常量, 不是首次, // StringBuilder.toString() 会重新生成一个, 不是把常量的引用拿过来用 String str2 = new StringBuilder("Java(TM) SE ") .append("Runtime Environment").toString(); // false System.out.println(str2.intern() == str2); String ss = "Java(TM) SE Runtime Environment"; // true System.out.println(str2.intern() == ss); ``` **输出的结果是** ![](https://img.kancloud.cn/a2/58/a258d42a3037760e17cb7293def4793b_949x549.png) Father f = new Son(); System.out.println(f.x); 执行过程是 1\. 子类构造器会**先调用父类构造器**, 在这里, 父类构造器里的this 指向的是 子类, **调用的也就是子类 print 方法**, 因为子类已经**重写了print**, 直接就执行子类的print方法, 但是这时候子类的 **x 还没有赋值 = 30,**所以输出 Son.x = 0。 2\. 执行完父类构造器再执行子类构造器,这时候**再调用子类的print x 已经是30了**, 所以输出 Son.x = 30。 3\. **f.x 是调用的 父类 x 变量**, 所以输出 20,父类的 x 在父类构造器中修改为 20了,父类 = new 子类 多态时, 父类是调用不了子类的变量的, 如果子类还定义了个 a 变量, 这时候是没法 f.a 的 **字节码** **字节码(.class) 文件结构** * 魔数 (用来标识文件类型的) * class文件版本 (jdk的版本信息) * 常量池计数器 和 常量池表数据 * 访问标识(或标志) (public final 等等信息 ) * 类索引、父类索引、接口索引集合 (继承那个类, 实现哪些接口) * 字段表集合 * 方法表集合 * 属性表集合 ![](https://img.kancloud.cn/d9/bb/d9bbcf1adcc38fff62c60a81ef45997e_1500x641.png) **字节码指令分类** * 加载与存储指令 * 算术指令 * 类型转换指令 * 对象的创建与访问指令 * 方法调用与返回指令 * 操作数栈管理指令 * 控制转移指令 * 异常处理指令 * 同步控制指令 **方法指令调用** ![](https://img.kancloud.cn/22/c5/22c581dcca3ff3f75b5ee9d2ba5ba5ee_673x498.png) **基本类型为什么不存到堆中而是存到栈中** 栈空间相对小, 运算速度更快, 基本类型占用空间小更适合放到栈中