普及一下虚拟机中的一些区域,垃圾收集算法,垃圾收集器等。
运行时数据区域
程序计数器
- 线程私有
- 一块较小的内存空间
- 记录正在执行的java方法的虚拟机字节码指令的地址(native方法为空)
- 没有OutOfMemoryError的情况
Java虚拟机栈
-
线程私有
-
为虚拟机执行java方法服务
-
与线程的生命周期相同
-
当线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError,当虚拟机扩展时无法申请到足够的内存,将会抛出OutOfMemoryError
-
方法执行时创建的栈帧用于存储局部变量表,操作数栈,动态链接,方法出口
-
方法调用时分配栈帧,离开时撤销栈帧
-
局部变量表:
- 存放基本数据类型
- 对象引用
本地方法栈
- 为虚拟机执行native方法服务
- 和虚拟机栈相同,会抛出StackOverflowError和OutOfMemoryError
Java堆
- 线程共享
- 内存中最大的一块
- 存放对象实例以及数组
- 垃圾收集器管理的主要区域(GC堆)
- 堆中分为新生代和老年代
- 堆无法再扩展时,抛出OutOfMemoryError
方法区
- 线程共享
- 存储已被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码
- 方法区无法满足内存需求时抛出OutOfMemoryError
- 使用永久代实现
运行时常量池
- 方法区的一部分
- 存储字面量和符号引用
- 无法再申请到内存时抛出OutOfMemoryError
对象的访问
句柄访问
- 引用存储对象的句柄地址
- 对象被修改时只会改变句柄中的实例数据指针,引用本身不改变
直接指针访问
- 引用存储对象的地址
- 速度更快,Sun HotSpot虚拟机中使用
异常
堆溢出
- 不断创建对象实例并且GC Roots到对象之间有可达路径避免垃圾回收时会产生(OOME Java heap space)
栈溢出
- 栈深度大于虚拟机所允许的深度(SOE)
- 虚拟机扩展时无法申请到足够的内存
- 多线程下导致的内存溢出,可以通过减少最大堆和减少栈容量来换取更多的线程(OOME)
方法区和运行时常量池溢出
- 产生大量的动态类(OOME PermGen space)
判断对象是否已经死亡
引用计数算法
- 有一个地方引用,计数器加1,引用失效时减1,当为0时即代表不可能被使用
- 缺点是无法解决循环引用的问题
A.instance = B;B.instance = A。两个对象都已经不可能被访问
可达性分析算法
-
GC Roots对象作为起点,判断是否存在路径(引用链)可以到达对象
-
可以作为GC Roots的对象有:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- native方法引用的对象
引用
强引用
A a = new A();
只要强引用存在,垃圾回收器永远不会回收被引用的对象
软引用
- 有用但非必需的对象
- 在将要发生内存溢出之前,进行回收,这次回收内存还没有足够的内存,才会抛出异常
- 使用SoftReference来实现
弱引用
- 非必需对象
- 只能生存到下一次垃圾收集发生之前
- 使用WeakReference来实现
虚引用
- 无法获得对象实例
- 唯一目的是被垃圾回收时收到一个系统通知
- 使用PhantomReference来实现
垃圾收集算法
标记-清除算法
- 首先标记需要回收的对象
- 清除标记的对象
- 缺点是会产生大量不连续的内存碎片,当需要分配较大对象时,会提前触发另一次垃圾回收
复制算法
- 将内存按容量划分为大小相等的两块,每次只使用其中的一块,使用完成后将存活的对象复制到另一块,然后清理使用过的内存空间
- 缺点是浪费内存
标记-整理算法
- 首先标记需要回收的对象
- 将所有存活的对象向一端移动
- 清理掉边界以外的内存
分代收集算法
- 分为新生代和老年代进行收集
- 新生代每次都有大批对象死去,可以选用复制算法,老年代中对象存活率高,使用标记-清除算法或者标记-整理算法
垃圾收集器
Serial收集器
- 单线程收集器,垃圾收集时,需停止其他的所有工作线程(Stop The World)
- 在Client模式下时很好的选择
- 新生代采用复制算法,老年代采用标记-整理算法
- 由于单线程的环境,所以简单高效
- 新生代收集器
Serial Old收集器
- Serial收集器的老年代版本
- 在jdk1.5之前和Parallel Scavenge收集器搭配使用(1.5之后才有Parallel Old收集器)。
- 作为CMS收集器的后备预案(发生Concurrent Mode Failure时使用)
- 老年代收集器
ParNew收集器
- Serial收集器的多线程版本
- 新生代采用复制算法,老年代采用标记-整理算法
- 与CMS收集器搭配使用
- 并发收集器
- 新生代收集器
Parallel Scavenge收集器
- 吞吐量优先
- 可以直接设置最大垃圾收集停顿时间(缩短停顿时间,同时也会缩短停顿间隔,减小吞吐量)和吞吐量(代码运行时间/代码运行时间+垃圾回收时间)大小
- 可以使用自适应的调节策略来控制停顿时间和吞吐量(与ParNew的主要区别)
- 年轻代收集器
- 多线程
Parallel Old收集器
- 多线程
- 老年代收集器
- jdk1.6中开始提供
CMS收集器
-
停顿时间最短化
-
分为四个步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记和重新标记需要Stop The World但是时间并不长,剩下两个步骤可以和用户线程并发执行
- 基于标记-清除(注意),会产生大量的内存碎片
- 缺点是并发占用一部分用户线程,造成吞吐量降低应用程序变慢,无法处理浮动垃圾(垃圾回收时产生的垃圾)
G1收集器
-
分代收集
-
使用多个cpu并行执行垃圾回收(缩短Stop The World的时间),通过并发使其他线程和垃圾回收线程一起执行
-
整体上标记-整理算法,局部上基于复制算法
-
可预测何时停顿以及停顿时间,通过后台维护一个垃圾回收优先列表来实现
-
与其他收集器不同,不再以新生代和老年代做区分,而是划分为多个大小相同的区域。
-
通过Remembered Set来避免全堆扫描
-
分为四个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
每一本书,都是我们升华的阶梯
网友评论