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


内存模型JMM 内存模型是用来屏蔽掉各种硬件和操作系统的内存访问差异 , 以实现让 Java 程序在各个平台下都能达到一致的内存访问效果 。
Java 内存模型规定了所有的共享变量都是存储在主内存 , 每个线程还有自己的工作内存 , 线程的工作内存保存了该线程使用到的共享变量的主内存副本拷贝 , 线程对变量的操作都必须在工作内存中进行 , 而不能直接读写主内存中的变量 , 不同的线程之间也无法直接访问对方工作内存中的数据 , 线程间变量值的传递均需要主内存来完成 。
那么为什么要这么做呢?
其实就要讲到一些硬件知识了 , 我们知道 CPU 执行的速度是远超于内存访问速度 , 为了中和这种速度差异 , 在 CPU 和内存之间会加入多个 CPU 缓存 , 比如 L1、L2、L3 。 CPU 在处理数据时会先把内存中的数据读到自己的 CPU 缓存中 , 然后在缓存中进行操作数据 , 最后再把数据同步到内存中 。 这里 , 就可以把 CPU 的缓存看成是线程的工作内存 , 而把内存看成是主内存 , 虽然这个说法并不严谨 , 但是易于理解 。
内存分配回收策略内存分配回收策略包含三点:

  1. 对象优先在 Eden 区分配准确的来说 , 是优先在 Eden 区的 TLAB 上分配 , 如果 Eden 区没有足够的空间进行分配时 , 就会触发一次 Minor GC 。
  2. 大对象直接进入老年代所谓的大对象是指需要连续大量内存空间的 Java 对象 , 比如数组 , 一般来说 , 超过 3M 的对象会直接在老年代进行分配 。
  3. 长期存活的对象进入老年代既然虚拟机采用了分代收集的思想来管理内存 , 那么内存回收就必须得识别哪些对象应放在新生代还是老年代 。 为了做到这一点 , 虚拟机给每个对象定义了一个对象年龄计数器 。 如果对象在 Eden 出生并经过一次 Minor GC 后仍然存活 , 并且能被 Survivor 容纳的话 , 将会被移到 Survivor 空间中 , 并且对象年龄设置为 1.对象每在 Survivor 区熬过一次 Minor GC , 年龄就会增加 1 。 当年龄增加到一定程度 , 默认是 15 , 就将会晋升到老年代中 。
最后讲一下 Minor GC 和 Full GC 。
Minor GC 是指发生在新生代的垃圾回收动作 , 因为 Java 对象大多都是朝生夕死的 , 所以 Minor GC 比较频繁 , 回收速度也比较快 。
Full GC/Major GC 指发生在老年代的 GC , 出现 Full GC 经常会伴随着至少一次的 Minor GC , Full GC 一般会比 Minor GC 慢十倍以上 。
Java 对象的创建、内存布局和访问定位先说对象创建 , 在虚拟机遇到一条 new 指令时 , 首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用 , 并且检查这个符号引用代表的类是否已经被加载过了 , 如果没有就走类加载流程 。 在类加载检查通过之后 , 虚拟机就会为新生对象分配内存 , 对象所需内存在类加载完成之后就确定了 。 为对象分配内存空间就等同于把一块确定大小的内存从 Java 堆中划分出来 。 分配方式有指针碰撞和空闲列表两种 , 选择哪种分配方式由 Java 堆是否规整决定 , 而 Java 堆是否规整又由所采用的垃圾收集器是否具有压缩整理功能决定 。 对象创建在虚拟机是非常频繁的行为 , 即使是仅仅修改了一个指针指向的位置 , 在并发情况下也不是线程安全的 。 解决方案有两种 , 一种是采用 CAS 配上失败重试 , 另一种是使用线程私有的分配缓冲区 TLAB 。
接着是对象的内存布局 , 在 HotSpot 虚拟机中 , 对象在内存中存储的布局可以分为三块区域:对象头、实例数据和对其填充 。 可以使用 OpenJDK 开源的 JOL 工具查看对象的内存布局 , 直接 new Object 所占用的大小为 16 字节 , 即 12 个字节的对象头 + 4 个字节的对其填充 。 JOL 对分析集合源码扩容、HashMap 的 hash 冲突等非常有用 。
最后是对象的访问定位 , Java 程序需要通过栈上的 reference 数据来操作堆上的具体对象 , 由于 reference 类型在 Java 虚拟机规范中只规定了一个指向对象的引用 , 并没有规定这个引用应该通过什么方式去定位和访问堆中的对象 , 所以对象访问方式也是取决于虚拟机实现而定 。 目前主流的方式有使用句柄和直接指针两种 。 使用句柄 , 就是相当于加了一个中间层 , 在对象移动时只会改变句柄中的实例数据的指针 , reference 本身不需要改变 。 HotSpot 使用的是第二种 , 使用直接指针的方式访问的最大好处就是速度很快 。