Java虚拟机--垃圾收集器和内存分配
垃圾收集器和内存分配
程序计数器、虚拟机栈、本地方法栈这三个区域和线程的生命周期一致,所以方法结束或者线程结束时,内存自然就跟着回收了。Java堆和方法区,只有在程序处于运行期间才能知道会创建哪些对象,即这部分的内存分配和回收都是动态的,垃圾回收主要关注的是堆内存。
对象存活判断
在进行垃圾回收之前,首先要判断哪些对象还存活,哪些已经死去去。判断对象存活的方法,有如下几种:
引用计数法
每个对象有一个引用计数器,每当有一个地方引用了它计数+1;引用失效计数器-1;当引用计数为0时,说明这个对象在任何地方都不被使用了,可以进行回收了。
引用计数法有缺点:对象之间的循环引用。当两个对象互相引用对方,除此之外它们都再无其他任何引用时,两个对象的引用计数都不为0,造成了GC收集器无法回收它们。
可达性分析法
Java中正是使用了这种算法来判断对象是否存活。这种算法使用了类似树形结构来搜索对象,作为根结点的称为GC Roots,是搜索的起点,搜索走过的路径叫做搜索链,可以作为GC Roots的对象有
- 虚拟机栈中引用的对象
- 方法区中类静态属性有引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(通常所说的Native方法)引用的对象
当某个对象到GC Roots的路径上没有引用,或者说从GC Roots开始搜索不到这个对象(GC Roots到这个对象是不可达的),那么该对象就可以被回收。
图中GC Roots到Object5、6、7都不可达。
可达性分析中不可达的对象并不是一定会被回收,对象真正被回收需要经过两次标记。可达性分析后发现GC Roots到某个对象不可达时,该对象会被第一次标记。接着判断,如果:
- 对象没有覆盖
finalize()
方法 finalize()
方法已被调用(只能被系统调用唯一一次)
满足以上条件的任一个,虚拟机则认为“没有必要执行finalize方法”,接着经过第二次标记后,对象被回收。否则,有必要执行finalize,对象进入F-Queue队列之中,finalize方法是对象存活的最后机会——只需和引用链上的任一个对象关联即可,那么在第二次标记时将被移除出“即将回收”的集合;如果还不能finalize中逃脱,该对象才真正被回收。
引用
引用有4种:
- 强引用。比如
Object obj = new Object()
,只要强引用还在,GC收集器不会将被引用的对象回收。 - 软引用。用于描述一些还有用但非必需的对象,和软引用关联的对象,系统将要发生内存溢出时,会将这些对象列入回收范围中进行第二次回收;若这次回收后还是没有足够的内存,才抛出内存溢出异常。
- 弱引用。也用于描述非必需对象,比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
- 虚引用。最弱的引用关系,对象的虚引用存在与否,不会对其生存时间造成影响,也不能通过引用取得对象。设置虚引用的作用是当对象被回收时能收到一个系统通知。
垃圾收集算法
标记-清除算法
- 标记要回收的对象
- 统一回收被标记的对象
缺点如下:
- 标记和清除两个阶段效率不高
- 清除后产生大量不连续的内存碎片,对之后范培大对象带来不便(不得不提前出发一次垃圾收集)
复制算法
将内存按比例分成两块,每次只使用其中一块。当这一块内存用完了,将存活对象全部复制到另一块中,接着将已使用的内存空间一次性清除。
优点:不会产生内存碎片;缺点:内存利用率低。
标记-整理算法
和标记-清除算法类似,不同的是标记后并不是直接清理,而是让所有存活对象向一端移动,然后直接清除掉端边界外的内存。
分代收集算法
根据对象的存活周期的不同将内存划分为几块,通常将Java堆分为新生代和老年代。根据各个年代的特点采用最适当的收集算法:
- 新生代。每次垃圾收集都发现只有少量对象存活,采用复制算法,因为所需复制操作次数少;
- 老年代。对象存活率高、没有额外的空间对它进行分配担保,必须使用标记-清除或标记-整理算法。
GC进行时必须停顿所有Java执行线程。程序执行时只有在到达安全点才能暂停。而安全区域则是安全点的扩展:指在一段代码中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。
垃圾收集器
Serial收集器:单线程的收集器,在它进行垃圾回收时必须暂停其他所有的工作线程,直到它收集完成为止。优点:简单高效,没有线程交互的开销;缺点:GC时候其他线程不能工作。
ParNew收集器:Serial的多线程版本,使用多条线程进行垃圾收集,其余和Serial收集器几乎一致。
Parallel Scavenge收集器:收集器使用复制算法的新生代收集器,且是并行的多线程收集器。该收集器的目的是达到一个可控制的吞吐量。
吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + GC收集时间)
Serial Old收集器:Serial收集器的老年代版本,使用标识-整理算法。
Parallel Old收集器:Paralell Scavenge的老年版本,使用多线程和标记-整理算法。
CMS收集器:老年代的收集器目的是尽可能缩短垃圾收集时用户线程的停顿时间。适合需要用户交互的场景,能获得较短的响应时间。
基于标记-清除算法实现,整个过程分为以下4步:
- 初始标记:标记GC Roots能直接关联到的对象
- 并发标记:进行GC Roots Tracing的过程,即在堆中堆对象进行可达性分析,从GC Roots开始找出存活的对象
- 重新标记:修正并发标记期间因用户程序继续运作导致标志产生变动的那部分对象的标志记录
- 并发清除:并发清除要回收的对象
缺点:
- CMS无法处理浮动垃圾(浮动垃圾指CMS在并发清理的过程中用户线程还在继续运行,因此还会产生垃圾,这些新产生的垃圾在标记之后,故CMS无法在本次收集中清理掉它们)
- CMS基于标记-清除,故会产生大量不连续的内存碎片
- 对CPU资源很敏感
G1收集器:有如下特点:
- 并行与并发
- 分代收集
- 从整体上看是基于标记-整理算法实现,从局部(两个Region之间)来看是基于复制算法的收集器,以确保G1运行期间不会产生内存空间碎片
- 可预测的停顿,G1除了追求低停顿外,还能建立可预测的时间模型,主要原因是它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
在使用G1收集器时,Java堆的内存划分为多个大小相等的独立区域,新生代和老年代不再是物理隔离。G1跟踪各个区域的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域。
G1收集器的运作大概有以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
前三个步骤和CMS的前三个步骤类似。最后一步筛选回收会对各个区域的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。
内存分配和回收策略
对象主要分配在新生代的Eden区上,少数情况下也可能直接分配在老年代。当Eden区没有足够的空间时,虚拟机将发起一次Minor GC。
- Minor GC:发生在新生代的垃圾收集,Java对象大多“朝生夕灭”,因此Minor GC比较频繁,回收速度快;
- Full GC / Major GC:发生在老年代的GC,出现Full GC一般会伴随至少一次的Minor GC,且速度相比Minor GC会慢很多。
大对象会直接进入老年代。大对象指的是需要大量连续内存空间的Java对象,比如长字符串和大数组。
长期存活的对象将进入老年代。虚拟机给每个对象设置了一个对象年龄计数器,如果对象在Eden区出生并经历过一次Minor GC后仍然存活,且能被Survivor区容纳的话,该对象将被移动到Survivor区,且对象年龄设为1。此后,该对象每在Survivor区“熬过”一次Minor GC,对象年龄就+1,当对象年龄增长到一定程度(默认15岁)就晋升到老年代。但这个准则并不是一定的,如果在Survivor区中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接晋升老年代。
Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否能容纳新生代所有对象空间,若满足,则此次Minor GC是安全的;若不满足,则虚拟机会查看是否设置了允许担保失败,若允许,判断老年代的最大可用连续空间是否大于历次晋升到老年代对象的平均大小,若大于,就尝试着进行一次Minor GC,如果小于或者设置了不允许担保失败或者,那么将进行一次Full GC。
by @sunhaiyu
2018.6.9
Java虚拟机--垃圾收集器和内存分配的更多相关文章
- Java虚拟机垃圾收集器与内存分配策略
Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...
- 深入理解java虚拟机----->垃圾收集器与内存分配策略(下)
1. 前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保 2. 垃圾 ...
- 深入理解JAVA虚拟机 垃圾收集器和内存分配策略
引用计数算法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用的 ...
- [深入理解Java虚拟机]<垃圾收集器与内存分配策略>
Overview 垃圾收集考虑三件事: 哪些内存需要回收? 什么时候回收? 如何回收? 重点考虑Java堆中动态分配和回收的内存. Is Object alive? 引用计数法 给对象添加一个引用计数 ...
- Java虚拟机 垃圾收集器与内存分配策略
说起GC,我们要思考的主要有三件事 哪些内存需要回收 那些已经“死去”的对象,那么哪些对象“死”,哪些对象“活”呢,有个简单的办法 引用计数法,但是没法解决循环依赖问题 所以Java虚拟机采用的是可达 ...
- java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...
- 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略
1. 前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...
- 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略
前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...
- 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略
垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...
随机推荐
- WPF 斜角border
最近看了一些科技感UI设计,其中很多的按钮都不是常见的圆角边,而是斜角边.查了一下,wpf中好像没有现成的斜角border,网上也没搜到现成的,于是自己写了点时间做了一个,写的较简单,有一些bug(主 ...
- C语言实现简单CMDShell
1.首先使用vc6编译器编译后门,并运行 #pragma comment(lib,"ws2_32.lib") #ifdef _MSC_VER #pragma comment( li ...
- 全世界最顶级黑客同时沸腾在DEF CON 25,是怎样一种体验?
2017,我在这里!圆你黑客梦!! 被称为黑客“世界杯”与“奥斯卡”的美国黑帽技术大会Black Hat和世界黑客大会DEF CON 是众多黑客心中最神圣的梦! 有位小表弟告诉我说:Black ...
- 漏洞复现-vsftpd-v2.3.4
vsftpd-2.3.4早期版本存在恶意的后门,在钟馗之眼上目前骇客以收到如此的主机,不过很多的服务器都已经被修复过,但总有漏网之鱼,有兴趣的小伙伴不妨去试试 0×01前言: vsftpd-2.3.4 ...
- .NET Core 从1.1升级到2.0记录(Cookie中间件踩坑)
.NET Core 2.0 新时代 万众瞩目的.NET Core 2.0终于发布了,原定于9.19的dotnetconf大会的发布时间大大提前了1个月,.NET Core 2.0/.NET Stand ...
- Ubuntu系统的安装(虚拟机) 并配置C/C++编译器
一.系统的初始化配置 1.配置静态IP和DNS 配置静态IP 1.sudo vim /etc/network/interfaces,修改文件内容如下: auto eth0 #表示让网卡开机自动挂载e ...
- 使用ServiceStack改造我们的项目
ServiceStack是一个NET环境下的开源框架集合 包括轻量级的Orm框架,数据库访问,Json处理,Redis驱动等多个模块,我们可以按需选择使用 serviceStack.Ormlite s ...
- Git使用(1)
安装git完成后 1.首先配置你的用户信息,用于体现在你的提交记录中包含your name and your email git config --global user.name "you ...
- i=i+1,i+=1,i++哪个执行效率最高?为什么?
(1)i=i+1最低,它的执行过程如下:读取右i的地址i+1读取左i的地址将右值传给左边的i(编译器并不认为左右i的地址相同)(2)i+=1其次,它的执行过程如下:读取左x的地址i+1将得到的值传给i ...
- gulp 使用入门
什么是gulp? 用自动化构建工具增强你的工作流程! Gulp 是基于node.js的一个前端自动化构建工具,开发者可以使用它构建自动化工作流程(前端集成开发环境). 使用gulp你可以简化工作量,让 ...