Java的性能优化,整理出一篇文章,供之后温故知新。算法
JVM GC(垃圾回收机制)缓存
在学习Java GC 以前,咱们须要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM由于须要执行GC而中止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,全部的线程都进入等待状态,直到GC任务完成。GC优化不少时候就是减小stop-the-world 的发生。性能优化
JVM GC回收哪一个区域内的垃圾?服务器
须要注意的是,JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出做用域后会被JVM自动释放掉,因此其不在JVM GC的管理范围内。数据结构
JVM GC怎么判断对象能够被回收了?多线程
· 对象没有引用并发
· 做用域发生未捕获异常性能
· 程序在做用域正常执行完毕学习
· 程序执行了System.exit()优化
· 程序发生意外终止(被杀线程等)
在Java程序中不能显式的分配和注销缓存,由于这些事情JVM都帮咱们作了,那就是GC。
有些时候咱们能够将相关的对象设置成null 来试图显示的清除缓存,可是并非设置为null 就会必定被标记为可回收,有可能会发生逃逸。
将对象设置成null 至少没有什么坏处,可是使用System.gc() 便不可取了,使用System.gc() 时候并非立刻执行GC操做,而是会等待一段时间,甚至不执行,并且System.gc() 若是被执行,会触发Full GC ,这很是影响性能。
JVM GC何时执行?
eden区空间不够存放新对象的时候,执行Minro GC。升到老年代的对象大于老年代剩余空间的时候执行Full GC,或者小于的时候被HandlePromotionFailure 参数强制Full GC 。调优主要是减小 Full GC 的触发次数,能够经过 NewRatio 控制新生代转老年代的比例,经过MaxTenuringThreshold 设置对象进入老年代的年龄阀值(后面会介绍到)。
按代的垃圾回收机制
新生代(Young generation):绝大多数最新被建立的对象都会被分配到这里,因为大部分在建立后很快变得不可达,不少对象被建立在新生代,而后“消失”。对象从这个区域“消失”的过程咱们称之为:Minor GC 。
老年代(Old generation):对象没有变得不可达,而且重新生代周期中存活了下来,会被拷贝到这里。其区域分配的空间要比新生代多。也正因为其相对大的空间,发生在老年代的GC次数要比新生代少得多。对象从老年代中消失的过程,称之为:Major GC 或者 Full GC。
持久代(Permanent generation)也称之为 方法区(Method area):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生GC。发生在这个区域的GC事件也被算为 Major GC 。只不过在这个区域发生GC的条件很是严苛,必须符合如下三种条件才会被回收:
一、全部实例被回收
二、加载该类的ClassLoader 被回收
三、Class 对象没法经过任何途径访问(包括反射)
可能咱们会有疑问:
若是老年代的对象须要引用新生代的对象,会发生什么呢?
为了解决这个问题,老年代中存在一个 card table ,它是一个512byte大小的块。全部老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只须要查询 card table 来决定是否能够被回收,而不用查询整个老年代。这个 card table 由一个write barrier 来管理。write barrier给GC带来了很大的性能提高,虽然由此可能带来一些开销,但彻底是值得的。
默认的新生代(Young generation)、老年代(Old generation)所占空间比例为 1 : 2 。
新生代空间的构成与逻辑
为了更好的理解GC,咱们来学习新生代的构成,它用来保存那些第一次被建立的对象,它被分红三个空间:
· 一个伊甸园空间(Eden)
· 两个幸存者空间(Fron Survivor、To Survivor)
默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1
每一个空间的执行顺序以下:
一、绝大多数刚刚被建立的对象会存放在伊甸园空间(Eden)。
二、在伊甸园空间执行第一次GC(Minor GC)以后,存活的对象被移动到其中一个幸存者空间(Survivor)。
三、此后,每次伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间。
四、当一个幸存者空间饱和,还在存活的对象会被移动到另外一个幸存者空间。而后会清空已经饱和的哪一个幸存者空间。
五、在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代。
从上面的步骤能够发现,两个幸存者空间,必须有一个是保持空的。若是两个两个幸存者空间都有数据,或两个空间都是空的,那必定是你的系统出现了某种错误。
咱们须要重点记住的是,对象在刚刚被建立以后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。
也有例外出现,对于一些比较大的对象(须要分配一块比较大的连续内存空间)则直接进入到老年代。通常在Survivor 空间不足的状况下发生。
老年代空间的构成与逻辑
老年代空间的构成其实很简单,它不像新生代空间那样划分为几个区域,它只有一个区域,里面存储的对象并不像新生代空间绝大部分都是朝闻道,夕死矣。这里的对象几乎都是从Survivor 空间中熬过来的,它们毫不会轻易的狗带。所以,Full GC(Major GC)发生的次数不会有Minor GC 那么频繁,而且作一次Major GC 的时间比Minor GC 要更长(约10倍)。
JVM GC 算法讲解
一、根搜索算法
根搜索算法是从离散数学中的图论引入的,程序把全部引用关系看做一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点。当全部的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
上图红色为无用的节点,能够被回收。
目前Java中能够做为GC ROOT的对象有:
一、虚拟机栈中引用的对象(本地变量表)
二、方法区中静态属性引用的对象
三、方法区中常亮引用的对象
四、本地方法栈中引用的对象(Native对象)
基本全部GC算法都引用根搜索算法这种概念。
二、标记 - 清除算法
标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象进行直接回收,如上图。
标记-清除算法不须要进行对象的移动,而且仅对不存活的对象进行处理,在存活的对象比较多的状况下极为高效,但因为标记-清除算法直接回收不存活的对象,并无对还存活的对象进行整理,所以会致使内存碎片。
三、复制算法
复制算法将内存划分为两个区间,使用此算法时,全部动态分配的对象都只能分配在其中一个区间(活动区间),而另一个区间(空间区间)则是空闲的。
复制算法采用从根集合扫描,将存活的对象复制到空闲区间,当扫描完毕活动区间后,会的将活动区间一次性所有回收。此时本来的空闲区间变成了活动区间。下次GC时候又会重复刚才的操做,以此循环。
复制算法在存活对象比较少的时候,极为高效,可是带来的成本是牺牲一半的内存空间用于进行对象的移动。因此复制算法的使用场景,必须是对象的存活率很是低才行,并且最重要的是,咱们须要克服50%内存的浪费。
四、标记 - 整理算法
标记-整理算法采用 标记-清除 算法同样的方式进行对象的标记、清除,但在回收不存活的对象占用的空间后,会将全部存活的对象往左端空闲空间移动,并更新对应的指针。标记-整理 算法是在标记-清除 算法之上,又进行了对象的移动排序整理,所以成本更高,但却解决了内存碎片的问题。
JVM为了优化内存的回收,使用了分代回收的方式,对于新生代内存的回收(Minor GC)主要采用复制算法。而对于老年代的回收(Major GC),大多采用标记-整理算法。
垃圾回收器简介
须要注意的是,每个回收器都存在Stop The World 的问题,只不过各个回收器在Stop The World 时间优化程度、算法的不一样,可根据自身需求选择适合的回收器。
一、Serial(-XX:+UseSerialGC)
从名字咱们能够看出,这是一个串行收集器。
Serial收集器是Java虚拟机中最基本、历史最悠久的收集器。在JDK1.3以前是Java虚拟机新生代收集器的惟一选择。目前也是ClientVM下ServerVM 4核4GB如下机器默认垃圾回收器。Serial收集器并非只能使用一个CPU进行收集,而是当JVM须要进行垃圾回收的时候,需暂停全部的用户线程,直到回收结束。
使用算法:复制算法
JVM中文名称为Java虚拟机,所以它像一台虚拟的电脑在工做,而其中的每个线程都被认为是JVM的一个处理器,所以图中的CPU0、CPU1实际上为用户的线程,而不是真正的机器CPU,不要误解哦。
Serial收集器虽然是最老的,可是它对于限定单个CPU的环境来讲,因为没有线程交互的开销,专心作垃圾收集,因此它在这种状况下是相对于其余收集器中最高效的。
二、SerialOld(-XX:+UseSerialGC)
SerialOld是Serial收集器的老年代收集器版本,它一样是一个单线程收集器,这个收集器目前主要用于Client模式下使用。若是在Server模式下,它主要还有两大用途:一个是在JDK1.5及以前的版本中与Parallel Scavenge收集器搭配使用,另一个就是做为CMS收集器的后备预案,若是CMS出现Concurrent Mode Failure,则SerialOld将做为后备收集器。
使用算法:标记 - 整理算法
运行示意图与上图一致。
三、ParNew(-XX:+UseParNewGC)
ParNew其实就是Serial收集器的多线程版本。除了Serial收集器外,只有它能与CMS收集器配合工做。
使用算法:复制算法
ParNew是许多运行在Server模式下的JVM首选的新生代收集器。可是在单CPU的状况下,它的效率远远低于Serial收集器,因此必定要注意使用场景。
四、ParallelScavenge(-XX:+UseParallelGC)
ParallelScavenge又被称为吞吐量优先收集器,和ParNew 收集器相似,是一个新生代收集器。
使用算法:复制算法
ParallelScavenge收集器的目标是达到一个可控件的吞吐量,所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。若是虚拟机总共运行了100分钟,其中垃圾收集花了1分钟,那么吞吐量就是99% 。
五、ParallelOld(-XX:+UseParallelOldGC)
ParallelOld是并行收集器,和SerialOld同样,ParallelOld是一个老年代收集器,是老年代吞吐量优先的一个收集器。这个收集器在JDK1.6以后才开始提供的,在此以前,ParallelScavenge只能选择SerialOld来做为其老年代的收集器,这严重拖累了ParallelScavenge总体的速度。而ParallelOld的出现后,“吞吐量优先”收集器才名副其实!
使用算法:标记 - 整理算法
在注重吞吐量与CPU数量大于1的状况下,均可以优先考虑ParallelScavenge + ParalleloOld收集器。
六、CMS (-XX:+UseConcMarkSweepGC)
CMS是一个老年代收集器,全称 Concurrent Low Pause Collector,是JDK1.4后期开始引用的新GC收集器,在JDK1.五、1.6中获得了进一步的改进。它是对于响应时间的重要性需求大于吞吐量要求的收集器。对于要求服务器响应速度高的状况下,使用CMS很是合适。
CMS的一大特色,就是用两次短暂的暂停来代替串行或并行标记整理算法时候的长暂停。
使用算法:标记 - 清理
CMS的执行过程以下:
· 初始标记(STW initial mark)
在这个阶段,须要虚拟机停顿正在执行的应用线程,官方的叫法STW(Stop Tow World)。这个过程从根对象扫描直接关联的对象,并做标记。这个过程会很快的完成。
· 并发标记(Concurrent marking)
这个阶段紧随初始标记阶段,在“初始标记”的基础上继续向下追溯标记。注意这里是并发标记,表示用户线程能够和GC线程一块儿并发执行,这个阶段不会暂停用户的线程哦。
· 并发预清理(Concurrent precleaning)
这个阶段任然是并发的,JVM查找正在执行“并发标记”阶段时候进入老年代的对象(可能这时会有对象重新生代晋升到老年代,或被分配到老年代)。经过从新扫描,减小在一个阶段“从新标记”的工做,由于下一阶段会STW。
· 从新标记(STW remark)
这个阶段会再次暂停正在执行的应用线程,从新重根对象开始查找并标记并发阶段遗漏的对象(在并发标记阶段结束后对象状态的更新致使),并处理对象关联。这一次耗时会比“初始标记”更长,而且这个阶段能够并行标记。
· 并发清理(Concurrent sweeping)
这个阶段是并发的,应用线程和GC清除线程能够一块儿并发执行。
· 并发重置(Concurrent reset)
这个阶段任然是并发的,重置CMS收集器的数据结构,等待下一次垃圾回收。
CMS的缺点:
一、内存碎片。因为使用了 标记-清理 算法,致使内存空间中会产生内存碎片。不过CMS收集器作了一些小的优化,就是把未分配的空间汇总成一个列表,当有JVM须要分配内存空间的时候,会搜索这个列表找到符合条件的空间来存储这个对象。可是内存碎片的问题依然存在,若是一个对象须要3块连续的空间来存储,由于内存碎片的缘由,寻找不到这样的空间,就会致使Full GC。
二、须要更多的CPU资源。因为使用了并发处理,不少状况下都是GC线程和应用线程并发执行的,这样就须要占用更多的CPU资源,也是牺牲了必定吞吐量的缘由。
三、须要更大的堆空间。由于CMS标记阶段应用程序的线程仍是执行的,那么就会有堆空间继续分配的问题,为了保障CMS在回收堆空间以前还有空间分配给新加入的对象,必须预留一部分空间。CMS默认在老年代空间使用68%时候启动垃圾回收。能够经过-XX:CMSinitiatingOccupancyFraction=n来设置这个阀值。
七、GarbageFirst(G1)
这是一个新的垃圾回收器,既能够回收新生代也能够回收老年代,SunHotSpot1.6u14以上EarlyAccess版本加入了这个回收器,Sun公司预期SunHotSpot1.7发布正式版本。经过从新划份内存区域,整合优化CMS,同时注重吞吐量和响应时间。杯具的是Oracle收购这个收集器以后将其用于商用收费版收集器。所以目前暂时没有发现哪一个公司使用它,这个放在以后再去研究吧。
整理一下新生代和老年代的收集器。
新生代收集器:
Serial (-XX:+UseSerialGC)
ParNew(-XX:+UseParNewGC)
ParallelScavenge(-XX:+UseParallelGC)
G1 收集器
老年代收集器:
SerialOld(-XX:+UseSerialOldGC)
ParallelOld(-XX:+UseParallelOldGC)
CMS(-XX:+UseConcMarkSweepGC)
G1 收集器
目前了解的GC收集器就是这么多了,若是有哪位有缘的朋友看到了这篇文章,刚好有更好的GC收集器推荐,欢迎留言交流。
原帖地址:https://zhuanlan.zhihu.com/p/25539690