JVM
自动内存管理
运行时数据区
线程私有的:虚拟机栈、本地方法栈、程序计数器
线程共享的:方法区、堆
程序计数器
线程私有的区域,指向本线程下一条要执行的字节码。不会抛出OOM异常
虚拟机栈
为执行java方法提供服务,如果栈深度超过允许范围,会抛出StackOverflowError;如果允许虚拟机栈扩展,在申请不到内存无法进行扩展时,会抛出OutOfMemoryError
本地方法栈
为执行native方法提供服务,类似于虚拟机栈,也会抛出StackOverflowError和OutOfMemoryError
堆
为对象实例和数组分配内存要在堆上进行。会抛出OOM异常。
堆可以位于物理上不连续的空间,逻辑连续即可。
GC的主要区域
方法区
保存虚拟机运行过程中加载的类信息、常量、静态变量等。
无法分配内存时,抛出OOM异常
这个区域的GC只要回收无用常量、卸载类
对象的创建过程
类加载、分配内存(如何保证原子性?)、内存清零、设置对象头、init
对象的内存布局:对象头、实例数据、对齐填充
垃圾回收机制
自动垃圾回收需要考虑的三个问题:
- 回收哪些内存?对象存活判断(引用计数、可达性分析)
- 什么时候回收?
- 怎么回收?
垃圾回收算法
标记-清除
空间碎片
复制算法
Eden:From Survivor:To Survivor=8:1:1
复制操作效率低
标记-整理
分代算法
新生代和老年代采用不同算法
垃圾收集器
CMS
Concurrent Mark Sweep,顾名思义,采用标记清除算法回收垃圾,工作在老年代,多线程。工作过程如下:
- 初始标记(Stop The World):标记与GC Roots直接相关的对象
- 并发标记:根据上一阶段的标记遍历标记
- 重新标记(STW):处理上一阶段用户线程对内存做的改动
- 并发清除
CMS的缺点:
- 对CPU资源非常敏感
- 浮动垃圾:并发清除阶段由用户线程产生的垃圾内存
- 空间碎片:标记清除算法的缺陷
G1
Garbage-First,多线程,新生代和老年代不再物理隔离,将内存分区Region,新生代和老年代都由一系列的Region组成,维护Region的优先队列,每次优先回收价值大的Region(因此叫GarbageFirst),标记整理算法
工作过程:
- 初始标记:STW,标记与GC Roots直接相关的对象
- 并发标记:遍历标记
- 最终标记:STW,修正
- 筛选回收:STW,对Region根据回收价值和成本进行排序,按照用户期望进行回收
GC 分为两类:minorGC和major/fullGC。
minorGC发生在新生代,快;
fullGC发生在老年代,慢一个数量级
内存分配策略
自动内存管理可以分为两个模块:如何分配内存、如何回收内存
堆的分区
总体分为新生代和老年代
新生代采用复制算法回收内存,因此又可以分为三个区:Eden、From Survivor、To Survivor
jvm分配内存的常用策略
下面来看jvm分配内存的常用策略:
- 对象优先分配在Eden区
- 大对象直接进入老年代,利用担保机制
- 长期存活的对象进入老年代:每个对象都有年龄,如果对象熬过第一次minorGC并且进入了Survivor区,则age=1;以后每次存活,age++;当age达到一定值之后,对象晋升老年代
新生代对象内存分配过程:首先查看eden区空间是否够用,不够用则进行一次minor gc到survivor区,如果survivor区也不够用,通过分配担保机制转移到老年代。
JVM调优
类加载
类加载是如何把字节码转换成运行时数据
类加载的过程
类加载分为:
加载、验证、准备、解析、初始化、卸载
其中加载、验证、准备、初始化、卸载这5个过程必须按顺序开始(而不是“完成”)
加载
加载过程做了什么?
- 根据类全限定名获取二进制字节码
- 将字节码转换为运行时数据结构放在方法区
- 创建该类对应的Class对象
验证
验证字节码是否符合虚拟机规范
准备
类变量分配内存、置零
解析
符号引用转化为直接引用
符号引用:字面量
直接引用:地址
初始化
类构造方法 <clinit>()
,由类变量赋值语句和static块合成
类加载器
类和它的类加载器共同确立了这个类在jvm中的唯一性
双亲委派模型
类加载器具有层次化,从上往下依次是:
- 启动类加载器
- 扩展类加载器
- 应用类加载器
双亲委派模型的工作过程:
如果一个类加载器收到了一个类加载请求,它首先会把加载任务交给上一层类加载器,如果上层类加载器不能完成类加载,则由自己完成类加载。
破坏双亲委派模型
- 重写loadClass()方法
- 线程上下文类加载器 Thread Context ClassLoader
- OSGi