java虚拟机入门(四)-垃圾回收的故事
谈到垃圾回收器,java程序员骄傲了起来,c语言你是够快,但是你有管家帮你打扫吗,还不是得靠自己的一双手,有钱就是任性。既然如此令java程序员骄傲的垃圾回收器,怎能让人不想去一探究竟呢!
垃圾回收器字面意思就是回收垃圾的,那么对于程序来说,什么事垃圾呢,怎样定位垃圾呢,我们来看一下:
一、什么是垃圾
垃圾简单来说,就是废弃的东西,我不需要了,他就是垃圾,记得之前看言情剧总有一句台词,就算是被全世界嫌弃,但是在我这里依然是宝贝,同理,如何定义垃圾,众说纷纭,对于程序来说,最常见的垃圾回收器主要还是:引用计数法,可达性分析。
1.引用计数法:
引用计数法就是对每个对象的引用进行计数,有一个引用指向它就加1,当计数为0表示没有引用,这就是个垃圾。
2.可达性分析
可达性分析理解起来稍微复杂一点,核心就是根(GC ROOT)可达,从根节点出发,向下发散搜索,所有能引用到的对象才是宝贝,其他的都是垃圾。那么问题来了,根节点到底是啥:
可以作为根节点的对象:
1. 虚拟机栈中(本地变量表)引用到的对象,如各个线程堆栈使用的参数,局部变量,临时变量等,其实就是当前还被线程持有的变量,
2. 方法区中类静态属性引用到的对象如java类的静态变量
3. 方法区中常量对象的引用,如字符串常量池的引用
4.本地方法栈中引用的对象,即我们的native方法。
5.java内部引用,如class对象、异常对象(NullPointException,OutofMemoryError等),系统类加载器
6.同步锁synchronized持有的对象
7.JVM 内部的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
8.JVM 实现中的“临时性”对象,跨代引用的对象
说实话我也不怎么记这个东西,我觉得要我背的东西都不是好东西,所以我理解的话,主要还是理解前面几个如虚拟机栈中局部变量表持有对象,那么局部变量表里面有的对象又是啥呢,线程在使用的对象,那么自然是不能被回收的,静态变量属于类级别的,这种对象是需要可以直接被使用的,自然也不能被回收,常量我理解和静态变量一样,native方法那不是我们能管的,自然也不能被回收,其实理解了也就是线程在用的不能回收,随时要被用的不能回收,native不归你管,你凭啥回收呢,不用背,自然能记得。
看了上述两种方法,让你选你会选哪个,你可能会选择第一个,毕竟简单,但是咱们再来看一下,第一种垃圾分类的方法,如果我有俩对象,虽然都不用了,但是你拉着我,我拉着你,自然是不能被回收的,但是其实呢,已经不用了,一个固然无所谓,但是程序在运行是,可能会出现无数个这样的状况,而且这种对象你很可能永远都无法回收,越积越多,多恐怖的一件事,所以你必然是需要其他方式来解决这种问题,这么一想好像也没那么简单,但是第二种虽然听起来麻烦一点,但是确实不会出现这样的问题,也不需要任何辅助。好了如果是你,现在你会选哪个呢。
二、垃圾回收基础
1.丐帮秘诀基础篇 --- 什么是GC
GC全称Garbage Collection,翻译过来就是垃圾回收,在java中其实就是自动化的垃圾回收机制
我们一般关注的GC主要是针对堆中进行回收,栈中由于是随线程自动回收,方法区的回收效率极低,因此不需要太多关注,毕竟还是回收对象嘛,对象在哪呢,自然是堆中。
2.丐帮秘决晋级篇 --- 垃圾分代
垃圾收集器一般分为新生代和老年代,新生代又分为Eden区和两个survior区,至于为什么分呢,首先我们要了解一下对象的GC分配策略:
1.栈上分配--- 判断当前对象可逃出当前方法,其实就是判断当前对象是否有可能在任何其他方法中有可能被修改,任意如作为入参传入其他方法,作用域不仅限于当前方法等都算是逃出当前方法(其实这就是大名鼎鼎的逃逸分析),如果确定不会逃出方法,则可直接在栈上分配,相信看过前面文章的同学应该知道栈比堆要快很多,直接栈上分配可以显著提高jvm效率。
2.对象优先在Eden区分配 --- 即对象会首先进入Eden区,如果Eden区分配满了,则会发起一次minorGc,为啥要这么做待会会细讲。
3. 大对象直接进入老年代 --- 啥叫大对象呢,比如一个大的list,一个占用很大内存的数组,其实就是一个对象个体,但是占用的内存很大。
4.长期存活对象进入老年代 --- 什么是长期存活对象呢,上节有说过,对象头里面有个记录GC年龄的,每次发生minorGc,年龄都会加一,hotsport默认是年龄达到15会进入老年代,当然,这个也是可以通过配置-XX:MaxTenuringThreshold 调整。
5.对象年龄动态判断 --- 如果在survior空间中,同意年龄对象总和大于survior空间的一半,年龄大于或等于这个年龄的对象可以直接进入老年代
6.空间分配担保 --- 网上的解释话太多了,我总结一下就是对象从新生代准备进入老年代的时候,如果设置了允许空间担保, 则继续检查新生代最大可用连续空间是否大于历代晋级到老年代对象的平均大小,如果大于,则直接进入老年代,担保失败则需进行fullGc,而没有设置分配担保如果新生代总大小大于老年代最大可用连续空间,则需先fullGc。可以看到区别就是一个是担保失败fullGc,而另一个则是直接fullGc(这里新生代大于老年代最大可用连续空间是前提)。
介绍完了对象分配策略,我们在回过头来看一下为啥分代要这么分,大家都知道对象是朝生夕死的,而分新生代和老年代就是要让一大部分对象在新生代就pass掉,而新生代又分为Eden和survior区,这样分完,很多对象在进行第一次minorGc时,就直接pass掉了,根本不需要进入survior区,这也是为啥那么对象要优先在Eden区分配,而大对象(可以设置参数-XX:PretenureSizeThreshold控制大小)直接进入老年代,如果当出现很多朝生夕死的大对象,那么由于没有新生代的过滤,老年代很快占满,就需要频繁FullGc,就会导致系统卡顿,这种确实没有什么好的办法解决,毕竟大对象新生代是装不了多少的,想想还是这么设计靠谱,大牛们果然不是盖的。
GC分配策略好像是有点多,背肯定是会忘记的,所以还是要靠理解,比如上面说的为啥要这么分代,正是因为这么解决是比较合适的,理解了这么设计的原因,其实这些策略看起来更像是一种解决方案。
三、垃圾回收算法
垃圾回收算法主流的主要有三种,复制回收,标记清除,标记整理,评价之前我们先了解一下吧:
1.复制回收
复制回收算法强调复制两个字,首先会将内存分为两块,一部分存储,一部分用来复制,因此只有一半内存可用,流程如下图首先会将可用内存复制到另一半,之后清掉之前那一半内存,供下次复制使用。复制算法的优点就是快,直接复制过来,原来的直接清掉,而且不会产生内存碎片。
2.标记-清除(Mark-Sweep)
标记清除算法分为两步,标记和清除,看起来就很麻烦,首先标记出所有需要回收的对象,之后清除回收掉之前标记的对象。标记清除相对于复制算法不需要额外的一份内存,但是会导致有内存碎片,如下图,可以发现很多断断续续的内存块,这种情况如果来一个比较大的对象,即便内存够用,但是没有足够大的连续内存,依然无法使用,此时就必须要再次触发垃圾回收。
3.标记整理(Mark-Compact)
标记整理算法在我看来更应该叫标记-整理-清除算法,首先标记出所有可回收对象,随后将所有不可回收对象向另一边移动,之后清除掉可以范围外的垃圾,可以看出步骤很复杂,耗时明显会比前两种要长,但是相比复制算法,它不需要额外的内存,相比标记清除算法,它不会产生垃圾碎片。
以上是常用的三种垃圾回收算法,复制算法明显的是用空间换时间,标记清除舍弃了部分空间内存连续性,而最后一种就是用时间换空间了。
记得很久之前面试的时候,面试官问过我一个问题,新生代使用的是什么算法,我记得自己看过,就回答了复制算法,说实话当时也是连猜带蒙的,紧接着就是为什么,这个问题给我干蒙了,当时脑子里的想法就是,我哪知道为什么,你问设计这个算法的人啊。但是最后还是卑微的回答了句,不知道。我知道这个面试已经结束了,确实是实力不够,回来之后就把jvm这块系统的重新学了遍,回炉重造。
还是回到问题,新生代为啥要用复制算法呢,有研究表明(确实是用大量数据得出的结果),98%以上的对象都是朝生夕死的,复制过去的对象只占用极少数,那么新生代为啥又要搞这么多分区,又是Eden又是survior,survior还整俩,我们梳理一下常规对象进入新生代的过程是怎样的:首先进入Eden区,随后Eden区满之后,会进行minorGc,可用对象会第一次进入survior区,而下一次survior区满之后,会再次进行minorGc,将对象从survior 1区复制到2区,再清掉1区,循环直到满足GC分配策略进入老年代。如果没有Eden区,则俩survior区都需要扩大很多,而复制算法必须要用俩同样大小的内存,因此俩survior区缺一不可,这时候可能有的小伙伴要说了,标记清除,标记整理不都可以吗,也不是不可以,但是它不够快,标记清除还会有残留内存碎片,标记整理效率太低,因此后两种更多地用于老年代的回收。
总结:
这一章主要还是介绍垃圾回收的概念,什么是垃圾,垃圾回收器是啥样的,最后垃圾回收器在回收的时候是怎么玩的,都是偏理论的知识,可能理解起来没那么容易(也是我没有写好),但是我相信看完也大概知道我们的垃圾回收器大概是个啥,其实我最想表达的不仅仅是垃圾回收器的分代是啥样的,我想写出的是为什么要这么设计,从对象朝生夕死的特点,我们也可以知道这种分代方式是比较合理的,又通过反证法,证明如果说少了Eden或者survior,都是有很多不必要的弊端,而在对象分配策略中,我们基本也可以通过对象的特点和分代模型,分析出这样设计的合理性,比如优先在Eden区分配是因为这样可以过滤掉绝大部分朝生夕死的对象,其实理解了这些,可能问你分配策略时你还是记不住哪些点,但是你对jvm调优这方面,绝对前进了了一大步。
java虚拟机入门(四)-垃圾回收的故事的更多相关文章
- 《深入理解 Java 虚拟机》学习 -- 垃圾回收算法
<深入理解 Java 虚拟机>学习 -- 垃圾回收算法 1. 说明 程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,这几个区域的内存分配和回收都具备确定性 Java 堆和方 ...
- 深入理解Java虚拟机之JVM垃圾回收随笔
1.对象已经死亡? 1.1引用计数法:给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用 的.但是它很难解决 ...
- Java虚拟机(JVM)与垃圾回收机制(GC)的详解
一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部分: 1.类加载器(ClassLoader):在JVM启动时或者在类运行时 ...
- 深入理解java虚拟机之——JVM垃圾回收策略总结
如何判断一个对象是否存活 引用计数算法:给对象中添加一个引用计数器,每当有引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用. Java虚拟机里面没有 ...
- 深入理解java虚拟机---读后笔记(垃圾回收)
运行时数据区,主要包括方法区.虚拟机栈.本地方法栈.堆.程序计数器,该部分内存都是线程隔离的. 然后和其交互的有执行引擎.本地库接口,此部分线程之间是可以共享的. 1. 引用计数算法 给对象添加一个引 ...
- 【转】Java虚拟机的JVM垃圾回收机制
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp43 1.JVM内存空间 JVM堆(Heap)= 新生代 ...
- Java基础教程:垃圾回收
Java基础教程:垃圾回收 垃圾回收 垃圾回收(Garbage Collection,GC),顾名思义是释放垃圾占用的空间,防止内存泄漏.有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使 ...
- JAVA虚拟机内存分配与回收机制
Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等 ...
- Java内存管理 -JVM 垃圾回收
版权声明:本文为博主原创文章,未经博主允许不得转载 一.概述 相比起C和C++的自己回收内存,JAVA要方便得多,因为JVM会为我们自动分配内存以及回收内存. 在之前的JVM 之内存管理 中,我们介绍 ...
随机推荐
- SpringBoot从入门到精通教程(一)
写在前面的话: 在很早之前,记笔记时候,我就一直在思考一个问题,我记笔记是为了什么,我一直想不明白 ,后面发现技术跟新迭代的速度实在太快了,笔记刚纪完,技术又跟新了,于是我想了想干脆边写博客,边记笔记 ...
- vs2012新特性
VS2012的六大技术特点: 1.VS2012和VS2010相比,最大的新特性莫过于对Windows 8Metro开发的支持.Metro天生为云端而生,简洁.数字化.内容优于形式.强调交互的设计已经成 ...
- 10天,从.Net转Java,并找到月薪2W的工作(三)
放弃Offer之后,压力一天比一天打 好点的公司,除了技术追根问底,还对你平时代码实践问的很多.比如问你,redis缓存一般设置多大.问你项目内容的细节,业务流程. 集合.锁.Jvm.多线程高并发.微 ...
- MybatisPlus学习(四)条件构造器Wrapper方法详解
文章目录 1.条件构造器 2.QueryWrapper 2.1.eq.ne 2.2.gt.ge.lt.le 2.3.between.notBetween 2.4.like.notLike.likeLe ...
- git使用上
因为最近工作上多处都用到了基于 Git 的开发,需要深入理解 Git 的工作原理,以往的 Git 基本知识已经满足不了需求了,因此写下这篇 Git 进阶的文章,主要是介绍了一些大家平时会碰到但是很少去 ...
- [leetcode]203. Remove Linked List Elements链表中删除节点
这道题很基础也很重要 重点就是设置超前节点 public ListNode removeElements(ListNode head, int val) { //超前节点 ListNode pre = ...
- [leetcode]725. Split Linked List in Parts链表分块
思路很简单 按时链表的题做起来很容易犯小错误,思维要缜密 还要多练习啊 做之前最好画算法框图 public ListNode[] splitListToParts(ListNode root, in ...
- SpringBoot 获取微信小程序openid
最近做一个项目用到小程序,为了简化用户啊登录,通过获取小程序用户的openid来唯一标示用户. 1.官方教程 2.具体步骤 3.具体实现 4.可能出现的错误 5.代码下载 1.官方教程 先来看看官方怎 ...
- 关于char是否能表示一个中文
char是可以表示中文的 这个问题点有3个考核点 1 char是多少位的 2 java用的是什么方式表示字符 3 Unicode是用多少位表示的 1的答案是16位的,2的答案是Unicode,3的答案 ...
- [学习笔记]尝试go-micro开发微服务<第一波>
平时项目都是基于c++,lua,node, 现在打算开始自学开发微服务; 也顺带磨砺下go和docker 前期准备 1. 有golang编程基础 本系列文章是基于有golang编程基础,有过实际开 ...