zoukankan      html  css  js  c++  java
  • 开发高性能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应用基础与实践

  • 相关阅读:
    hdu 4521 小明系列问题——小明序列(线段树 or DP)
    hdu 1115 Lifting the Stone
    hdu 5476 Explore Track of Point(2015上海网络赛)
    Codeforces 527C Glass Carving
    hdu 4414 Finding crosses
    LA 5135 Mining Your Own Business
    uva 11324 The Largest Clique
    hdu 4288 Coder
    PowerShell随笔3 ---别名
    PowerShell随笔2---初始命令
  • 原文地址:https://www.cnblogs.com/sa-dan/p/6837151.html
Copyright © 2011-2022 走看看