知识总结--性能优化总结(摘录+转载)

目录

1.硬件选型

2.操作系统

3.应用程序

1、项目自身的优化

java 代码优化

2、数据库优化

mysql调优

对于索引的优化策略

对于sql语句优化策略

3、架构上的优化

4、redis优化及使用注意

5、jvm调优总结

如何做jvm调优

jvm调优流程

性能定义

调优原则

性能调优流程

确定内存占用

如何才算稳定阶段?

系统延迟需求

优化新生代的大小

优化老年代的大小

对象提升率

吞吐量调优

最后


讲到性能调优就需要了解我们调优的是什么。计算机由硬件、操作系统和应用程序组成,在性能调优时无外乎对这几块进行调优。硬件:无外乎内存、磁盘、cpu、网卡等,操作系统:无外乎进程、虚拟内存、文件系统、网络等。应用程序就是作为开发需要着重注意的地方,比如:mysql、nginx、redis、包括本身应用程序的jvm、代码、sql脚本等等。性能调优就是对硬件、操作系统和应用程序有相当深刻的理解,调节三者之间的关系从而使整个系统达到性能最优,并能不断地满足业务需求变更。优化步骤无外乎以下几个步骤:

1.硬件选型

不管你是租服务器也好还是自己买服务器也好都要遇到一个问题,我们选择什么样硬件配置的服务器。

一般我们是根据应用类型来选择服务器,因为你不可能一种硬件配置来满足所有的应用需求,因为每个应用的具体需求不一样。下面我们来看一下在项目实施中有哪些应用类型:

  • 负载均衡:性能要求相对较低,因为只负责转发数据,但要保证选一性能突出的网卡即可。(推荐配置:CPU E5620 x 1 内存 8G 硬盘 500G(RAID5))
  • Web 服务器:一般只处理一些静态页面或者图片等,因此要求也不是很高,主流的服务器都可以。(推荐配置:CPU E5620 x 1 内存 16G 硬盘 500G(RAID5))
  • 应用服务器:一般应用程序服器,他承担网站功能的实现,在架构中占有比较重的位置,特别是网站架构中只有一台应用服务器,对CPU、内存、磁盘要求都比较高。(推荐配置:CPU E5620 x 2 内存 32G 硬盘 500G(RAID10))
  • 缓存服务器:分为前端页面缓存与后面数据缓存,他们典型的应用分别是Varnish与Memcached,对内存的要求比较大,一般我们配置服务器时使用较大有内存。(推荐配置:CPU E5620 x 1 内存 32G 硬盘 500G(RAID10))
  • 数据库服务器:数据服务器对CPU、内存、磁盘的要求都很高,一但某个硬件是短板都会带来性能问题。(推荐配置:CPU E5620 x 2 内存 64G 固态硬盘 500G(RAID10))
  • 备份服务器:备份服务器一般就没有什么要求,但有点可以肯定是必须有足够大的硬盘空间。(推荐配置:CPU E5620 x 1 内存 4G 硬盘 2TB(RAID5))
  • 监控服务器:一般也没什么需要,普通的PC服务器就可以。(推荐配置:CPU E5620 x 1 内存 4G 硬盘 500(RAID5))
  • 其它服务器:至于其它服务器就看具体需要具体分析了。

性能调优就是根据你具体的应用,进行具体分析特别是像MySQL这样的服务器,对CPU、内存、磁盘要求都比较高。

所以,对硬件的性能调优我们必须做到选择合适的硬件配置。这是网站架架构或者项目实施首先要解决的问题!

硬件优化一般也包括两块:

  • 上线前(硬件选型)
  • 上线后(硬件扩展)

一般项目搭建时都需要根据具体的应用进行硬件配置选型,在这方面需要一定的运维经验刚接触的朋友可以在这方面有点欠缺,但没事一般做过一两个项目以后,对硬件配置选型也就会了,嘿嘿。

但有个不成文的经验,硬件配置还是越高越好(别说是我说的)。

我们为什么说需要根据具体的应用来选型呢,一方是什么样的应用需要什么样的硬件配置,还有点很重要就是节约成本,钱得要在刀刃上不该花的钱我们不能乱花,也是为公司节约成本,实现资源利用最大化。

上面我们说的是项目搭建初期,你运气比较好项目一开始你就在这边。

一般有经验的运维工程师在硬件选型是不会有问题的,所以我们在性能优化时就不考虑硬件这块,从理论上讲我们服务器硬件配置一般不会出现在这种性能问题上。

但是呢,由于我们业务做的越来越好,项目创建初期没有考虑到会有这么大的性能需要(访问量),现在有的硬件不能满足业务需求,所我们这时需要更换更好的CPU、更大的内存和更快的磁盘。

2.操作系统

有本书叫《Linux Performance Tuning》(Linux 性能调优)这本书是老外写的,作者是 Fernando Apesteguia 。

为什么我们需要性能调优?他得出的结论是这样的:

“当一个发行版打包发送到客户手中的时候,它是为了完全兼容市场中大部分计算机而设计的。这是一个相当混杂的硬件集合(硬盘,显卡,网卡,等等)。

所以Red Hat,SUSE,Mandriva,Ubuntu和其他的一些发行版厂商选择了一些保守的设置来确保安装成功。”

