安卓面试必备的JVM虚拟机制详解,看完之后简历上多一个技能


安卓面试必备的JVM虚拟机制详解,看完之后简历上多一个技能文章插图
掌握了本篇知识之后 , 简历上就可以多加一条个人技能了:
熟悉 JVM 相关知识 , 包括内存区域、内存模型、GC、类加载机制、编译优化等
下面就是正文了 , 欢迎讨论~:
目录

  1. 内存区域
  2. 内存模型
  3. 内存分配回收策略
  4. Java 对象的创建、内存布局和访问定位
  5. GC1)引用计数及可达性分析2)垃圾回收算法3)G1 及 ZGC
  6. 类加载机制
  7. 双亲委派模型
  8. 编译器优化1)方法内联2)逃逸分析
  9. 虚拟机相关1)HotSpot 及 JIT2)Dalvik3)ART 及 AOT
  10. JVM 是如何执行方法调用的?
  11. JVM 是如何实现反射的?
  12. JVM 是如何实现泛型的?
  13. JVM 是如何实现异常的?
  14. JVM 是如何实现注解的?
内存区域Java 中的运行时数据可以划分为两部分 , 一部分是线程私有的 , 包括虚拟机栈、本地方法栈、程序计数器 , 另一部分是线程共享的 , 包括方法区和堆 。
程序计数器是一块较小的内存空间 , 它可以看作是当前线程所执行的字节码的行号指示器 。 虚拟机栈描述的是 Java 方法执行的内存模型 , 每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接地址、方法出口等信息 。 每一个方法从调用直至执行完成的过程 , 就对应着一个栈桢在虚拟机中入栈和出栈的过程 。 本地方法栈和虚拟机栈所发挥的作用是非常相似的 , 只不过本地方法栈描述的是 Native 方法执行的内存模型 。
Java 堆是所有线程共享的一块数据区域 , 主要用来存放对象实例 。 它也是垃圾收集器管理的主要区域 , 从内存回收的角度来看 , 由于现代收集器基本上都采用分代回收 , 所以 Java 堆还可以细分为新生代和老年代 。 再细致一点还可以把新生代划分为 Eden 区、From Survivor 区和 To Survivor 区 。 从内存分配的角度来看 , 线程共享的 Java 堆中可能划分为多个线程私有的分配缓冲区 TLAB 。 不过不论如何划分 , 都与存放内容无关 , 无论哪个区域 , 存放的都是对象实例 , 进一步划分的目的是为了更好的回收内存或者更快的分配内存 。 方法区是用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据 。 JVM 对方法区的限制比较宽松 , 除了和 Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外 , 还可以选择不实现垃圾回收 。 相对而言 , 垃圾回收在这个区域是比较少出现的 。 运行时常量池是方法区的一部分 , 它用来存储编译期生成的各种字面量和符号引用 。 运行时常量池相比 Class 文件常量池一个重要的特点是具备动态性 , 也就是在运行期间也可能将新的常量放入池中 , 比如 String 的 intern 方法 。
在 Java 6 版本中 , 永久代在非堆内存区;到了 Java 7 版本 , 永久代的静态变量和运行时常量池被合并到了堆中;而到了 Java 8 , 永久代被元空间取代了 。 很多开发者都习惯将方法区称为 “永久代” , 其实两者并不是等价的 。 HotSpot 虚拟机只是使用永久代来实现方法区 , 但是在 Java 8 已经将方法区中实现的永久代去掉了 , 并用元空间替换 , 元空间的存储位置是本地内存 。 那么 Java 8 为什么使用元空间替换永久代呢?这样做有什么好处嘛?
官方给出的解释是:移除永久代是为了融合 HotSpot JVM 和 JRockit VM 而做出的努力 , 因为 JRockit 没有永久代 , 所以不需要配置永久代;其次 , 永久代内存经常不够用 , 易 OOM 。 这是因为在 Java 7 中 , 指定的 PermGen 区大小为 8M , 由于 PermGen 中类的元数据信息在每次 FullGC 的时候回收率都偏低 , 而且为 PermGen 分配多大的空间很难确定 , PermSize 的大小依赖于很多因素 , 比如 JVM 加载的 class 总数、常量池的大小和方法的大小等等 。
内存模型JMM 内存模型是用来屏蔽掉各种硬件和操作系统的内存访问差异 , 以实现让 Java 程序在各个平台下都能达到一致的内存访问效果 。
Java 内存模型规定了所有的共享变量都是存储在主内存 , 每个线程还有自己的工作内存 , 线程的工作内存保存了该线程使用到的共享变量的主内存副本拷贝 , 线程对变量的操作都必须在工作内存中进行 , 而不能直接读写主内存中的变量 , 不同的线程之间也无法直接访问对方工作内存中的数据 , 线程间变量值的传递均需要主内存来完成 。
那么为什么要这么做呢?
其实就要讲到一些硬件知识了 , 我们知道 CPU 执行的速度是远超于内存访问速度 , 为了中和这种速度差异 , 在 CPU 和内存之间会加入多个 CPU 缓存 , 比如 L1、L2、L3 。 CPU 在处理数据时会先把内存中的数据读到自己的 CPU 缓存中 , 然后在缓存中进行操作数据 , 最后再把数据同步到内存中 。 这里 , 就可以把 CPU 的缓存看成是线程的工作内存 , 而把内存看成是主内存 , 虽然这个说法并不严谨 , 但是易于理解 。