基本结构
类加载子系统
类的生命周期
加载 : class文件从磁盘读取到内存
连接 :
验证 :验证字节码文件的正确性
准备 : 给类的静态变量分配内存,并赋予默认值
解析 : 类装载器装入类所引用的其他所有类
初始化: 为类的静态变量赋予正确的初始值,执行静态代码块
使用 :
卸载
类加载器的种类
启动类加载器(Bootstarp ClassLoader)
负责加载jre的核心类库
扩展类加载器(Extension ClassLoader)
负责加载jre扩展目录ext中的jar包
系统类加载器(Application ClassLoader)
负责加载ClassPath路径下的类包
用户自定义加载器(UserClassLoader)
类加载机制
全盘负责委托机制 : 当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入
双亲委派机制:指定委托父类加载器寻找目标类,在找不到的情况下载自己的路径查找载入目标类
双亲委派的优势: 安全 (防止核心类库被随意篡改) 避免重复加载 (当父类已经加载该类的时候,就不需要子类在加载一次)
运行时数据区(内存结构)
方法区
类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在这里定义.所有定义的方法的信息都保留在改区域,静态变量+常量+类信息(构造函数/接口定义)+运行时常量池都存在方法区.虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却有一个别名叫做Non-Heap(非堆),目的应该是为了和java的堆区分开(jdk1.8以前hotspot虚拟机叫做永久代,持久代 ,jdk时叫做元空间)
堆
虚拟机启动时自动分配创建,用于存放对象的实例,几乎所有对象都在堆上分配内存,当对象无法在该空间申请到内存将抛出OutOfMemoryError异常.同时也是来及收集器管理的主要区域
Eden(新生代)
Survivor(新生代-幸存者区)
Tenured Space(老年代)
栈(虚拟机栈)
局部变量表 操作数栈 动态链接 返回地址
java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息) 不存在垃圾回收问题,只要线程一结束改栈就释放,生命周期和线程一致
本地方法栈
和栈作用相似,区别不过是java栈为JVM执行java方法服务,而本地方法栈为JVM执行native方法服务,等记native方法,在Execution Engine执行时加载本地方法库
程序计数器
就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),有执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
执行引擎
对象
组成
对象头 实例数据 对其填充
状态
无锁 偏向锁 轻量锁 重量锁 GC标记
GC算法和收集器
判断对象可回收
引用计数法
给对象添加一个引用计数器,每当有一个地方引用,计数器+1,引用失效,计数器-1,任何时候计数器为0的对象就是不可能再被使用的
简单,高效 但是对象互相引用时无法判断
可达性分析算法
通过一系列的称为"GC Roots"的对象为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连接的话,则对象不可用
GC Roots根节点: 虚拟机栈的局部变量表,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法引用的对象
如何判断一个类是无用的
该类所有的实例都已经被回收,也就是java堆中不存在该类的实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
垃圾回收算法
标记-清除算法
分为两个阶段 标记和清除 ,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.存在两个不足的地方:
效率问题 标记和清除两个过程的效率不高
空间问题 标记清除后会产生大量不连续的碎片
复制算法
它把内存分为大小相同的两块,每次使用其中一块,当这一块的内存使用完,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉,这样就使每次的内存回收都是对内存区间的一半进行回收
标记-整理算法
根据老年代提出的一种标记算法,标记过程和"标记-清除"一样,但是后续步骤不是之间对可回收对象进行回收,而是让所有存活的对象向一段移动,然后直接清理掉边界以外的内存
分代收集算法
现在商用虚拟机的垃圾收集器基本都采用"分代收集"算法,这种算法就是根据对象存活周期的不同将内存分为几块,一般将java堆分为新生代和老年代,这样就可以使用合适的垃圾收集算法
在新生代中,每次收集都有大量对象死去,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成垃圾收集,而老年代存活几率比较高,而且没有足够的空间对它进行分配担保,就必须选择"标记-清除"或者"标记-整理"
垃圾收集器
java虚拟机规范对垃圾收集器应该如何实现没有任何规定,因为没有所谓最好的垃圾收集器出现,更不会有万金油收集器,只能根据具体的应用场景选择适合的收集器
Stop The Word (STW) : 停顿时间
吞吐量: 垃圾收集的时间和总时间的占比
并行: 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
并发: 指用户线程与垃圾收集线程同时执行(可能交替执行),用户线程在继续执行,而垃圾收集器运行在另一个CPU上
Serial收集器
Serial(串行)收集器是最基本的,历史最悠久的垃圾收集器, 它是一个单线程的收集器,只会使用一条垃圾收集线程去完成垃圾收集工作,而且它在进行收集工作的时候必须暂停其他所有的工作线程,直到收集结束
优势 简单高效(与其他收集器的单线程) 没有线程交互的开销,自然获得很高的单线程收集效率
ParNew收集器
Serial收集器的多线程版本,使用多线程进行垃圾收集 它是许多运行在server模式下的虚拟机的首要选择
Parallel Scavenge收集器(1.8默认)
并行的收集器
类似于Par News收集器 它关注点是吞吐量(高效的利用CPU)
Serial Old 收集器
Serial收集器的老年代版本,它同样是一个单线程收集器 主要有两大用途: 一 在jdk1.5以及以前版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案
Parallel Old 收集器
Parallel Scavenge收集器的老年代版本 使用多线程和"标记-整理"算法,在关注吞吐量以及CPU资源的场合,可以优先考虑Parallel Scavenge和Parallel收集器
CMS收集器
CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的收集器,是HotSpot虚拟机第一款真正意义上的并发收集器
收集过程分为四个步骤
初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快
并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象.单在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象.因为用户线程可能会不断的跟新引用域,所以GC线程无法保证可达性分析的实时性.所以这个算法里会跟踪记录这些发生引用更新的地方
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记时间稍长,远远比并发标记时间短
并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫
CMS主要优势: 并发收集,低停顿 但有三个明显缺点:
对CPU资源敏感(耗CPU)
无法处理浮动垃圾
他使用的回收算法"标记-清除"会导致空间碎片
G1收集器
G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征 被视为1.7中HotSpot虚拟机的一个重要进化特征
并行与并发:G1能充分利用CPU,多核环境下的硬件优势,使用多个CPU来缩短停顿时间.部分其他收集器原本需要停顿java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行
分代收集: 虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念
空间整合: 与CMS的"标记-清除"不同,G1从整体来看是基于"标记-整理"算法实现,从局部来看是基于"复制"
可预测的停顿: 这是G1相对于CMS的另一个大优势,降低停顿时间是G1与CMS共同关注点,但G1除了追求地停顿,还能建立可预测的停顿时间模型
大致分为几个步骤
初始化标记 并发标记 最终标记 筛选回收
G1收集器在后台维护了一个优先列表,每次根据允许收集时间,优先选择回收价值最大的Region,这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间可以尽可能的收集
垃圾收集器的选择
1 优先调整堆的大小让服务器自己选择
2 如果内存小于100m,使用串行收集器
3 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
4 如果允许停顿时间超过1秒,选择并行或者JVM自己选择
5 如果响应时间最重要,并且不超过1秒,使用并发收集器
硬件可以的情况下,官方推荐G1,性能高