开发高性能JAVA应用程序基础(内存篇)

虽然Java的垃圾回收和当前高配置的服务器可以让程序员大部分时间忘掉OutOfMemoryError的存在,但是访问量增大后频繁的GC会额外消耗CPU (使用top查看结果为us值高),系统响应速度下降,积压的请求又会占用更多内存从而恶性循环,严重时可能导致系统不断Full GC造成应用停顿。

优化内存的使用可从以下几方面着手:

一、节流
1 使用单例模式

单例模式是开发者最早接触并使用的设计模式之一,尽管写代码的时候可能还不知道用了设计模式。简单来说就是构造函数private化,通过静态方法获得唯一实例。因为其特性,对于某些场景例如每次请求都要使用无状态工具类的检验方法,使用单例模式可以大量节省创建新对象的开销。

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class Singleton {  
  2.   
  3.     private Singleton() {}  
  4.       
  5.     private static Singleton instance = new Singleton();  
  6.       
  7.     public static Singleton getInstance() {  
  8.         return instance;  
  9.     }  
  10.       
  11.     public void doSomething() { }  
  12. }  
2 缓存常用对象

简单来说就是按一定特征创建"对象缓存池",使用集合类保存已创建的对象,当有相同特征的对象申请时,使用缓存池中现有的对象代替通过 new关键字重新创建。

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public class BigObjectPoolTest {  
  2.     public static void main(String[] args) {  
  3.         long start = System.nanoTime();  
  4.         for(int i = 0; i < 10000; i++) {  
  5.             BigObjectPool.getBigObject("xxx", true);  
  6.         }  
  7.         System.out.println("使用缓存池耗时" + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) + "毫秒");  
  8.         start = System.nanoTime();  
  9.         for(int i = 0; i < 10000; i++) {  
  10.             BigObjectPool.getBigObject("xxx", false);  
  11.         }  
  12.         System.out.println("不使用缓存池耗时" + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) + "毫秒");  
  13.     }  
  14. }  
  15.   
  16. class BigObjectPool {  
  17.     private static Map<String, BigObject> map = new HashMap<String, BigObject>();  
  18.       
  19.     static {  
  20.         map.put("xxx", new BigObject("xxx"));  
  21.         map.put("yyy", new BigObject("yyy"));  
  22.     }  
  23.       
  24.     public static BigObject getBigObject(String key, boolean usePool) {  
  25.         if(usePool) {  
  26.             BigObject bo = map.get(key);  
  27.             if(bo == null) {  
  28.                 bo = new BigObject(key);  
  29.             }  
  30.             return bo;  
  31.         } else {  
  32.             return new BigObject(key);  
  33.         }  
  34.     }  
  35. }  
  36.   
  37. class BigObject{  
  38.     private String name;  
  39.     private byte[] data = new byte[1024 * 1024];  
  40.     public BigObject(String name) { this.name = name; }  
  41. }  

以-Xms32m -Xmx32m -Xloggc:d:/gc.log 参数运行

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. 使用缓存池耗时3毫秒  
  2. 不使用缓存池耗时998毫秒  

(查看gc.log,可以观察到不使用缓存池触发Minor GC 1000次以上)

实际业务中通常使用EhCache等框架代替自己实现缓存池。

与这种实现原理相似的也有一个设计模式:享元模式,区别是享元模式更关注类设计结构上的优化,对上下文环境的设计也做了明确定义。

3 避免设计过大的对象

如果业务模型中要求的类的属性和方法都非常多,可以尝试将其拆分成多个小类,再通过合成/聚合模式组装成一个大类,这也符合设计模式的优化思想。甚至可以结合上面的对象缓存池的方式将其中一部分内容缓存化。

[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. class Composition {  
  2.     private BigObject bigObject = null;  
  3.     private int id;  
  4.   
  5.     public void setBigObject(BigObject bigObject) {  
  6.         this.bigObject = bigObject;  
  7.     }  
  8.   
  9.     public Composition(int id) {  
  10.         this.id = id;  
  11.     }  
  12. }  
[java] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. Composition c = new Composition(1);  
  2. c.setBigObject(BigObjectPool.getBigObject("xxx", true));  
4 一些小技巧

使用StringBuilder代替用+号连接字符串

尽量使用int, long等基本类型代替Integer, Long包装对象

合理利用SoftReference和WeakReference

二、开源 - 调整虚拟机参数

一般设置 java -server -Xms2048m -Xmx2048m -XX:PermSize=256m  -XX:MaxPermSize=256m

-Xms和-Xmx决定java堆区可使用的内存最小值和最大值,通常设为相同的值,避免运行期间反复的重新申请内存。如果出现OutOfMemoryError: Java heap space,则在硬件允许的情况下临时调大-Xmx,为排查问题和优化代码争取时间。

-XX:PermSize和-XX:MaxPermSize决定永久代可用空间大小,存放class和meta信息,通常设置为相同的值。如果出现OutOfMemoryError: PermGen space,说明加载的类和jar文件过多,可以调大这两个参数值。

如果web容器下有多个应用引用了相同的第三方jar文件,可以转移到容器的共享目录。

另一个重要参数是-Xmn,决定堆区新生代的大小,通常占-Xmx的比值设置为1/4到1/3。如果业务中有大量体积大且生命周期很短的对象创建需求,可适当调大新生代空间以利于失效对象在新生代中被回收。

此外,可通过参数设置回收算法

–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

回收算法的选择和对比需要较大的篇幅介绍,这里不做详细的解释。通常来说,对于响应时间优先的web应用,ConcMarkSweepGC(CMS)是个不错的选择。

需要注意的是,经过几代发展后,JVM对内存管理已经做的非常好。如果不是有明确的证据证明JVM的默认选择不合理,就没必要做过多细节的调整设置。调整后,可通过-XX:+PrintGCDetails -XX:+PrintGCTimeStamps等参数输出GC信息进行比对,优化的首要目标是减少Full GC次数和时间。

参考资料: 分布式java应用基础与实践

原文地址:https://www.cnblogs.com/sa-dan/p/6837151.html