深入理解 Java 虚拟机 笔记 (2018.06.19-23) – 热爱改变生活
我的GitHub GitHub |     登录
  • If you can't fly, then run; if you can't run, then walk; if you can't walk, then crawl
  • but whatever you do, you have to keep moving forward。
  • “你骗得了我有什么用,这是你自己的人生”
  • 曾有伤心之地,入梦如听 此歌

深入理解 Java 虚拟机 笔记 (2018.06.19-23)

读书笔记 sinvader 681℃ 0评论

笔记书籍:《深入理解 Java 虚拟机》

笔记日期: 2018-06-20 一一 2018-06-23

Java 的优点

  1. 它摆脱了硬件平台的束缚. 实现了” 一次编写, 到处运行” 的理想
  2. 它提供了一个相对安全的内存管理和访问机制, 避免了绝大部分的内存泄露和指针越界问题
  3. 它实现了热点代码检测和运行时编译及优化, 使得 Java 应用能够随着运行时间的增加而获得更高的性能.
  4. 它有一套完善的应用程序接口, 还有无数来自商业机构和开源社区的第三方类库来帮助它实现各种各样的功能.

什么是 JDK

JDK 是用于支持 Java 程序开发的最小环境

JDk1.2 中发布了什么

在 JDK1.2 中,SUN 公司把 Java 体系拆分为了 3 个方向 :

  1. 面向桌面因公的 J2SE
  2. 面向企业级开发的 J2EE
  3. 面向手机等移动终端开发的 J2ME

JDK1.5 中发布了什么

JDK1.5 中加入了自动装箱, 泛型, 动态注解, 枚举, 可变长参数, 便利循环等语法特性

Android 中的 Java 虚拟机

Android 平台的虚拟机是 Dalvik VM, 它不是一个 Java 虚拟机, 它并没有遵循 Java 虚拟机规范, 不能直接执行 Java 的 Class 文件, 同时它使用的是寄存器架构, 而不是 JVM 中常见的栈架构. 它执行的 dex 文件可以通过 Class 文件转化而来.

Java 对多核并行的支持

在 JDK1.5 就引进了 java.util.concurrent 包实现了一个粗粒度的并发框架

在 JDK1.7 中加入了 java.util.concurrent.forkjoin 包

OpenJDK

SUN 公司在 2006 年将 OpenJDK 开源,OpenJDK 和 OracleJDK 除了在 FontRenderer 和 FlightRecorder 之外几乎没有什么差别.

虚拟机运行时数据区

程序计数器

程序计数器可以看做是当前线程所执行的字节码的行号指示器, 通过改变这个计数器的值来选取下一条需要执行的字节码指令.

每个线程都有一个独立的程序计数器, 各条线程之间的计数器互不影响, 独立存储, 这类内存区域为” 线程私有” 的内存

虚拟机栈

虚拟机栈用来执行 Java 方法, 每个线程的栈都是私有的. 栈描述的是 Java 方法执行的 内存模型 :

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表, 操作数栈, 动态链接, 方法出口等信息, 每一个方法从调用直至执行完成的过程, 就对应着一个栈帧在虚拟机中从入栈到出栈的过程.

其中的局部变量表存放了编译器可知的各种基本数据类型, 对象引用和 returnAddress.

局部变量表所需的内存空间咋编译期间完成分配

本地方法栈

本地方法栈为虚拟机使用到的 Native 方法服务.

Java 堆

Java 堆的唯一目的就是存放对象的实例, 几乎所有的对象实例都在这里分配内存.

Java 虚拟机中的表述是: 所有对象实例以及数组都要在堆上分配

但是随着 JIT 编译器的发展与逃逸分析技术的逐渐成熟, 栈上分配、标量替换优化技术将会导致一些微妙的变化, 独有的对象都分配到堆上也渐渐变的不是那么” 绝对” 了

方法区

方法区是各个线程共享的内存区域, 用户存储已被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据.

方法区内存回收的目标主要是针对常量池的回收和对类型的卸载.

运行时常量池

运行时常量池是方法区的一部分.Class 文件中除了有类的版本, 字段, 方法, 接口等描述信息外, 还有一项是 常量池 , 用于存放编译期生成的各种字面量和符号引用, 这部分内容将在类加载后进入方法区的运行时常量池中存放.

常量池除了保存 Class 文件中描述的符号引用外, 还会把翻译出来的直接引用也存储在运行时的常量池中.

运行期间也可将新的常量放入到池中, 用的比较多的就是 String 类的 intern() 方法了.

对象的创建

在类加载检查通过后, 虚拟机将为新生对象分配内存.

假设虚拟机中的内存是绝对规整的, 分配内存就仅仅是把指针向空闲空间那边挪动一段与对象大小相等的距离, 这种分配方式成为” 指针碰撞”.

如果内存不是规整的, 那虚拟机必须维护一个列表, 记录上哪些内存块是可用的, 在分配的时候从列表中找到一块儿足够大的空间划分给对象实例, 并更新列表上的记录, 这种分配方式被称为” 空闲列表”.

选择哪种分配方式由 Java 堆是否规整来决定, 而堆是否规整又由 GC 是否有 压缩整理 功能来决定

并发的情况下怎么给对象分配内存

一种是在分配的时候给分配行为加锁 一种是每个线程在 Java 堆中预先分配一小块儿内存, 称为 本地线程分配缓冲 (TLAB), 哪个线程要分配内存了, 就在它自己的 TLAB 中先分配内存, 只有在 TLAB 用完之后需要重新分配新的 TLAB 的时候, 才需要同步锁定.

内存分配时候的大致逻辑

首先判断这个类是否已经解释和初始化, 然后看 TLAB 够不够, 不够移动指针分配去. 够的话分配内存, 然后判断是否要置为 0, 再判断是否启用偏向锁来设置对象头信息, 最后将对象放入到堆中.

对象的内存布局

对象在内存中存储的布局可以分为 3 个区域: 对象头 (Header), 实例数据 (Instance Data) 和对象填充 (Padding)

HotSpot 虚拟机的对象头包括两部分信息

  1. 第一部分用于存储对象自身的运行时数据 ( 哈希码,GC 分代年龄, 锁标识状态, 线程持有的锁, 偏向线程的 ID, 偏向时间戳等)
  2. 第二部分是类型指针, 类型指针是对象指向它的类元数据的指针, 也就是这个指针用来指示这个对象是哪个类的对象.

实例数据部分是对象真正存储的有效信息, 是代码中定义的各种字段内容. 包括从父类继承下来的和在子类中定义的, 这些数据在内存中的顺序受到虚拟机分配策略以及字段在 Java 中定义顺序的影响. 在满足相同宽度 ( int,long 等的长度 ) 的字段总是分配在一起的前提之下, 在父类中的变量会出现在子类之前.

¥ 有帮助么?打赏一下~

转载请注明:热爱改变生活.cn » 深入理解 Java 虚拟机 笔记 (2018.06.19-23)


本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » 深入理解 Java 虚拟机 笔记 (2018.06.19-23)

喜欢 (0)
发表我的评论
取消评论
表情

如需邮件形式接收回复,请注册登录

Hi,你需要填写昵称和邮箱~

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址