简单说,你的操作系统已经运行的不错了,但是你可以调节它获得更高的性能,比如你有个高性能的磁盘,但你的操作系统中一些选项参数默认没有启动,就不能实现这些高级功能来提高硬盘性能。

还有我想说就是对操作系统发行版选择的问题,RedHat或CentOS这些操作系统在项目实施或网站架构中用的比较多,主要针对企业应用而开发的操作系统。

而Ubuntu之类的操作系统对桌面支持的比较好,所以选择发行版本时得注意。(一般企业中用的比较多的是CentOS)再有就是我们一般不要选择最新的发行版,因为刚出来的发行版相对来说bug还比较多,不要先当“小白鼠”了,

比如:刚刚出来CentOS 7 等过一段时间稳定了再使用,目前我们可以选择 CentOS 6.4 或 6.5即可。

(但新版本也有很多好处,新版本中加入了很多新功能,去掉习已知bug,对于一些不重要的应用,可尝试使用新的操作系统)。

其实绝大部分的优化都在操作系统和应用程序的优化,除了上线前的硬件选型和上线后的硬件扩展,下面我们就来看看操作系统优化包括哪些:

  • 操作系统安装优化
  • 系统初始化
  • 进程调优
  • 内存调优
  • IO 调优
  • 文件系统调优
  • 网络调化

3.应用程序

最后,得来说说应用程序了。对于以上两个地方的优化完全可以找一个专业的运维或者是网络架构师来处理,然而对于应用程序的优化需要开发手把手来优化。应用程序优化一般是对项目用到的工具以及项目自身进行优化。

1、项目自身的优化

java 代码优化

代码优化最重要的作用应该是避免未知的错误,因此在写代码的时候,从源头开始注意各种细节,权衡并使用最优的选择,将会很大程度上避免出现未知的错误,从长远看也极大的降低了工作量。所以说代码优化的目标是减小代码体积、提高代码运行效率。

(1)尽量指定类方法的final修饰符

带有 final 修饰符的类是不可派生的。在 Java 核心 API 中,有许多应用 final 的例子,例如 java.lang.String,整个类都是 final 的。为类指定 final 修饰符可以让类不可以被继承,为方法指定 final 修饰符可以让方法不可以被重写。如果指定了一个类为 final,则该类所有的方法都是 final 的。Java 编译器会寻找机会内联所有的 final 方法,内联对于提升 Java 运行效率作用重大。

(2)尽量重用对象

特别是 String 对象的使用,出现字符串连接时应该使用 StringBuilder/StringBuffer 代替。由于 Java 虚拟机不仅要花时间生成对象,以后还需要花时间对这些对象进行垃圾回收和处理,因此生成过多的对象将会给程序的性能带来很大的影响。

(3)适时的创建使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。而且在方法中创建的变量不会有线程安全问题。但是物极必反,过于频繁的创建局部变量可能会造成youngGc回收过于频繁,并且会导致年轻代的占用极高而老年代却几乎没有占用这种尴尬境地,总的来说对于无需变更的数据最好使用全局变量,而对于实时变动的则必须要使用局部变量。

(4)及时关闭流

Java 编程过程中,进行数据库连接、I/O 流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。当然目前数据库连接由spring为我们做了很好的管理,但是IO的操作仍然需要我们自己手动关闭。

(5)在用下标循环集合或数组时要先获取长度再循环

因为在用下标循环时意味着每一个获取集合值的动作都创建了一个长度局部变量,当数组或集合长度极大,消耗的内存还是很可观的。

(6)尽量使用懒加载策略,及在需要的时候才创建对象

(7)慎用异常,最好写一个工具类统一管理所有异常对象

异常对性能不利,抛出异常首先要创建一个新的对象,Throwable 接口的构造函数调用名为 fillInStackTrace() 的本地同步方法,fillInStackTrace() 方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java 虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

(8)如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

比如 ArrayList、StringBuilder、StringBuffer、HashMap、HashSet 等,以 StringBuilder 为例,StringBuilder() 构造方法默认分配 16 个字符的空间,StringBuilder(int size) 构造方法默认分配 size 个字符的空间,StringBuilder(String str) 构造方法默认分配 16 个字符加 str.length() 个字符空间,所以可以通过类的构造方法来设定它的初始化容量,这样可以明显地提升性能。

(9)当需要较大数据复制操作时,使用system.arraycopy()

(10)乘除法使用移位操作

用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,但是移位操作虽然快,可能会使代码不太好理解,因此最好加上相应的注释。

(11)循环内不要多次创建对象的引用

(12)基于效率和类型检查的考虑,应该尽可能使用 array,无法确定数组大小时才使用 ArrayList

(13)尽量使用 HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用 Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销

(14)不要将数组声明为 public static final

因为这毫无意义,这样只是定义了引用为 static final,数组的内容还是可以随意改变的,将数组声明为 public 更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

(15)在合适的场合使用单例

使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

控制资源的使用,通过线程同步来控制资源的并发访问;

控制实例的产生,以达到节约资源的目的;

控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信;

(16)不要随意使用静态变量

因为当某个对象被定义为 static 的变量所引用,那么 gc 通常是不会回收这个对象所占有的堆内存的。

(17)及时清理不再使用的会话

为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为 30 分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用 HttpSession 的 invalidate() 方法清除会话。

