java虚拟机系列--内存分配与垃圾回收机制

来源:互联网 发布:安卓 windows 如何切换 编辑:程序博客网 时间:2024/05/29 13:33

读书笔记:《java虚拟机精讲》、《深入理解java虚拟机》、《java虚拟机规范SE》

java虚拟机中的运行时数据区按照访问权限的不同可以分为两大类:线程共享区和线程私有区

线程共享区主要包含:方法区,堆区和运行时常量池

方法区:方法区存储每一个java类的结构信息:比如运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容以及类、实例、接口初始化时需要使用到的特殊方法等数据。
方法区也被称为永久代,如果不显示指定的话,GC回收的目标仅针对方法区的常量池和类型卸载

运行时常量池:常量池可以理解为Class文件中的资源仓库,主要存放字面量和符号引用。字面量比较接近java语言层面的常量概念,如:文本字符串,声明为final的常量值等,符号引用主要包含:1:类和接口的全限定名 2:字段和方法的名称和描述符 3:方法的名称和描述符

堆区:主要存放各种对象实例,几乎所有的对象实例都在这里分配内存。虽然其为线程共享区,不过其中可能分配出多个线程私有的分配缓冲区TLAB(Thread Local Allocation Buffer)。同时,按照分代回收机制可以细分为:新生代和老年代,新生代有可以分为:Eden空间,From Survivor空间和To Survivor空间等。

线程私有区主要分为:虚拟机栈,本地方法栈和程序计数器

程序计数器:每条线程都有,主要用于记录程序的字节码执行的行数

虚拟机栈:每个方法在执行的时候都需要创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息

本地方法栈:和虚拟机栈很类似,只不过虚拟机栈中执行的为Java代码,本地方法栈中执行的为Native方法

直接内存:这块不是虚拟机运行时数据区的一部分,不过这块区域如果频繁使用也可能造成OutOfMemoryError错误。(主要是NIO可以直接使用Native函数进行堆外内存分配)

下面以一个对象从创建到销毁的过程进行说明
当虚拟机需要一个new 指令时,首先检查这个指令参数能否在常量池中定位到一个类的符号引用,并且检查这个类是否已经经历加载、连接、初始化的过程。如果没有,则需要进行相应的类加载过程。如果已经经历了这个过程,那么这个对象所需要的内存大小已经确定了。于是为对象分配内存就相当于在堆中划分一块指定大小的内存出来。根据内存是否规整有两种分配策略,如果内存规整(内存回收时带有压缩功能的),只需要移动空闲指针即可,称为:指针碰撞,如果不规整(采用标记-清除算法的),则需要维护一个空闲内存的列表,在列表中寻找相应的内存空间。 虚拟机先尝试在TLAB空间中(属于Eden区)分配,如果空间不够,则尝试加锁,在直接在Eden中分配,如果仍然不够,则进行MinorGC ,直到可以在其中分配内存。(对于大对象,则直接在老年代分配)。分配完所需内存后,进行零值初始化,然后再初始化对象头和实例数据,最后将引用入栈,更新程序计数器值,这样一个对象就创建完成了。

对象如果只创建不销毁,那么内存很快就会消耗完

java采用自动垃圾回收机制
主要考虑三个问题:
1.哪些内存需要回收?
2.什么时候回收?
3.如何回收?

回收堆区
在堆区几乎存放了所有的对象实例,垃圾收集器进行垃圾回收时,第一件要做的事情就是判断哪些对象已经死亡,需要进行回收
java主要采用可达性分析算法判断对象是否存活
这个算法的思想就是从根节点集合开始向下进行搜索,如果根节点到某个对象是不可达的,则说明这个对象是不可用的,是可以被回收的
根节点集合包含一下几种
1:虚拟机栈中引用的对象
2:方法区类静态属性引用的对象
3:方法区中常量引用的对象
4:本地方法栈中引用 的对象
回收方法区(永久代)
回收目标主要为废弃常量和无用的类
满足一下三个条件才可以被判为无用的类:
1:该类的所有实例都已被回收,也就是java堆中不存在该类的任何实例
2:加载该类的ClassLoader已经被回收
3:该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法
1.标记-清除算法
首先标记出所有需要被回收的对象,在标记完成后统一回收。缺点:一是标记和清除的效率可能都不高,二是清除之后可能产生大量不连续的内存碎片
2.复制算法
将内存区域分为两块,每次只使用其中的一块,当一块用完了,就将其中存活的对象移到另一块上,将这块完全清空。现在商用的虚拟机大都采用这种算法。Hotspot虚拟机中,Eden和Survivor 的大小区域为8:1
每次只使用Eden和其中的一块Survivor另一块保留
3.标记-压缩算法
这种算法和标记清除算法很像,不过后续步骤不是直接对可回收对象进行整理,二是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

垃圾收集器 图中两两连线的说明可以相互配合使用

这里写图片描述

Serial收集器(复制算法)新生代
其为单线程收集器,不是说只用一个CPU,一条收集线程去工作,而是其在垃圾收集时必须需要暂停其他所有工作线程。优点:简单而高效。缺点显而易见:停顿时间可能过长,影响用户体验
Serial Old收集器(标记-整理算法)老年代
ParNew收集器(复制算法)新生代
这个其实就是Serial收集器的多线程版本,其可以与CMS收集器配合使用
Parallel收集器(复制算法)新生代
并行多线程,注重吞吐量优先
ParallelOld收集器(标记-整理算法)老年代
(这两个主要用于注重吞吐量和CPU敏感的环境)

CMS收集器(标记-清除算法)
这个是以获取最短回收停顿时间为目标的收集器。
整个过程为:
1:初始标记
2:并发标记
3:重新标记
4:并发清除
初始标记和重新标记仍然需要Stop The World 。初始标记只是标记一下根节点集合能直接关联到的对象,速度很快。并发标记阶段就是根节点集合Tracing的过程。重新标记阶段就是为了修正并发标记阶段因用户程序继续运作所导致部分对象的标记变动。
优点:并发收集、低延迟
缺点:对CPU资源很敏感,无法处理浮动垃圾,会产生大量的内存碎片

G1收集器(新生代和老年代)
基于并行和并发,低延迟以及暂停时间更加可控的区域化分代式垃圾收集器
这个收集器和其他收集器有很大不同
它将堆区分为大小相同 约为2048个大小相同的块,每个块控制在1M到32M之间。这样划分的好处是可以更好的提升GC 的回收效率和缩短”Stop the world”的停顿时间。
收集过程为:
1:初始标记阶段
2:根区域扫描阶段
3:并发标记阶段
4:再次标记阶段
5:清除阶段
6:拷贝阶段
G1收集器的执行过程和CMS相似。

0 0
原创粉丝点击