性能调优读书笔记(上篇)

博客 动态
0 164
羽尘
羽尘 2022-08-30 00:03:49
悬赏:0 积分 收藏

性能调优读书笔记(上篇)

一、Amdahl定律

加速=优化前耗时/优化后耗时比
公式图:

二、设计模式

1、单例模式

静态内部类的方式:

/** * 内部类的单例模式 */public class StaticSingleton {    private StaticSingleton(){        System.out.println("aaa");    }    private static class StaticSingletonHolder{       private static StaticSingleton singleton=new StaticSingleton();    }    public static StaticSingleton getInstance(){        return StaticSingletonHolder.singleton;    }}

除了反射机制强制调用私有构造函数,生成多个实例外,序列化和反序列化也可能会导致。

防止序列化的单例:

/** * 可以被串行化的单例 */public class SerSingleton implements Serializable {    String name;    private SerSingleton(){        System.out.println("SerSingleton is create");        name="SerSingleton";    }    private static SerSingleton instance=new SerSingleton();    public static SerSingleton getInstance(){        return instance;    }    public static void createString(){        System.out.println("createString in Singleton");    }    //阻止生成新的实例,总是返回当前对象    private Object readResolve(){        return instance;    }}

关键是实现了readResolve方法

2、代理模式

1、代理模式用于延迟加载

2、动态代理
动态代理是指再运行时,动态生成代理类。

注意:动态代理使用字节码动态生成加载技术,在运行时生成并加载类。

常见的有jdk动态代理,cglib和javassist三种方式。

JDK方式

/** * JDK的动态代理 */public class JdkDBQueryHandler implements InvocationHandler {    IDBQuery real=null;    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if(real==null){            real=new DBQuery();        }        return real.request();    }    public static IDBQuery createJdkProxy(){        IDBQuery jdkProxy=(IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IDBQuery.class},                new JdkDBQueryHandler());        return jdkProxy;    }}

CGLIB:

