深入理解JVM

一整篇讲完Java虚拟机,作为《深入理解JVM》一书阅读笔记

1. 运行时数据区

这里写图片描述


2. 垃圾回收

2.1 对象已死算法

GC的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象

2.1.1 引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器就+1,当引用失效时,计数器就-1.任何时刻计数器为0的对象就是不可再被使用的。

2.1.2 可达性分析算法

通过一系列的”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

2.2. 生存还是死亡

真正宣告一个对象死亡,至少要经历两次标记过程

  1. 进行可达性分析后发现没有与GC Roots相连接的引用链
  2. 如果判定有必要执行finalize()方法,则会放置在F-Queue队列中

2.3. 回收方法区

  1. 废弃常量

    • 没有任何String对象引用常良池中的”abc”常量,也没有其他地方引用了这个字面量
  2. 无用的类

    1. 该类的所有的实例都已经回收,也就是Java堆中不存在该类的任何实例
    2. 加载该类的ClassLoader已经被回收
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

2.4 垃圾收集算法

2.4.1 标记-清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点
1. 效率不高
2. 产生大量的不连续的内存碎片

2.4.2 复制算法

将可用的内存划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用的内存空间一次清理掉

2.4.3 标记-整理算法

首先标记出所有需要回收的对象,让所有存货的对象都向一端移动,然后直接清理掉端边界以外的内存

2.4.4 分代收集算法
  • 新生代每次都有大批对象死去,只有少数存活,选择复制算法
  • 老年代对象存活率高,没有额外空间进行分配担保,就使用”标记-清理”或”标记-整理”算法。

2.5 垃圾收集器

HotSpot虚拟机的垃圾回收器

2.5.1 Serial 收集器(复制)

单线程收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。简单而高效

2.5.2 ParNew 收集器(复制)

是Serial收集器的多线程版本

2.5.3 Parallel Scavenge 收集器(复制)

为了达到一个可控制的吞吐量

2.5.4 Serial Old(标记整理)

单线程收集器,与 Parallel Scavenge 搭配使用,作为CMS收集器的后备预案

2.5.5 Parallel Old(标记整理)

Parallel Scavenge 老年代版本

2.5.6 CMS(Concurrent Mark Sweep)收集器(标记清除)

以获取最短回收停顿时间为目标的收集器
分为四个步奏
1. 初始标记(STW)
2. 并发标记
3. 重新标记(STW)
4. 并发清除

2.5.7 G1收集器

面向服务端应用的垃圾收集器。
特点
1. 并行与并发
2. 分代收集
3. 空间整合
4. 可预测的停顿


3. 类加载机制

3.1 类文件结构

3.2 类加载机制


加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的,类必须按照这种顺序按部就班的开始。

3.2.1 有且只有五种情况必须立即对类进行”初始化”
  • 遇到new、getstatic、putstatic、invokestatic

    1. 实例化对象
    2. 读取或设置静态字段
    3. 调用一个类的静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用的时候。

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则触发父类初始化
  • 虚拟机启动时,指定的main方法所在的类
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果的方法句柄对应的类没有进行过初始化,则触发其初始化
3.2.2 加载
  1. 通过一个类的全限定名来获取定义此类的二进制流
  2. 将这个字符流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

数组类本身不通过类加载器创建,是由Java虚拟机直接创建的。

3.2.3 验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
1. 文件格式验证
2. 元数据验证
3. 字节码验证
4. 符号引用验证

3.2.4 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段

  1. 进行内存分配的仅包括类变量,而不包括实例变量
  2. 实例变量将在对象实例化时随着对象一起分配在Java堆中
  3. 初始值在通常情况下是数据类型的零值
  4. 对于final修饰的静态变量属于ConstantValue,在准备阶段就会赋值,而不是初始值
3.2.5 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  1. 类或接口的解析
  2. 字段解析
  3. 类方法解析
  4. 接口方法解析
3.2.6 初始化

初始化阶段是执行类构造器clinit方法的过程

  1. clinit是由编译器字段收集类中的所有类变量的赋值运动和静态语句块中的语句合并产生的。
  2. 虚拟机会保证执行子类的clinit之前父类的clinit方法已经被执行完毕(所以第一个肯定是Object
  3. 由于父类的clinit先执行,所以父类中定义的静态语句块要优先于子类的变量赋值操作

注意

  • clinit不是必须的,如果没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为该类生成clinit
  • 接口的clinit不需要先执行父类接口的clinit方法
  • 虚拟机会保证一个类的clinit方法在多线程环境中被正确的加锁同步。

3.2 类加载器

类加载器实现了通过一个类的全限定名来获取此类的二进制字节流,他是在虚拟机外部去实现的

3.2.1 类与类加载器

比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义

3.2.2 双亲委派模型
  1. 启动类加载器 Bootstrap ClassLoader 。

    • 负责将存放在Javahome/lib目录中的类库加载到虚拟机内存中
    • 无法被Java程序直接引用
  2. 扩展类加载器Extension ClassLoader,负责加载Javahome/lib/ext目录中的类库

  3. 应用程序加载器Application ClassLoader
    • 也叫系统类加载器
    • 负责加载用户类路径上所指定的类库

双亲委派模型要求出了顶层的启动类加载器以外,其余的类加载器都应当有自己的父类加载器(组合)
如果一个类收到了类加载的请求,首先会委派给父类加载器去完成,只有父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值