(18)实现RandomAccess接口的集合,应当使用最普遍的for循环,而不是foreach

这是 JDK 推荐给用户的,JDK API 对于 RandomAccess 接口的解释是实现 RandomAccess 接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现 RandomAccess 接口的类实例,假如是随机访问的,使用普通 for 循环效率将高于使用 foreach 循环,反过来,如果是顺序访问的,则使用 Iterator 会效率更高。

(19)尽量使用同步代码块控制同步范围,避免使用同步方法

(20)常量要声明为static final,并用大写字母命名

这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量。

(21)及时清理不再使用的对象和导入的类

(22)程序运行过程中避免使用反射

不建议在程序运行过程中使用,除非万不得已,尤其是频繁使用反射机制,特别是 Method 的 invoke 方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存,用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。

(23)当需要使用线程时要使用线程池,当使用数据库时要是有连接池

这两个池都是用于重用对象的,前者可以避免频繁地创建和销毁线程,后者可以避免频繁地打开和关闭连接。

(24)要使用带缓冲的IO操作流

带缓冲的输入输出流,即 BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升 IO 效率。

(25)顺序插入和随机访问比较多的场景使用 ArrayList,元素删除和中间插入比较多的场景使用 LinkedList

(26)public方法不要有太多的形参

这违反了面向对象的思想,当形参超过三个时应当考虑用对象封装,这样更易于维护和阅读。

(27)字符串变量和字符串常量 equals 的时候将字符串常量写在前面,这样可以避免空指针

(28)建议使用 if (i == 1) 而不是 if (1 == i) 的方式,前一种写法在java中并不会报错,而且前一种写法更符合阅读习惯。

(29)数组不要使用toString,这样打出的是对象信息而不是对象内容,集合可以使用是因为集合的父类 AbstractCollections重写了 Object 的 toString() 方法

(30)不要对基本数据类型做向下转型

(31)公共集合类不使用的数据一定要remove掉

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

(32)把一个基本数据类型转为字符串,基本数据类型.toString() 是最快的方式、String.valueOf(数据) 次之、数据+"" 最慢

因为 String.valueOf() 方法底层调用了 .toString() 方法,但是会在调用前做空判断;.toString() 是直接调用;i + "" 底层使用了 StringBuilder 实现,先用 append 方法拼接,再用 toString() 方法获取字符串ringBuilder 实现,先用 append 方法拼接,再用 toString() 方法获取字符串。

(33)使用最有效的方式遍历map

遍历 Map 的方式有很多,通常场景下我们需要的是遍历 Map 中的 Key 和 Value,那么推荐使用的、效率最高的方式是 entrySet(),如果只是想遍历一下这个 Map 的 key 值则 keySet() 会比较合适一些。

(34)对资源的 close() 建议分开操作

虽然有些麻烦,却能避免资源泄露,这其实和 try-catch 机制相关,各自分开 close 各自的 try-catch 就会互不影响,防止写在一个 try-catch 中因为一个异常了后面的释放不了。

(35)对于 ThreadLocal 在线程池场景使用后一定要先 remove

因为线程池技术做的是一个线程重用,这意味着代码运行过程中一条线程使用完毕并不会被销毁而是等待下一次的使用,而 Thread 类中持有 ThreadLocal.ThreadLocalMap 的引用,线程不销毁意味着上条线程 set 的 ThreadLocal.ThreadLocalMap 中的数据依然存在,那么在下一条线程重用这个 Thread 的时候很可能 get 到的是上条线程 set 的数据而不是自己想要的内容。这个问题非常隐晦,一旦出现这个原因导致的错误,没有相关经验或者没有扎实的基础非常难发现这个问题,因此在写代码的时候就要注意这一点,这将给你后续减少很多的工作量。

(36)j尽量以常量定义的方式替代魔鬼数字,魔鬼数字的存在将极大地降低代码可读性,字符串常量是否使用常量定义可以视情况而定

(37)long 或者 Long 初始赋值时使用大写的 L 而不是小写的 l,因为字母 l 极易与数字 1 混淆,这个点非常细节,值得注意

(38)所有重写的方法必须保留 @Override 注解

(39)推荐使用 JDK7 中新引入的 Objects 工具类来进行对象的 equals 比较,直接 a.equals(b) 有空指针异常的风险

(40)循环体内不要使用 "+" 进行字符串拼接,要使用 StringBuilder 不断 append

因为每次虚拟机碰到 "+" 这个操作符对字符串进行拼接的时候会 new 出一个 StringBuilder,然后调用 append 方法,最后调用 toString() 方法转换字符串赋值给对象,所以循环多少次,就会 new 出多少个 StringBuilder() 来,这对于内存是一种浪费。

(41)不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类

异常处理效率低,RuntimeException 的运行时异常中绝大多数完全可以由程序员来规避,比如 ArithmeticException 可以通过判断除数是否为空来规避,NullPointerException 可以通过判断对象是否为空来规避,IndexOutOfBoundsException 可以通过判断数组/字符串长度来规避,ClassCastException 可以通过 instanceof 关键字来规避,ConcurrentModificationException 可以使用迭代器来规避。

(42)静态类、单例类、工厂类将它们的构造函数置为 private

