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 对象已死吗 在垃圾收集器进行回收 ...
随机推荐
- 如何做好错误处理?(PHP篇)
起因 之前我在封装 PHP 一个类库的时候,如果有遇到错误(例如构造函数传参不合法的话),则直接 die() ,后来发现这种方法很不好,会直接退出程序. 所以我想到给 PHP 上异常捕获的机制了. 错 ...
- pringboot+mybatis+redis+cookie单点登录
一.基本思路 单点sso用于多系统分布式,当多个系统分布式部署后,当然需要统一的登录接口.sso应运而生. 可以想见,单点应该是提供一个服务给其他系统,当其他系统需要验证登录状态的时候,调用服务,就可 ...
- Docker - 基础讲义
Docker Docker - 官网 Docker - Hub GitHub - Docker dockerinfo Docker中文社区 Docker入门教程 Docker从入门到实践 虚拟化技术 ...
- windows下docker的安装及常用命令学习
docker search 镜像名 本文主要介绍Docker在Windows下的安装.关于Docker的介绍和文档在其官网中可以找到:http://www.docker.com .安装环境:Windo ...
- 为什么需要jQuery Mobile
1.没有所谓的移动互联网,只有一个互联网 2.设计移动网站不需要什么特别处理 3.一个站点应当在所有设备(台式机.手机.电视)上都能运转 jQuery Mobile诞生 ...
- 【LeetCode】547. 朋友圈
题目 班上有 N 名学生.其中有些人是朋友,有些则不是.他们的友谊具有是传递性.如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友.所谓的朋友圈,是指所有朋友的集 ...
- vue教程3-07 vue-loader
vue-loader: vue-loader: 其他loader -> css-loader.url-loader.html-loader..... 后台: nodeJs -> requi ...
- python多线程-Semaphore(信号对象)
Semaphore(value=1) Semaphore对象内部管理一个计数器,该计数器由每个acquire()调用递减,并由每个release()调用递增.计数器永远不会低于零,当acquire() ...
- Java8简明指南
Java8简明指南 转载自并发编程网 – ifeve.com本文链接地址: Java8简明指南 欢迎来到Java8简明指南.本教程将一步一步指导你通过所有新语言特性.由短而简单的代码示例,带你了解如何 ...
- Hadoop(三)搭建Hadoop全分布式集群
原文地址:http://www.cnblogs.com/zhangyinhua/p/7652686.html 阅读目录(Content) 一.搭建Hadoop全分布式集群前提 1.1.网络 1.2.安 ...