深入理解java虚拟机之垃圾收集器
Java一个重要的优势就是通过垃圾管理器GC (Garbage Collection)自动管理和回收内存,程序员无需通过调用方法来释放内存。也因此很好多的程序员可能会认为Java程序不会出现内存泄漏的问题,这种想法是不对的,当我们对内存使用不当的时候仍然可能会出现内存泄漏,并且问题相对与c++来说更隐秘,问题的根源排查起来也比较困难。不过,当我们了解了Java虚拟机内存区域,Java垃圾收集器之后,对于解决内存泄漏的问题也就不是什么困难的事情了。
相关阅读
在前一篇博客中已经已经详细分析了java内存运行时各个区域,其中程序计数器、虚拟机栈、本地方法栈随着线程而生,随着线程而灭亡,操作数栈中的栈帧随着方法的执行的有条不紊地进行着入栈和出栈的操作,每个栈帧中分配多少内存基本上在类结构确定下来时就已经确定了大小了,因此这几个区域的内存分配和回收都具备确定性,在这个几个区域中就不需要过多的考虑内存分配的问题了,因为随着线程的结束,内存在然而然地就已经被回收了。而java堆和方法区不一样,一个接口中多个实现类所需要的内存可能不一样,一个方法中多个分支需要的内存的大小也有可能是不同的,我们在程序的运行期才能确定哪些对象需要创建,创建所需内存是多大,这些内存都是动态分配的,垃圾回收器考虑的就是这部分的内存,接下来我们将围绕以下3个问题来展开描述java垃圾收集器是如何自动回收内存的:
1、哪些内存需要回收?
2、什么时候回收?
3、如何回收?
本片博客就围绕这第一个问题展开说明,其余连个问题将在后面的博客中一一详解;
首先,Java垃圾回收器的一个想要解决的问题是什么样的内存需要被回收呢?Java垃圾收集器认为,当一个对象再无被其它对象引用时可以认为这个对象可以回收了。那么垃圾收集器是怎么知道对象的死亡的还是存活的呢?目前,Java虚拟机有两种算法来确定哪些对象是无用的,需要被回收的。
1.引用计数法
该算法的思路是给每个对象都添加一个引用计算器,每当有其它对象引用时计数器就+1,当引用失效时计数器-1,任何时刻当该对象的引用数为0的时候,则判定这个对象不会再被使用了,可以将该对象回收了。这种算法实现起来很简单,效率也非常高,但是并没有被Java所采用,原因是这种算法很难解决对象相互引用的问题。看一下下面例子的代码:
public class Test { private Object instance = null;
private static final int _1MB = 1024 * 1024; /** 这个成员属性唯一的作用就是占用一点内存 */
private byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args)
{
Test A= new Test();
Test B = new Test();
A.instance = B;
B.instance = A;
A = null;
B = null; System.gc();
}
}
运行结果:
[GC (System.gc()) 9299K->720K(249344K), 0.0010947 secs]
[Full GC (System.gc()) 720K->627K(249344K), 0.0075042 secs]
上面的例子中A和B相互引用,它们的引用计数并不为0,但是执行System.gc()方法后仍然被回收了,这说明了Java垃圾收集器并不是使用引用计数法的。
2.可达性分析算法
可达性分析算法就是Java垃圾收集器判断‘垃圾’对象的算法。基本思路是通过一系列的”GC Roots“ 的对象作为起点,从这些节点开始向下搜索,搜索所走过的路程叫做引用链,当一个对象没有任何引用链与”GC Roots“有链接时,那么可以判定这个对象是无用的对象。可以作为GC Roots对象的包括一下几种:
1)Java虚拟机栈中局部变量表引用的对象;
2)本地方法栈中JNI所引用的对象;
3)方法区中的静态变量;
4)方法区中常量引用的对象;
下图为GC Roots:
图中obj1 ~ obj7都能够直接或间接地与GC Roots有连接,因此他们是存活对象,不会被GC回收。obj8基本上可以被回收了,obj9和obj10虽然有相互引用,但是他们的引用链中并没有达到GC Roots,因此也会判定为非存活对象。
3.引用的四种状态
在jdk1.2之前,Java的引用类型是比较简单的,只有被引用和未引用两种状态,类型过于简单的话不利于垃圾收集器的回收,当已经将未被引用的对象都被回收之后内存仍然比较紧张时,垃圾收集器将无法确认被引用的对象是否需要回收,哪些对象可以被回收等。jdk1.2后Java对引用类型进行的拓展,包括:强引用、软引用、弱引用和虚引用四种情况,四者的引用强度依次递减。这样在虚拟机中内存使用不同的情况下,分别回收不容引用类型的对象。
1)强引用
在程序中我们直接new出来的对象的引用都属于强引用,比如:Object obj = new Object();只要强引用还在,就不会被GC回收。
2)软引用
描述部分部分有用但非必须的对象。在系统将要发生内存溢出的时候,虚拟机会尝试从这部分的引用类型中进行二次回收,如果二次回收后的内存仍不够用才会抛出内存溢出异常。Java中的类SoftReference表示软引用。
3)弱引用
描述被必须对象。被弱引用关联的对象只能生存到下一次回收之前,在垃圾收集器工作之后,无论内存是否够用,这类的对象都会被回收掉。Java中的类WeakReference表示弱引用。
4)虚引用
被虚引用关联的对象在被回收时会收到系统的通知。被虚引用关联的对象,和其生存时间完全没关系。Java中的类PhantomReference表示虚引用。
对于可达性分析算法,对于那些没有与GC Roots关联的对象并非是立即回收的,而是经历过两次标记后仍然没有与GC Roots关联上,此时才会回收该对象。对象在进行可达性分析后如果没有与GC Roots关联,则会进行第一次标记和第一次筛选,筛选条件为是否有必要执行finalize方法,比如如果finalize方法是否被覆盖,或是否已被执行一次。如果没有被覆盖或已被执行了,基本可以确认该对象会被回收了。否则,将这个对象放到F-Queue队列中。对F-Queue队列二次标记,如果在这次标记中对象成功关联上GC Roots,则该对象拯救了自己,将从”即将回收“的集合中移除,否则将会被回收。然而,对象只能拯救自己一次,第二次就会被回收了。
迎大家关注公众号: 【java解忧杂货铺】,里面会不定时发布一些技术干货博客;关注即可免费领取大量最新,最流行的技术教学视频:
深入理解java虚拟机之垃圾收集器的更多相关文章
- 深入理解Java虚拟机:垃圾收集器与内存分配策略
目录 3.2 对象已死吗 判断一个对象是否可被回收 引用类型 finalize() 回收方法区 3.3. 垃圾收集算法 1.Mark-Sweep(标记-清除)算法 2.Copying(复制)算法 3. ...
- 《深入理解Java虚拟机》垃圾收集器
说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态 ...
- 《深入理解Java虚拟机》——垃圾收集器与内存分配策略
GC需要完成: 哪些内存需要回收 什么时候回收 如何回收 如何确定对象不再使用 引用计数算法 给对象添加一个引用计数器,当有一个地方引用它时,计数器值进行加1操作:当引用失效时,计数器值进行减1操作: ...
- 深入理解Java虚拟机笔记——垃圾收集器与内存分配策略
目录 判断对象是否死亡 引用计数器算法 可达性分析算法 各种引用 回收方法区 垃圾收集算法 标记-清除算法 复制算法 标记-整理算法 分代收集算法 HotSpot算法实现 枚举根节点 GC停顿(Sto ...
- 深入理解java虚拟机(3)垃圾收集器与内存分配策略
一.根搜索算法: (1)定义:通过一系列名为"GC Roots"的对象作为起点,从这些起点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时 ...
- 深入理解JVM虚拟机-2垃圾收集器
这里讨论的收集器基于JDK 1.7 Update 14之后的HotSpot虚拟机. 如果两个收集器之间存在连线,说明可以搭配使用.虚拟机所处的区域,则表示它是属于新生代收集器还是年老代收集器.在这里我 ...
- Java虚拟机学习 - 垃圾收集器
HotSpot JVM收集器 上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器.如果两个收集器之间存在连线,就说明它们可以搭配使用. Serial(串行GC)收集器 Serial收集 ...
- Java虚拟机学习 - 垃圾收集器 (4)
HotSpot JVM收集器 上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器.如果两个收集器之间存在连线,就说明它们可以搭配使用. Serial(串行GC)收集器 Serial收集 ...
- java虚拟机(六)--垃圾收集器和内存分配策略
目前没有完美的收集器,不同的厂商.版本的虚拟机提供的垃圾收集器会有很大的差别,用户根据自己应用特点和要求组合出各个年代所使用 的收集器.基于jdk1.7Update14之后的虚拟机. HotSpot的 ...
随机推荐
- SpringMvc+AngularJS通过CORS实现跨域方案
什么是跨域请求问题? 这个问题的起因在于现代浏览器默认都会基于安全原因而阻止跨域的ajax请求,这是现代浏览器中必备的功能,但是往往给开发带来不便. 但跨域的需求却一直都在,为了跨域,勤劳勇敢的程序猿 ...
- Linux 下常用的Shell 命令
英文原文链接:https://www.lopezferrando.com/30-interesting-shell-commands/ 1. 监控命令(每2秒运行一次) watch "ls ...
- json.parseArray源码解析
json.parseArray源码解析 public static <T> List<T> parseArray(String text, Class<T> cla ...
- 第三方支付设计——账户体系
第三方支付架构设计之-帐户体系 一, 什么是第三方支付? 什么是第三方支付?相信很多人对这个名字很熟悉,不管是从各种媒体等都经常听到,可以说是耳熟能熟.但,如果非得给这个名词 ...
- 卸载重装Mysql
卸载重装前请备份数据库 卸载 sudo apt autoremove --purge mysql-server-core-5.7 清理残留 sudo rm -r /var/lib/mysql* sud ...
- .NET开发设计模式-模板模式
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 【转】微信小游戏接入Fundebug监控
在SegmentFault上看到Fundebug上线小游戏监控,刚好最近开始玩微信小游戏,于是尝试接入试了一下. 接入方法 创建项目的时候选择左下角的微信小游戏图标. 点击继续进入接入插件页面. 第三 ...
- mysql中enum类型理解
ENUM是枚举类型,它虽然只能保存一个值,却能够处理多达65535个预定义的值.下面是我写的一个mysql语句 CREATE TABLE student( id INT(11) PRIMARY key ...
- SpringMVC中的文件上传
1. 配置图片服务器 一般图片会单独保存在图片服务器上, 本文为简化处理, 在Tomcat中配置一个路劲用于专门存放图片 在tomcat上配置图片虚拟目录,在tomcat下conf/server.xm ...
- 网络编程之非阻塞connect编写
一.connect非阻塞编写 TCP连接的建立涉及到一个三次握手的过程,且socket中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回, 这意味着每 个connect函数总 ...