这是因为静态类、单例类、工厂类这种类本来我们就不需要外部将它们 new 出来,将构造函数置为 private 之后,保证了这些类不会产生实例对象。

2、数据库优化

首先对于数据库的硬件和操作系统的优化,就是上面的那些,对于其他的诸如sql语句等不同的数据库优化略有不同,对于mysql而言,由于mysql的优化器不够成熟,因此在写mysql的sql语句时,如果涉及到多表关联查询则必须要加上join,加上join与不加join会有极大的差别。而对于收费的数据库而言,他们的优化器都有极好的优化效果,因此总体来说需要优化的点要比mysql少的多。

mysql调优

(1)选取最适用的字段属性

MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。例如,在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,我们应该使用MEDIUMINT而不是BIGIN来定义整型字段。

另外一个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。

(2)使用连接(join)代替子查询

MySQL从4.1开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询,如下所示:

DELETE FROM customerinfo

WHERE CustomerID NOT IN (SELECT CustomerID FROM salesinfo)

使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)..替代。例如,假设我们要将所有没有订单记录的用户取出来,可以用下面这个查询完成:

SELECT * FROM customerinfo

WHERE CustomerID NOT IN (SELECTC ustomerID FROM salesinfo)

如果使用连接(JOIN)..来完成这个查询工作,速度将会快很多。尤其是当salesinfo表中对CustomerID建有索引的话,性能将会更好,查询如下:

SELECT * FROM customerinfo

LEFT JOIN salesinfo ON customerinfo.CustomerID=salesinfo.CustomerID

WHERE salesinfo.CustomerID ISNULL

连接(JOIN)..之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

(3)使用union代替手动创建的临时表

MySQL从4.0的版本开始支持union查询,它可以把需要使用临时表的两条或更多的select查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要想同。下面的例子就演示了一个使用UNION的查询。

SELECT Name,Phone FROM client UNION

SELECT Name,BirthDate FROM author UNION

SELECT Name,Supplier FROM product

(4)灵活使用事务

尽管我们可以使用子查询(Sub-Queries)、连接(JOIN)和联合(UNION)来创建各种各样的查询,但不是所有的数据库操作都可以只用一条或少数几条SQL语句就可以完成的。更多的时候是需要用到一系列的语句来完成某种工作。但是在这种情况下,当这个语句块中的某一条语句运行出错的时候,整个语句块的操作就会变得不确定起来。设想一下,要把某个数据同时插入两个相关联的表中,可能会出现这样的情况:第一个表中成功更新后,数据库突然出现意外状况,造成第二个表中的操作没有完成,这样,就会造成数据的不完整,甚至会破坏数据库中的数据。要避免这种情况,就应该使用事务,它的作用是:要么语句块中每条语句都操作成功,要么都失败。换句话说,就是可以保持数据库中数据的一致性和完整性。事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以把数据库恢复到BEGIN开始之前的状态。

BEGIN; INSERT INTO salesinfo SET CustomerID=14; UPDATE inventory SET Quantity=11 WHERE item='book'; COMMIT;

事务的另一个重要作用是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方法来为用户提供一种安全的访问方式,这样可以保证用户的操作不被其它的用户所干扰。

(5)适时的使用锁

尽管事务是维护数据库完整性的一个非常好的方法,但却因为它的独占性,有时会影响数据库的性能,尤其是在很大的应用系统中。由于在事务执行的过程中,数据库将会被锁定,因此其它的用户请求只能暂时等待直到该事务结束。如果一个数据库系统只有少数几个用户来使用,事务造成的影响不会成为一个太大的问题;但假设有成千上万的用户同时访问一个数据库系统,例如访问一个电子商务网站,就会产生比较严重的响应延迟。

其实,有些情况下我们可以通过锁定表的方法来获得更好的性能。下面的例子就用锁定表的方法来完成前面一个例子中事务的功能。

LOCK TABLE inventory WRITE SELECT Quantity FROM inventory WHERE Item='book';

...

UPDATE inventory SET Quantity=11 WHERE Item='book'; UNLOCKTABLES

这里,我们用一个select语句取出初始数据,通过一些计算,用update语句将新值更新到表中。包含有WRITE关键字的LOCKTABLE语句可以保证在UNLOCKTABLES命令被执行之前,不会有其它的访问来对inventory进行插入、更新或者删除的操作。

(6)使用外键进行关联更新

锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键。

例如,外键可以保证每一条销售记录都指向某一个存在的客户。在这里,外键可以把customerinfo表中的CustomerID映射到salesinfo表中CustomerID,任何一条没有合法CustomerID的记录都不会被更新或插入到salesinfo中。

CREATE    TABLE    customerinfo( CustomerIDINT    NOT    NULL,PRIMARYKEY(CustomerID))TYPE=INNODB;

CREATE    TABLE    salesinfo( SalesIDNT    NOT    NULL,CustomerIDINT    NOT    NULL,

PRIMARYKEY(CustomerID,SalesID),

FOREIGNKEY(CustomerID)    REFERENCES    customerinfo(CustomerID)    ON    DELETE    CASCADE)TYPE=INNODB;

