内存优化利器,享元模式


在了解什么是享元模式之前,我们先来看个问题。

需求

我们需要创建一个兵工厂,这个兵工厂复制生产各种型号的子弹。每个子弹都需要型号说明和自己独一无二的标识。然后我用下面的代码实现了一份出来。

子弹的抽象规格

/**
 * 子弹规格,抽象对象
 */
public abstract class BulletSpecification {

    /**
     * 口径,mm
     * @return
     */
    public abstract double getCaliber();

    /**
     * 长度,mm
     * @return
     */
    public abstract double getLength();

    /**
     * 形状
     * @return
     */
    public abstract String getShape();

    @Override
    public String toString() {
        return "Type:" + this.getClass().getSimpleName() + ",Caliber:" + getCaliber() + ",Length:" + getLength() + ",Shape:" + getShape();
    }
}

各种具体的规格

/**
 * .22 BB Cap
 * @author skyline
 */
public class BBCap22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 7;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

/**
 * .22 CB Cap
 * @author skyline
 */
public class CBCap22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 11;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

/**
 * .22 Long
 * @author skyline
 */
public class Long22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 15;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

/**
 * .22 LR
 * @author skyline
 */
public class LR22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.7;
    }

    @Override
    public double getLength() {
        return 15;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

/**
 * .22 Short
 * @author skyline
 */
public class Short22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 11;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

/**
 * .22 Stinger
 * @author skyline
 */
public class Stinger22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 18;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}
/**
 * .22 Win
 * @author skyline
 */
public class Win22 extends BulletSpecification {
    @Override
    public double getCaliber() {
        return 5.59;
    }

    @Override
    public double getLength() {
        return 27;
    }

    @Override
    public String getShape() {
        return "Rim,S";
    }
}

子弹与子弹工厂

/**
 * 子弹
 * @author skyline
 */
public class Bullet {
    private final String id;
    private final BulletSpecification specification;

    public Bullet(BulletSpecification specification) {
        this.id = UUID.randomUUID().toString();
        this.specification = specification;
    }

    public String getId() {
        return id;
    }

    public BulletSpecification getSpecification() {
        return specification;
    }

    @Override
    public String toString() {
        return "Bullet{" +
                "id='" + id + '\'' +
                ", specification=" + specification +
                '}';
    }
}
/**
 * 子弹工厂
 * @author skyline
 */
public class BulletFactory {

    public Bullet createBullet(Class<? extends BulletSpecification> specificationClazz) {
        Bullet bullet = null;
        try {
            BulletSpecification bulletSpecification = specificationClazz.newInstance();
            bullet = new Bullet(bulletSpecification);
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return bullet;
    }
}

生产子弹

public class FlyWeightMain {
    public static void main(String[] args) {
        BulletFactory factory = new BulletFactory();
        Class<? extends BulletSpecification>[] types = new Class[]{BBCap22.class, CBCap22.class, Long22.class, LR22.class, Short22.class, Win22.class, Stinger22.class};
        List<Bullet> bullets = new ArrayList<>();
        for (Class<? extends BulletSpecification> type : types) {
            for (int i = 0; i < 100000; i++) {
                Bullet bullet = factory.createBullet(type);
                bullets.add(bullet);
                System.out.println(bullet.toString());
            }
        }
    }
}

问题

上面的这一套代码搞下来其实从程序运行的角度看,没啥问题。直接运行一下也可以正常结束。为了突出问题,我给程序加了一些限制-Xmx115M -Xms115M,然后我们再看看。
在这里插入图片描述
从截图中可以看出来GC的非常疯狂,最终GC总耗时大约是5.3秒。那么有没有优化空间呢?

思考

我们从对象上分析后发现,每个子弹对象都包含两部分,如下图:
在这里插入图片描述
一个是子弹的id,这个是每个子弹都不一样的。还有一个是子弹的规格信息specification。这个规格信息中包含了很多数据,而且其实没必要每个子弹都创建一个新的规格信息,相同规格的子弹共用一个规格信息对象即可。这样我们就可以省下很多内存空间。

改造

按照上面的思想我对程序作了改造,改造后程序如下:
在这里插入图片描述
相同规格的子弹使用的规格信息被缓存到specificationMap中,每次创建子弹时先从specificationMap中获取规格信息,如果规格信息不存在,那再创建。让我们看下改造之后的GC情况:
在这里插入图片描述
从GC上看明显比第一版程序快了很多。这就是享元模式给我们带来的好处

享元模式

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新的对象。

字符串常量池

JVM层面最典型的享元模式的应用就是字符串常量池了。String之所以能够作为常量是因为String本身是final的,同时String中的char[]也是final的,也就是说String一旦new出来,就不可能再发生变化了。
在这里插入图片描述
接下来我们看下下面这段经典代码:

/**
 * 字符串常量池
 */
public class StringMain {
    public static void main(String[] args) {
        String str1 = new String("a");
        String str2 = "a";
        String str3 = str1.intern();
        String str4 = new String("a").intern();
        System.out.println(str1 == str2); //false
        System.out.println(str2 == str3); //true
        System.out.println(str3 == str4); //true
        System.out.println(str4 == str1); //false
    }
}

总结

  1. 享元模式,共享元数据信息。可以达到节省内存提高效率的作用。
  2. 享元模式同样也蕴含了池化的思想,比如上面的specificationMap就是一个对象池。
  3. 因为享元模式需要对new方法进行封装,所以一般会和工厂模式一起用。
  4. String常量池就是JVM层面实现的享元模式。线程池也可以看作是享元模式。
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值