• 开发高性能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应用基础与实践

  • 相关阅读:
    Python字符串学习
    文本压缩版本三
    文件压缩版本二
    文件压缩(2)
    d17包,logging模块,hashlib模块 openpyxl模块,深浅拷贝
    d16 collections模块 时间模块 random模块 os模块 sys模块 序列化模块 subprocess模块
    d15 常用模块之正则模块
    14天 模块 , 导模块 , 循环导入, 相对,绝对导入, 项目目录规范,
    13t天 迭代器,生成器,内置函数
    55 jquery
  • 原文地址:https://www.cnblogs.com/sa-dan/p/6837151.html
Copyright © 2020-2023  润新知