注意例子中的参数“ON DELETE CASCADE”。该参数保证当customerinfo表中的一条客户记录被删除的时候,salesinfo表中所有与该客户相关的记录也会被自动删除。如果要在MySQL中使用外键,一定要记住在创建表的时候将表的类型定义为事务安全表InnoDB类型。该类型不是MySQL表的默认类型。定义的方法是在CREATETABLE语句中加上TYPE=INNODB。如例中所示。

(7)正确的使用索引

索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显。

那该对哪些字段建立索引呢?

一般说来,索引应建立在那些将用于JOIN,WHERE判断和ORDERBY排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引。对于一个ENUM类型的字段来说,出现大量重复值是很有可能的情况

例如customerinfo中的“province”..字段,在这样的字段上建立索引将不会有什么帮助;相反,还有可能降低数据库的性能。我们在创建表的时候可以同时创建合适的索引,也可以使用ALTERTABLE或CREATEINDEX在以后创建索引。此外,MySQL从版本3.23.23开始支持全文索引和搜索。全文索引在MySQL中是一个FULLTEXT类型索引,但仅能用于MyISAM类型的表。对于一个大的数据库,将数据装载到一个没有FULLTEXT索引的表中,然后再使用ALTERTABLE或CREATEINDEX创建索引,将是非常快的。但如果将数据装载到一个已经有FULLTEXT索引的表中,执行过程将会非常慢。

(8)优化查询语句

绝大多数情况下,使用索引可以提高查询的速度,但如果SQL语句使用不恰当的话,索引将无法发挥它应有的作用。

下面是应该注意的几个方面。

  • 首先,最好是在相同类型的字段间进行比较的操作。

    在MySQL3.23版之前,这甚至是一个必须的条件。例如不能将一个建有索引的INT字段和BIGINT字段进行比较;但是作为特殊的情况,在CHAR类型的字段和VARCHAR类型字段的字段大小相同的时候,可以将它们进行比较。

  • 其次,在建有索引的字段上尽量不要使用函数进行操作。

例如,在一个DATE类型的字段上使用YEAE()函数时,将会使索引不能发挥应有的作用。所以,下面的两个查询虽然返回的结果一样,但后者要比前者快得多。

  • 第三,在搜索字符型字段时,我们有时会使用LIKE关键字和通配符,这种做法虽然简单,但却也是以牺牲系统性能为代价的。

例如下面的查询将会比较表中的每一条记录。


SELECT    *    FROM    books

WHERE    name    like"MySQL%"

但是如果换用下面的查询,返回的结果一样,但速度就要快上很多:


SELECT    *    FROM    books

WHERE    name>="MySQL"    andname    <"MySQM"

最后,应该注意避免在查询中让MySQL进行自动类型转换,因为转换过程也会使索引变得不起作用。

对于索引的优化策略

对于查询为主的应用来说,索引显得极其重要。

很多时候的性能问题就是由于没有在合适的列添加索引所致。如果不增加索引,那么进行一个很简单的查询都需要全表扫面,这样对性能损耗是极其严重的。

一般来讲在新建表时必须会新建一个唯一索引,其他需要频繁用到的查询字段一般会使用复合索引。

为什么要创建复合索引而不是单一索引?

因为当我们创建单一索引时,我们只是对这一列创建了索引,而我们创建复合索引时当复合字段作为条件查询的场景时,就相当于在两列上都创建了索引。由于索引结构是B+tree格式在使用复合索引遵循的是最左原则。比如对a、b、c三个字段建一个复核索引,那么它会产生a、ab、abc三个索引,当使用三个字段中某一个或多个字段进行查询时会根据最佳左前缀特性选择索引,当然如果条件格式是b、c、bc则不会使用索引,写sql语句时也要注意查询条件的顺序。

当选择列作为索引时要注意,索引是不会包含有NULL值的列,只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

在建串列索引时,如果可能应当指定一个前缀长度,例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

在进行排序查询时要注意mysql查询同时只会使用一个索引及当where条件使用了那个字段索引之后,order by的列则不会使用索引。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

在列上进行运算以及使用类似于%xxx或%xxx%这样的like时也不会使用索引。当使用in、not in 或 <>操作时也不会使用索引。not in 可以用not exists代替。

对于sql语句优化策略

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。


2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null
最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.

备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。

不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。


可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num = 0

3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or Name = 'admin'
可以这样查询:

select id from t where num = 10
union all
select id from t where Name = 'admin'

5.in 和 not in 也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3
很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b)
用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)
 

6.下面的查询也将导致全表扫描:

select id from t where name like ‘%abc%’
若要提高效率,可以考虑全文检索。

7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num = @num
可以改为强制查询使用索引:

select id from t with(index(索引名)) where num = @num
.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2 = 100
应改为:

select id from t where num = 100*2

9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3) = ’abc’       -–name以abc开头的id
select id from t where datediff(day,createdate,’2005-11-30′) = 0    -–‘2005-11-30’    --生成的id
应改为:

select id from t where name like 'abc%'
select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'

10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

12.不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)

13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。

14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。

15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。


16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽量避免大事务操作,提高系统并发能力。

30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

3、架构上的优化

当遇到大表(mysql千万数据就算大表了)时,应该考虑分表分区来优化,比如对于历史数据表在创建之初就应当根据时间围度进行分区。当发现一台mysql无法支撑时,则可以通过读写分离、分库来缓解数据库压力,具体的拆分围度则需要根据业务来判定。当数据库的可用性无法保障时则需要考虑主从复制或使用热备机器。另外也可以通过在应用层与数据库之间增加缓存来缓解数据库压力。