/** * cglib方式 */public class CglibDBQueryInterceptor implements MethodInterceptor {    IDBQuery real=null;    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        if(real==null){            real=new DBQuery();        }        return real.request();    }    public static IDBQuery createCglibProxy(){        Enhancer enhancer=new Enhancer();        enhancer.setCallback(new CglibDBQueryInterceptor());    //指定切入器        enhancer.setInterfaces(new Class[]{IDBQuery.class});        IDBQuery cglibQuery=(IDBQuery)enhancer.create();        return cglibQuery;    }}

结论:jdk方式创建对象很快,但是调用方法较慢。

Hibernate中代理模式的应用:

User u=(User)HibernateSessionFactory.getSession().load(User.class,1);System.out.print(u.getClass().getName());System.out.print(u.getName());

以上代码中,load方法后,并没有查询数据库,在调用u.getName()时才查询的数据库,这就是Hibernate用代理模式做了延迟加载。

3、享元模式

概念:如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必每一次使用都创建新的对象。
功能组件如图:

注意:享元模式时为数不多的只为了提升系统性能而生的设计模式。他的主要作用就是复用大对象,节省内存和对象创建时间。

享元模式和对象池的最大不同在于:享元模式是不能互相替代的,他们有

/** * 报表接口 */public interface IReportManager {    String createReport();}
/** * 员工报表 */public class EmployeeReportManager implements IReportManager {    //租户ID    protected String   tenanId=null;    public EmployeeReportManager(String tenanId) {        this.tenanId = tenanId;    }    @Override    public String createReport() {        return "this is a employee Report";    }}
/** * 财务报表 */public class FinancialReportManager implements IReportManager {    //租户ID    protected String   tenanId=null;    public FinancialReportManager(String tenanId) {        this.tenanId = tenanId;    }    @Override    public String createReport() {        return "this is a Financial Report";    }}
/** * 享元工厂类 * 保证同一个id获取到的是同一个对象 */public class ReportManagerFactory {    Map<String,IReportManager> financialReportManager=new HashMap<>();    Map<String,IReportManager> employeeReportManager=new HashMap<>();    IReportManager getFinancialReportManager(String tenantId){        IReportManager r=financialReportManager.get(tenantId);        if(r==null){            r=new FinancialReportManager(tenantId);            financialReportManager.put(tenantId,r);        }        return r;    }    IReportManager getEmployeeReportManager(String tenantId){        IReportManager r=employeeReportManager.get(tenantId);        if(r==null){            r=new EmployeeReportManager(tenantId);            employeeReportManager.put(tenantId,r);        }        return r;    }}
4、装饰者模式

装饰者(Decorator)和被装饰者(ConcreteComponent)拥有相同的接口,装饰者可以在被装饰者的方法上加上特定的前后置处理,增强被装饰者的功能。

/** * 接口 */public interface IPacketCreator {    String handleContent();}
public abstract class PacketDecorator implements IPacketCreator {    IPacketCreator iPacketCreator;    public PacketDecorator(IPacketCreator iPacketCreator) {        this.iPacketCreator = iPacketCreator;    }}
public class PacketHTMLHeaderCreator extends PacketDecorator {    public PacketHTMLHeaderCreator(IPacketCreator iPacketCreator) {        super(iPacketCreator);    }    /**     * 将数据封装成html格式     * @return     */    @Override    public String handleContent() {        StringBuffer sb=new StringBuffer();        sb.append("<html>");        sb.append("<body>");        sb.append(iPacketCreator.handleContent());        sb.append("</body>");        sb.append("</html>\n");        return sb.toString();    }}
public class PacketHTTPHeaderCreator extends PacketDecorator {    public PacketHTTPHeaderCreator(IPacketCreator iPacketCreator) {        super(iPacketCreator);    }    @Override    public String handleContent() {        StringBuffer sb=new StringBuffer();        sb.append("Cache-Control:no-cache\n");        sb.append(iPacketCreator.handleContent());        return sb.toString();    }}

使用类:

@Test    public void test3(){        IPacketCreator iPacketCreator = new PacketHTTPHeaderCreator(new PacketHTMLHeaderCreator(new PacketBodyCreator()));        System.out.println(iPacketCreator.handleContent());    }
5、观察者模式

在软件系统中,当一个对象的行为依赖于另一个对象的状态时,观察者模式就非常有用。

代码示例如下:

package com.mmc.concurrentcystudy.design.guanchazhe;import java.util.Observable;import java.util.Observer;/** * 读者类,实现了观察者接口 */public class Reader implements Observer {    private String name;    public Reader(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    /**     * 关注作者     * @param writerName     */    public void subscribe(String writerName){        WriterManager.getInstance().getWriter(writerName).addObserver(this);    }    /**     * 取消关注     * @param writerName     */    public void unsunbscribe(String writerName){        WriterManager.getInstance().getWriter(writerName).deleteObserver(this);    }    /**     * 业务方法     * @param o     * @param arg     */    @Override    public void update(Observable o, Object arg) {        if(o instanceof Writer){            Writer writer=(Writer)o;            System.out.println(name+"知道"+writer.getName()+"发布了新书《"+writer.getLastNovel()+"》");        }    }}
package com.mmc.concurrentcystudy.design.guanchazhe;import java.util.Observable;/** * 作者类,要继承自被观察者类 */public class Writer extends Observable {    private String name;      //作者的名称    private String lastNovel;    //记录作者最新发布的小说    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getLastNovel() {        return lastNovel;    }    public void setLastNovel(String lastNovel) {        this.lastNovel = lastNovel;    }    public Writer(String name) {        this.name = name;        WriterManager.getInstance().add(this);    }    //发布新书    public void addNovel(String novel){        System.out.println(name+"发布了新书:《"+novel+"》");        lastNovel=novel;        setChanged();        notifyObservers();    }}
package com.mmc.concurrentcystudy.design.guanchazhe;import java.util.HashMap;import java.util.Map;/** * 管理器,保持一份独有的作者列表 */public class WriterManager {    private Map<String,Writer> map=new HashMap<>();    public void add(Writer writer){        map.put(writer.getName(),writer);    }    public Writer getWriter(String name){        return map.get(name);    }    //单例    private WriterManager(){}    public static WriterManager getInstance(){        return WriterManagerInstance.writerManager;    }    private static  class WriterManagerInstance{        private static WriterManager writerManager=new WriterManager();    }}

测试方法:

@Test    public void test4(){        Reader reader=new Reader("小明");        Reader reader2=new Reader("小红");        Reader reader3=new Reader("小李");        Writer writer=new Writer("韩寒");        Writer writer2=new Writer("李敖");        reader.subscribe("韩寒");        reader2.subscribe("韩寒");        reader3.subscribe("李敖");        writer.addNovel("三重门");        writer2.addNovel("不知道");    }

三、常见的优化组件

1、缓冲

示意图:

IO操作很容易形成性能瓶颈,所以尽可能加入缓冲组件。

2、缓存

缓存是为了系统性能而开辟的内存空间。最为简单的缓存是使用HashMap,但是这样做会遇到很多问题,比如不知道合适清理无效的数据,如何防止数据过多而内存溢出。

现在有很多缓存框架,如EHCache,OSCache,JBossCache等。

3、对象复用-“池”

如果一个类被频繁的使用,那么不必每次都生成一个实例,可以将这个实例保存在一个池中,待需要的时候直接从池中获取。这个池就称为对象池。

Apache中提供了一个Jakarta Commons Pool对象池组件,可以直接使用。

API列表:

public interface ObjectPool<T> extends Closeable {    //从对象池中获取到一个对象    T borrowObject() throws Exception, NoSuchElementException, IllegalStateException;    //对象返回给对象池    void returnObject(T var1) throws Exception;    }

Common Pool中内置了3个对象池,分别是StackObjectPool,GenericObjectPool,SoftReferenceObjectPool。

  • StackObjectPool:利用Stack来保存对象,可以指定初始化大小。

  • GenericObjectPool:是一个通用的对象池,可以设定对象池的容量,也可以设定无可用对象时应该怎样,有一个复杂的构造函数来定义这些行为。

  • SoftReferenceObjectPool:使用的是ArrayList保存,保存的是对象的软引用。

使用示例:

/** * 对象池 */public class PoolFactory extends BasePooledObjectFactory<Object> {    static GenericObjectPool<Object> pool = null;    // 取得对象池工厂实例    public synchronized static GenericObjectPool<Object> getInstance() {        if (pool == null) {            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();            poolConfig.setMaxIdle(-1);            poolConfig.setMaxTotal(-1);            poolConfig.setMinIdle(100);            poolConfig.setLifo(false);            pool = new GenericObjectPool<Object>(new PoolFactory(), poolConfig);        }        return pool;    }    public static Object borrowObject() throws Exception{        return (Object) PoolFactory.getInstance().borrowObject();    }    public static void returnObject(Object jdbcUtils) throws Exception{        PoolFactory.getInstance().returnObject(jdbcUtils);    }    public static void close() throws Exception{        PoolFactory.getInstance().close();    }    public static void clear() throws Exception{        PoolFactory.getInstance().clear();    }    @Override    public Object create() throws Exception {        return new Object();    }    @Override    public PooledObject<Object> wrap(Object obj) {        return new DefaultPooledObject<Object>(obj);    }} 

测试代码:

@Test    public void test6() throws Exception {        Object o=PoolFactory.borrowObject();        PoolFactory.returnObject(o);        Object o2=PoolFactory.borrowObject();        PoolFactory.returnObject(o2);        System.out.println(o==o2);    }

注意:只有对重量级对象使用对象池技术才能提高系统性能,对轻量级的对象使用反而会降低性能。

4、并行替代串行
5、负载均衡

为保证应用程序的服务质量,需要使用多台计算机协同工作,将系统负载尽可能分配到各个计算机节点上。

6、时间换空间

一般用于嵌入式设备或者内存,硬盘不足的情况下。
比如一个简单的例子,a和b两个变量的值的替换。最常用的方法是引入一个中间变量。为了省去中间变量可以用这样的方法:

 @Test    public void test7(){        int a=3;        int b=5;        a=a+b;        b=a-b;        a=a-b;        System.out.println(a);        System.out.println(b);    }
7、空间换时间

最典型的应用就是缓存了,除了缓以外,有一些排序方法也会用到。

一个空间换时间的排序示例:

/** * 空间换时间的排序 */public class SpaceSort {    public static int arrayLen=1000000;    public static void main(String[] args) {        int [] a=new int[arrayLen];        int [] old=new int[arrayLen];        Map<Integer,Object> map=new HashMap<>();        int count=0;        while (count<a.length){      //初始化数组            int value=(int)(Math.random()*arrayLen*10);            if(map.get(value)==null){                map.put(value,value);                a[count]=value;                count++;            }        }        System.arraycopy(a,0,old,0,a.length);        long start=System.currentTimeMillis();        Arrays.sort(a);        System.out.println("Arrays.sort spend:"+(System.currentTimeMillis()-start));        start=System.currentTimeMillis();        spaceToTime(old);        System.out.println("spaceToTime spend:"+(System.currentTimeMillis()-start));    }    public static void spaceToTime(int[] array){        int i=0;        int max=array[0];        int l=array.length;        //找出最大值        for (i=0;i<l;i++){            if(array[i]>max)                max=array[i];        }        int []temp=new int[max+1];   //分配临时空间        for (i=0;i<l;i++){            temp[array[i]]=array[i];    //以索引下标标识数字大小        }        int j=0;        int max1=max+1;        for (i=0;i<max1;i++){   //线性复杂度            if (temp[i]>0){                array[j++]=temp[i];            }        }    }}

四、Java程序优化

1、字符串优化

使用StringTokenizer类分割字符串

 public void test9(){        StringBuilder sb=new StringBuilder();        int len=1000;        for (int i=0;i<len;i++){            sb.append(i);            sb.append(";");        }        String str=sb.toString();        long start=System.currentTimeMillis();        StringTokenizer st=new StringTokenizer(str,";");        for (int i=0;i<10000;i++){           while (st.hasMoreElements()){               String s = st.nextToken();           }           st=new StringTokenizer(str,";");        }        System.out.println(System.currentTimeMillis()-start);    }

目前来说这种方法也没快多少

2、数据结构

ArrayList和LinkedList

  • 添加元素到队列尾部 ArrayList块
  • 添加到任意位置 LinkedList快

删除对比:

  • 在能够有效评估ArrayList数组大小时,指定容量大小能对性能有提升
  • LinkedList不要用for(int i=0;i<list.size();i++)遍历,用foreach。即如果需要通过索引下标对集合进行访问,那最好用ArrayList

TreeMap和LinkedHashMap的区别。
他们都是可排序的,LinkedHashMap基于元素进入集合或者被访问的先后顺序排序,TreeMap是根据元素的固有顺序(Comparator或者Comparable)

3、优化集合访问方法
  1. 分离循环中被重复调用的代码
    如下面的那个list.size()会多次调用。但实际上这个方法也很快
   for (int i=0;i<list.size();i++){            count++;        }        len=list.size();        for (int i=0;i<len;i++){            count++;        }
  1. 减少方法调用
    如果可以直接访问内部元素,就不用调用对应的接口。因为函数调用是需要消耗系统资源的。
4、NIO提升性能

在读写文件上使用NIO会更快
以写入4000000的int数字为例

---StreamByteBufferMappedByteBuffer
写耗时295ms123ms32ms
读耗时433ms59ms17ms

测试示例:

/**     * IO写文件     * @throws IOException     */    @Test    public void test12() throws IOException {        long start=System.currentTimeMillis();        DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("d://1.txt")));        for (int i=0;i<4000000;i++){            dos.writeInt(i);        }        if(dos!=null)            dos.close();        System.out.println(System.currentTimeMillis()-start);    }    /**     * IO读文件     * @throws IOException     */    @Test    public void test13() throws IOException {        long start=System.currentTimeMillis();       DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("d://1.txt")));       for (int i=0;i<4000000;i++){           dis.readInt();       }       if(dis!=null)           dis.close();        System.out.println(System.currentTimeMillis()-start);    }    /**     * NIO写文件     * @throws IOException     */    @Test    public void test14() throws IOException {        long start=System.currentTimeMillis();        FileOutputStream fos=new FileOutputStream("d:/1.txt");        FileChannel fc=fos.getChannel();        ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);        for (int i = 0; i <4000000 ; i++) {            byteBuffer.put(int2byte(i));        }        byteBuffer.flip();        fc.write(byteBuffer);        System.out.println(System.currentTimeMillis()-start);    }    /**     * NIO读文件     * @throws IOException     */    @Test    public void test15() throws IOException {        long start=System.currentTimeMillis();        FileInputStream fin=new FileInputStream("d://1.txt");        FileChannel channel = fin.getChannel();        ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);        channel.read(byteBuffer);        channel.close();        byteBuffer.flip();        while (byteBuffer.hasRemaining()){            byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());        }        System.out.println(System.currentTimeMillis()-start);    }    public static byte[] int2byte(int res){        byte[] targets=new byte[4];        targets[3]=(byte)(res&0xff);        targets[2]=(byte)((res>>8)&0xff);        targets[1]=(byte)((res>>16)&0xff);        targets[0]=(byte)(res>>24);        return targets;    }    public static int byte2int(byte b1,byte b2,byte b3,byte b4){        return ((b1&0xff)<<24)|((b2&0xff)<<16)|((b3&0xff)<<8)|(b4&0xff);    }         /**     * NIO MappedByteBuffer写文件     * @throws IOException     */    @Test    public void test16() throws IOException {        long start=System.currentTimeMillis();       FileChannel fc=new RandomAccessFile("d://1.txt","rw").getChannel();        IntBuffer ib=fc.map(FileChannel.MapMode.READ_WRITE,0,4000000*4).asIntBuffer();        for (int i = 0; i < 4000000; i++) {            ib.put(i);        }        if(fc!=null)            fc.close();        System.out.println(System.currentTimeMillis()-start);    }    /**     * NIO MappedByteBuffer读文件     * @throws IOException     */    @Test    public void test17() throws IOException {        long start=System.currentTimeMillis();      FileChannel fc=new FileInputStream("d://1.txt").getChannel();      IntBuffer ib=fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();      while (ib.hasRemaining()){          ib.get();      }    if(fc!=null)        fc.close();        System.out.println(System.currentTimeMillis()-start);    }

2、直接内存访问
DirectBuffer:直接可以访问系统物理内存的类,在对普通的ByteBuffer访问时,系统会使用一个“内核缓冲区”进行间接的操作,而DirectBuffer所处的位置就相当于这个“内核缓冲区”。因此他更接近底层,更快。

但是DirectBuffer创建销毁都比较费时间,在需要频繁创建和销毁的情况下不适合用。

/**     * 使用DirectBuffer     * @throws IOException     */    @Test    public void test18() throws IOException {        long start=System.currentTimeMillis();       ByteBuffer b=ByteBuffer.allocateDirect(500);       for (int i=0;i<100000;i++){           for (int j=0;j<99;j++)               b.putInt(j);          b.flip();           for (int j=0;j<99;j++)               b.getInt();           b.clear();       }        System.out.println(System.currentTimeMillis()-start);    }    @Test    public void test19() throws IOException {        long start=System.currentTimeMillis();        ByteBuffer b=ByteBuffer.allocate(500);        for (int i=0;i<100000;i++){            for (int j=0;j<99;j++)                b.putInt(j);            b.flip();            for (int j=0;j<99;j++)                b.getInt();            b.clear();        }        System.out.println(System.currentTimeMillis()-start);    }
5、引用类型
  1. 强引用
  2. 软引用
    GC的时候,因为内存没满,没有被回收。
@Test    public void test20(){        MyObject myObject=new MyObject();        //创建引用队列        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();        //创建软引用        SoftReference<MyObject> softReference=new SoftReference<>(myObject,referenceQueue);        myObject=null;        System.gc();        System.out.println("After Gc:soft get="+softReference.get());        System.out.println("分配大内存");        byte[] b=new byte[4*1024*925];        System.out.println("After new byte[]:soft get="+softReference.get());    }
  1. 弱引用
    在GC的时候一旦发现有弱引用,直接被回收
@Test    public void test20(){        MyObject myObject=new MyObject();        //创建引用队列        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();        //创建软引用        WeakReference<MyObject> softReference=new WeakReference<>(myObject,referenceQueue);        myObject=null;        System.gc();        System.out.println("After Gc:soft get="+softReference.get());        System.out.println("分配大内存");        byte[] b=new byte[4*1024*925];        System.out.println("After new byte[]:soft get="+softReference.get());    }
  1. 虚引用
    虚引用是引用类型最弱的一个,他的作用在于跟踪垃圾回收。
  2. WeakHashMap
    当需要使用HashMap做一个简单的缓存时,建议使用WeakHashMap,他是弱引用的,可以在内存满的情况下,GC时清除没有被引用的表项
6、改善性能小技巧
  1. 使用局部变量

调用方法时传递的参数和在方法在创建的临时变量都保存在栈中,速度较快。其他变量,如静态变量,实例变量都在堆中。

 @Test    public void test21() throws IOException {        long start=System.currentTimeMillis();        int a=0;        for (int i=0;i<2000000000;i++){                a++;        }        System.out.println(a);        System.out.println(System.currentTimeMillis()-start);    }    private static int ta=0;    @Test    public void test22() throws IOException {        long start=System.currentTimeMillis();        int a=0;        for (int i=0;i<2000000000;i++){            ta++;        }        System.out.println(ta);        System.out.println(System.currentTimeMillis()-start);    }
  1. 位运算代替乘除法

a*2 用a<<1

a/2 用a>>1
3. 替代switch

用数组替代switch,效率会更高

 public int sw(int a){        switch (a){            case 1:return 1;            case 2:return 3;            case 3:return 5;            case 4:return 9;            default:return 0;        }    }        //用数组来实现    public int sw2(int a){        int[] array=new int[]{1,3,5,9,0};        return array[a];    }

4、复制数组用System.arraycopy
System.arraycopy时浅拷贝。对于非基本类型而言,他拷贝的是对象的引用,而非新建一个对象。

5、clone方法代替new

clone方法不会调用构造函数,所以能够快速的创造一个实例。默认情况下是浅拷贝。但是拷贝的对象修改属性,旧的对象的值可能是不会变的。

6、静态方法代替实例方法

实例方法需要维护一张类似虚拟结构表的东西以支持对多态的实现。所以比静态方法慢。

posted @ 2022-08-29 22:44 女友在高考 阅读(2) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员