前言
最近发现有时候看完一本书,时间久了容易忘记,看书不总结思考效果大打折扣,故打算写这一系列文章,一是为了整理书中的要点,帮助自己消化理解;二是勉励自己多看书思考。文章中不会把书中内容讲解的非常详细,只是总结概括,适合已经阅读过该书的读者。
第2章:Java内存区域与内存溢出异常
(1)JVM运行时数据区
- 程序计数器
- 当前线程所执行的字节码的行号指示器
- Java虚拟机栈
- 线程私有
- 基础数据类型,引用类型
- 本地方法栈
- 与Java虚拟机栈类似
- 前者为执行Java方法(字节码)服务,后者为Native方法服务
- Java堆
- 所有对象实例以及数组分配
方法区
- 加载的类信息、常量、静态变量、即时编译后的代码等数据
- 运行时常量池
特殊:直接内存
- NIO类,可直接分配堆外内存DirectByteBuffer,受本机总内存限制
(2)OutOfMemoryError异常
- Java堆溢出
- 无限new对象
- 虚拟机栈、本地方法栈溢出
- 无限递归函数、无限创建线程
- 方法区、运行时常量池溢出
- 无限产生String使常量池溢出
- 产生大量类使方法区溢出(利用反射等)
- 直接内存溢出
- 填充DirectByteBuffer
第3章:垃圾收集器与内存分配策略
(1)判断对象存活
- 引用计数算法
- 可达性分析算法
- 实现:GC Root:虚拟机栈中所引用的对象、方法区中类静态属性引用的对象、方法区中常量引用。虚拟机可直接得知哪些地方存放着对象引用,如在HotSpot虚拟机中使用OopMap的数据结构实现。
- 强引用:永远不会回收
- 软引用:内存溢出前回收
- 弱引用:下一次GC被回收
- 虚引用
(2)垃圾收集算法
- 标记-清楚算法
- 简单,效率不高,产生碎片
- 复制算法
- 效率较高,浪费空间
- 标记-整理算法
- 没有碎片
- 分代收集算法
- 新生代,老年代
(3)垃圾收集器
- Serial收集器
- 单线程,简单而高效,开始收集时停止所有工作线程(Stop The World)
- 适用于Client模式下的虚拟机
- ParNew收集器
- Serial的多线程版,Stop The World,
- 只有Serial和ParNew能与CMS配合使用
- Server模式下的虚拟机首选新生代收集器
- Parallel Scavenge收集器
- 基于优化吞吐量,上述两者是基于优化停顿时间
- 适合在后台运算而不需要太多交互的任务
- Serial Old收集器
- 在于给Client模式下的虚拟机用
- Parallel Old收集器
- 配合Parallel Scavenge使用,“吞吐量优先”收集器
- CMS收集器
- CMS(Concurrent Mark Sweep)收集器是一种以获得最短回收停顿时间为目标的收集器
- 优点:并发收集、低停顿
- 缺点:消耗CPU,无法处理浮动垃圾,基于标记清楚触发内存整理时无法并发
- G1收集器
- 特点:并行并发、分代收集、空间整合、可预测停顿
(4)内存分配
- 优先在新生代Eden区中分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代(对象Age计数器)
- 动态对象年龄判定
第6章:类文件结构
(1)系统无关性
- 实现编程语言与操作系统的无关性的基础是虚拟机和字节码存储格式,Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式关联
- 编程语言->编译器->字节码(.class文件)->虚拟机
(2)Class文件结构
- 任何一个Class文件都对应着唯一一个类或接口的定义信息,但类或接口并不一定定义在Class文件中,也可通过类加载器直接生成等
- Class文件包含有代码以及支持代码用的元数据(metadata)
- 代码部分就是字节码
- 元数据部分则包括诸如类名、成员名、方法签名、常量池、方法大小、方法的求值栈占用量等许多信息
- Class文件格式包括魔数、常量池、访问标志、类索引、父类索引、接口索引、字段集合、方法集合等
第7章:虚拟机类加载机制
(1)名词
- 虚拟机类加载机制(简称类加载)
- 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
- 加载
- 类加载过程中的一步
- 类加载器
(2)类加载的时机
- 虚拟机规范没强制要求加载的时机,但是严格规定了有且只有5种情况必须立即对类进行初始化
- 遇到new,getstatic,putstatic,invokestatic这4条字节码指令,常见的即使用new关键字、读取或设置一个类的静态字段(被final修饰的除外)
- 使用java.lang.reflect包的方法对类进行反射调用时
- 初始化一个类,但其父类未初始化时
- 虚拟机启动时,用户需要指定一个要执行的主类
- 当使用JDK1.7的动态语言支持时….(少用)
(3)类加载过程
- 加载
- 虚拟机需要完成3件事
- 1.通过一个类的全限定名来获取定义此类的二进制字节流
- 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 加载阶段完成后,虚拟机外部的二进制字节流就按虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义
- 虚拟机需要完成3件事
- 验证
- 目的:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包含:
- 文件格式验证:只有通过这个阶段,字节流才会进入内存的方法区中进行存储,所以后面的3个验证阶段全部基于方法区的存储结构进行,不再直接操作字节流
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备
- 正式为类变量分配内存,并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配
- 注意!
- 1:进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量在对象实例化时在Java堆中分配
- 2:初始值指数据类型的零值,实际赋值在初始化阶段
- 解析
- 将常量池内的符号引用替换为直接引用的过程
- 初始化
- 到了初始化,才真正执行类中定义的Java程序代码(即字节码)
(4)类加载器
定义:类加载阶段中“通过一个类的全限定名来获取定义此类的二进制字节流”这个动作在Java虚拟机外实现,实现这个动作的代码称为“类加载器”
判断类是否相等
- 1:是否由同一个加载器加载
- 2:这个类本身是否相等
3种系统提供的Java程序类加载器
- 1:启动类加载器:负责将\lib下的类库加载到虚拟机内存中
- 2:扩展类加载器:负责加载\lib\ext下的类库
- 3:应用程序类加载器:用户路径上的类库加载
双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都有自己的父类加载器
- 工作过程:一个类加载器收到类加载请求时,首先把请求委派给父类加载器去完成,只有父类加载器反馈无法完成,子加载器才尝试加载
- 好处:Java类随着它的类加载器一起具备有优先级的层次关系,详见“判断类是否相等”
第8章:虚拟机字节码执行引擎
所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果
许多Java虚拟机的执行引擎在执行Java代码的时候有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)
第10章:早期(编译期)优化
(1)概述
- 前端编译器:Javac
- 把.java文件转变成.class文件的过程
- JIT编译器:HotSpot VM的C1,C2编译器
- 即时编译器,把字节码转变成机器码的过程
(2)前端编译器
- 编译过程大致分为3个过程
- 1:解析与填充符号表过程
- 词法分析、语法分析等
- 2:插入式注解处理器的注解处理过程
- 3:分析与字节码生成过程
- 语法糖解析,字节码生成
(3)语法糖
- 范型与类型擦除
- C#的范型仔系统运行期间生成,有自己的虚方法表和类型数据,即类型膨胀,是真实范型
- Java语言的范型是基于类型擦除,即编译过程中转为原生类型,并响应地方插入强制转换,伪范型
第11章:晚期(运行期)优化
即时编译器:为了提高热点代码执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码
(1)优缺点
- 解释器
- 迅速启动,执行快,节约内存
- 编译器
- 时间推移,发挥作用,提升执行效率
(2)判断热点代码
- 被多次调用的方法
- 基于采样的热点探测:周期性检查各个线程的栈顶,统计哪个方法出现频率高
- 基于计数器的热点探测:方法调用计数器
- 被多次执行的循环体
- 基于计数器的热点探测:回边计数器
(3)编译优化技术
- 公共子表达式消除
- 如果一个表达式E已经计算过,并从先前到现在没有发生变化,那可采用之前的计算结果
- 数据边界检查消除
- 数据的边界检查是不是必须在运行期一次不漏检查
- 方法内联
- 减少函数调用
- 逃逸分析
- 对象在一个方法中定义,并且被外部方法引用,称为逃逸,有方法逃逸,线程逃逸
- 如果证明一个对象不会逃逸到方法或线程外,可以:
- 栈上分配
- 同步消除
- 标量替换:把对象拆分
第12章:Java内存模型与线程
Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异
主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节
(1)Java内存模型
- Java内存模型规定了所有的变量都存储在主内存中(类似Java堆中的对象实例),每条线程还有自己的工作内存(类似虚拟机栈的部分域),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
(补充图)
(2)Java内存模型特征
- 原子性
- 保证原子性操作
- 可见性
- 指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
- 有序性
- 总结一句话:在本线程内观察,所有操作都是有序的;在另一个线程观察,所有操作是无序的
(3)Java线程实现
- 内核线程实现
- 用户线程实现
- 用户线程加轻量级进程混合实现