还有就是配置文件的优化,比如连接池大小、连接等待数量、最大连接数、最小连接数、排序时缓冲区大小等等,这些配置都要根据场景来确定,不是固定值。当部署数据库时要开启慢查询日志并定期的对慢查询sql进行排查优化。

4、redis优化及使用注意

redis是目前最常使用的缓存工具,它是一个很好的数据缓存区,可以极大的减少数据库压力。但在使用redis时有一些地方还是值得注意的。

(1)key键的创建

在redis中每一个值都由一个key键标识,这个key不是随意创建的,为了增加redis的可读性和可管理性,一般而言key键的格式为:业务名:表名:ID,并且创建key时要注意key键的长度,因为key键也是创建在缓存中的,key键太长对内存的占用也是不容忽视的。在创建key键不可以有像空格、换行、双引号等特殊字符,特殊字符不仅是会增加阅读困难而且可能会出现一些意想不到的错误,因为redis中有些地方是使用到特殊字符的,这样可能会于底层冲突从而造成一些奇怪的报错。

(2)value的创建

在创建value时有一点要极其注意,千万不要创建bigKey,因为redis是单线程数据库,当你对bigKey进行删除会更新操作时会造成查询bigKey的阻塞,当有较多访问bigKey的请求时会造成阻塞网络从而让整个redis不可用,继而造成宕机。String对应的value必须控制在100K以内,越小越好。hash、list、set、zset元素个数不要超过5000。在创建value时要根据场景选择合适的数据结构。当存储java对象时一定要将对象转化成json格式再进行存储,一来是json格式的数据可读性更强,第二点用json序列化之后的数据存取效率更高。注意创建任何一个键最好都设置过期时间,redis不是垃圾桶,如果不设置过期时间,那么对于非热点数据将长期进驻在缓存浪费内存,设置过期时间时要注意操作的原子性以免出现并发问题。当hash、list、set、zset数据过大时可以使用hash取模的方式将值落到不同的键上,以减小单个value的大小。

redis的事务性较弱,当场景对事务要求较高时不建议使用。

5、jvm调优总结

jvm调优一般而言都是在系统即将上线时候进行的,在进行jvm调优之前要确保系统的架构、数据库、代码、缓存、业务等优化完成,因为jvm调优对系统的总体性能并不会有非常大的提升,就相当于锦上添花一样,效果肯定没有前几个优化来的立竿见影。那么为什么我们还要进行jvm调优呢?

首先我们要明确,任何java写的应用程序都是运行在jvm上的,jvm相当于一个容器,如果jvm设置的内存太小肯定是会影响应用程序的执行的,甚至会让应用频繁报oom错误,继而频繁宕机,另外选择不合适的垃圾收集器会让jvm垃圾收集时间变长,让系统停顿时间无法达到预期效果大大影响用户体验。

如何做jvm调优

jvm优化是一个长期迭代循环往复的事情,甚至于项目成功部署后还需要在生产环境进行调优,我们在压力测试时只能给出一个当前环境最优配置方案,然后放到生产环境通过分析GC日志以及系统体验再对参数进行微调,继而才能达到效果。

jvm调优流程

调优的最终目的都是为了令应用程序使用最小的硬件消耗来承载更大的吞吐。

jvm的调优也不例外,jvm调优主要是针对垃圾收集器的收集性能优化,令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量。当然这里的最少是最优的选择,而不是越少越好。

性能定义

要查找和评估器性能瓶颈,首先要知道性能定义,对于jvm调优来说,我们需要知道以下三个定义属性,依作为评估基础:

吞吐量:重要指标之一,是指不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用达到的最高性能指标。

延迟:其度量标准是缩短由于垃圾啊收集引起的停顿时间或者完全消除因垃圾收集所引起的停顿,避免应用运行时发生抖动。

内存占用:垃圾收集器流畅运行所需要的内存数量。

这三个属性中,其中一个任何一个属性性能的提高,几乎都是以另外一个或者两个属性性能的损失作代价,不可兼得,具体某一个属性或者两个属性的性能对应用来说比较重要,要基于应用的业务需求来确定。

调优原则

在调优过程中,我们应该谨记以下3个原则,以便帮助我们更轻松的完成垃圾收集的调优,从而达到应用程序的性能要求。

1. MinorGC回收原则: 每次minor GC 都要尽可能多的收集垃圾对象。以减少应用程序发生Full GC的频率。

2. GC内存最大化原则:处理吞吐量和延迟问题时候,垃圾处理器能使用的内存越大,垃圾收集的效果越好,应用程序也会越来越流畅。

3. GC调优3选2原则: 在性能属性里面,吞吐量、延迟、内存占用,我们只能选择其中两个进行调优,不可三者兼得。

性能调优流程

知识总结--性能优化总结(摘录+转载)_第1张图片

以上就是对应用程序进行jvm调优的基本流程,我们可以看到,jvm调优是根据性能测试结果不断优化配置而多次迭代的过程。在达到每一个系统需求指标之前,之前的每个步骤都有可能经历多次迭代。有时候为了达到某一方面的指标,有可能需要对之前的参数进行多次调整,进而需要把之前的所有步骤重新测试一遍。

另外调优一般是从满足程序的内存使用需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,要基于这个步骤来不断优化,每一个步骤都是进行下一步的基础,不可逆行之。以下我们针对每个步骤进行详细的示例讲解。

在JVM的运行模式方面,我们直接选择server模式,这也是jdk1.6以后官方推荐的模式。

在垃圾收集器方面,我们直接采用了jdk1.6-1.8 中默认的parallel收集器(新生代采用parallelGC,老生代采用parallelOldGC)。

确定内存占用

在确定内存占用之前,我们需要知道两个知识点:应用程序的运行阶段jvm内存分配

运行阶段

应用程序的运行阶段,我可以划分为以下三个阶段:

1、初始化阶段 : jvm加载应用程序,初始化应用程序的主要模块和数据。

2、稳定阶段:应用在此时运行了大多数时间,经历过压力测试的之后,各项性能参数呈稳定状态。核心函数被执行,已经被jit编译预热过。

3、总结阶段:最后的总结阶段,进行一些基准测试,生成响应的策报告。这个阶段我们可以不关注。

确定内存占用以及活跃数据的大小,我们应该是在程序的稳定阶段来进行确定,而不是在项目起初阶段来进行确定,如何确定,我们先看以下jvm的内存分配。

jvm内存分配&参数

jvm堆中主要的空间,就是以上整个堆大小=新生代大小 + 老生代大小 + 永久代大小。 具体的对象提升方式,这里不再过多介绍了,我们看下一些jvm命令参数,对堆大小的指定。如果不采用以下参数进行指定的话,虚拟机会自动选择合适的值,同时也会基于系统的开销自动调整。

知识总结--性能优化总结(摘录+转载)_第2张图片

注意:在设置的时候,如果关注性能开销的话,应尽量把永久代的初始值与最大值设置为同一值,因为永久代的大小调整需要进行FullGC 才能实现。3计算活跃数据大小

计算活跃数据大小应该遵循以下流程:

知识总结--性能优化总结(摘录+转载)_第3张图片

如前所述,活跃数据应该是基于应用程序稳定阶段时,观察长期存活与对象在java堆中占用的空间大小。

计算活跃数据时应该确保以下条件发生:

1.测试时,启动参数采用jvm默认参数,不人为设置。

2.确保Full GC 发生时,应用程序正处于稳定阶段。

采用jvm默认参数启动,是为了观察应用程序在稳定阶段的所需要的内存使用。

如何才算稳定阶段?

一定得需要产生足够的压力,找到应用程序和生产环境高峰符合状态类似的负荷,在此之后达到峰值之后,保持一个稳定的状态,才算是一个稳定阶段。所以要达到稳定阶段,压力测试是必不可少的,具体如何如何对应用压力测试,本篇不过多说明,后期会有专门介绍的篇幅。

在确定了应用出于稳定阶段的时候,要注意观察应用的GC日志,特别是Full GC 日志。

GC日志指令: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:

GC日志是收集调优所需信息的最好途径,即便是在生产环境,也可以开启GC日志来定位问题,开启GC日志对性能的影响极小,却可以提供丰富数据。

必须得有FullGC 日志,如果没有的话,可以采用监控工具强制调用一次,或者采用以下命令,亦可以触发

jmap -histo:live pid

在稳定阶段触发了FullGC我们一般会拿到如下信息:

知识总结--性能优化总结(摘录+转载)_第4张图片

从以上gc日志中,我们大概可以分析到,在发生fullGC之时,整个应用的堆占用以及GC时间,当然了,为了更加精确,应该多收集几次,获取一个平均值。或者是采用耗时最长的一次FullGC来进行估算。

在上图中,fullGC之后,老年代空间占用在93168kb(约93MB),我们以此定为老年代空间的活跃数据。其他堆空间的分配,基于以下规则来进行。

知识总结--性能优化总结(摘录+转载)_第5张图片

基于以上规则和上图中的FullGC信息,我们现在可以规划的该应用堆空间为:

java 堆空间: 373Mb (=老年代空间93168kb*4)

新生代空间:140Mb(=老年代空间93168kb*1.5)

永久代空间:5Mb(=永久代空间3135kb*1.5)

老年代空间: 233Mb=堆空间-新生代看空间=373Mb-140Mb

对应的应用启动参数应该为:

java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m4延迟调优

在确定了应用程序的活跃数据大小之后,我们需要再进行延迟性调优,因为对于此时堆内存大小,延迟性需求无法达到应用的需要,需要基于应用的情况来进行调试。

在这一步进行期间,我们可能会再次优化堆大小的配置,评估GC的持续时间和频率、以及是否需要切换到不同的垃圾收集器上。

系统延迟需求

在调优之前,我们需要知道系统的延迟需求是那些,以及对应的延迟可调优指标是那些。应用程序可接受的平均停滞时间: 此时间与测量的Minor GC持续时间进行比较。可接受的Minor GC频率:Minor GC的频率与可容忍的值进行比较。可接受的最大停顿时间: 最大停顿时间与最差情况下FullGC的持续时间进行比较。可接受的最大停顿发生的频率:基本就是FullGC的频率。

以上中,平均停滞时间和最大停顿时间,对用户体验最为重要,可以多关注。

基于以上的要求,我们需要统计以下数据:MinorGC的持续时间;统计MinorGC的次数;FullGC的最差持续时间;最差情况下,FullGC的频率;

优化新生代的大小

知识总结--性能优化总结(摘录+转载)_第6张图片

比如如上的gc日志中,我们可以看到Minor GC的平均持续时间=0.069秒,MinorGC 的频率为0.389秒一次。

如果,我们系统的设置的平均停滞时间为50ms,当前的69ms明显是太长了,就需要调整。

我们知道新生代空间越大,Minor GC的GC时间越长,频率越低。

如果想减少其持续时长,就需要减少其空间大小。

如果想减小其频率,就需要加大其空间大小。

为了降低改变新生代的大小对其他区域的最小影响。在改变新生代空间大小的时候,尽量保持老年代空间的大小。

比如此次减少了新生代空间10%的大小,应该保持老年代和持代的大小不变化,第一步调优后的参数如下变化:

java -Xms359m -Xmx359m -Xmn126m -XX:PermSize=5m -XX:MaxPermSize=5m

新生代的大小有140m变为126,堆大小顺应变化,此时老年代是没有变化的。

优化老年代的大小

同上一步一样,在优化之前,也需要采集gc日志的数据。此次我们关注的是FullGC的持续时间和频率。

上图中,我们可以看到

FullGC 平均频率 =5.8s

FullGC 平均持续时间=0.14s

(以上为了测试,真实项目的fullGC 没有这么快)

如果没有FullGC的日志,有办法可以评估么?

我们可以通过对象提升率进行计算。

对象提升率

比如上述中启动参数中,我们的老年代大小=233Mb。

那么需要多久才能填满老年代中这233Mb的空闲空间取决于新生代到老年代的提升率。

每次提升老年代占用量=每次MinorGC 之后 java堆占用情况 减去 MinorGC后新生代的空间占用

对象提升率=平均值(每次提升老年代占用量) 除以老年代空间

有了对象提升率,我们就可以算出填充满老年代空间需要多少次minorGC,大概一次fullGC的时间就可以计算出来了。

比如:

知识总结--性能优化总结(摘录+转载)_第7张图片

上图中每次minorGC后,老年代空间为:

第一次minor GC 之后,老年代空间:13740kb - 13732kb =8kb

第二次minor GC 之后,老年代空间:22394kb - 17905kb =4489kb

第三次minor GC 之后,老年代空间:34739kb - 17917kb =16822kb

第四次minor GC 之后,老年代空间:48143kb - 17913kb =30230kb

第五次minor GC 之后,老年代空间:62112kb - 17917kb =44195kb

老年代每次minorGC提升率

4481kb 第二次和第一次minorGC之间

12333kb 第3次和第2次minorGC之间

13408kb 第4次和第3次minorGC之间

13965kb 第5次和第4次minorGC之间

由此我们可以测算出:

每次minorGC 的平均提升为12211kb,约为12Mb

上图中,平均minorGC的频率为 213ms/次

提升率=12211kb/213ms=57kb/ms

老年代空间233Mb ,占满大概需要233*1024/57=4185ms 约为4.185s。

FullGC的预期最差频率时长可以通过以上两种方式估算出来,可以调整老年代的大小来调整FullGC的频率,当然了,如果FullGC持续时间过长,无法达到应用程序的最差延迟要求,就需要切换垃圾处理器了。

具体如何切换,下篇再讲,比如切换为CMS,针对CMS的调优方式又有会细微的差别。

吞吐量调优

经过上述漫长 调优过程,最终来到了调优的最后一步,这一步对上述的结果进行吞吐量测试,并进行微调。

吞吐量调优主要是基于应用程序的吞吐量要求而来的,应用程序应该有一个综合的吞吐指标,这个指标基于真个应用的需求和测试而衍生出来的。当有应用程序的吞吐量达到或者超过预期的吞吐目标,整个调优过程就可以圆满结束了。

如果出现调优后依然无法达到应用程序的吞吐目标,需要重新回顾吞吐要求,评估当前吞吐量和目标差距是否巨大,如果在20%左右,可以修改参数,加大内存,再次从头调试,如果巨大就需要从整个应用层面来考虑,设计以及目标是否一致了,重新评估吞吐目标。

对于垃圾收集器来说,提升吞吐量的性能调优的目标就是就是尽可能避免或者很少发生FullGC 或者Stop-The-World压缩式垃圾收集(CMS),因为这两种方式都会造成应用程序吞吐降低。尽量在MinorGC 阶段回收更多的对象,避免对象提升过快到老年代。

最后

据Plumbr公司对特定垃圾收集器使用情况进行了一次调查研究,研究数据使用了84936个案例。在明确指定垃圾收集器的13%的案例中,并发收集器(CMS)使用次数最多;但大多数案例没有选择最佳垃圾收集器。这个比例占用在87%左右。

知识总结--性能优化总结(摘录+转载)_第8张图片

JVM调优是一个系统而又复杂的工作,目前jvm下的自动调整已经做的比较优秀,基本的一些初始参数都可以保证一般的应用跑的比较稳定了,对部分团队来说,程序性能可能优先级不高,默认垃圾收集器已经够用了。调优要基于自己的情况而来。

你可能感兴趣的:(性能优化,服务器,java)