JVM实用参数 吞吐量收集器

在实践中我们发现对于大多数的应用领域,评估一个垃圾收集(GC)算法如何根据如下两个标准:

  1. 吞吐量越高算法越好
  2. 暂停时间越短算法越好

首先让我们来明确垃圾收集(GC)中的两个术语:吞吐量(throughput)和暂停时间(pause times)。 JVM在专门的线程(GC threads)中执行GC。 只要GC线程是活动的,它们将与应用程序线程(application threads)争用当前可用CPU的时钟周期。 简单点来说,吞吐量是指应用程序线程用时占程序总用时的比例。 例如,吞吐量99/100意味着100秒的程序执行时间应用程序线程运行了99秒, 而在这一时间段内GC线程只运行了1秒。

术语”暂停时间”是指一个时间段内应用程序线程让与GC线程执行而完全暂停。 例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的。 如果说一个正在运行的应用程序有100毫秒的“平均暂停时间”,那么就是说该应用程序所有的暂停时间平均长度为100毫秒。 同样,100毫秒的“最大暂停时间”是指该应用程序所有的暂停时间最大不超过100毫秒。

吞吐量 VS 暂停时间

高吞吐量最好因为这会让应用程序的最终用户感觉只有应用程序线程在做“生产性”工作。 直觉上,吞吐量越高程序运行越快。 低暂停时间最好因为从最终用户的角度来看不管是GC还是其他原因导致一个应用被挂起始终是不好的。 这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。 因此,具有低的最大暂停时间是非常重要的,特别是对于一个交互式应用程序。

不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。这样想想看,为了清晰起见简化一下:GC需要一定的前提条件以便安全地运行。 例如,必须保证应用程序线程在GC线程试图确定哪些对象仍然被引用和哪些没有被引用的时候不修改对象的状态。 为此,应用程序在GC期间必须停止(或者仅在GC的特定阶段,这取决于所使用的算法)。 然而这会增加额外的线程调度开销:直接开销是上下文切换,间接开销是因为缓存的影响。 加上JVM内部安全措施的开销,这意味着GC及随之而来的不可忽略的开销,将增加GC线程执行实际工作的时间。 因此我们可以通过尽可能少运行GC来最大化吞吐量,例如,只有在不可避免的时候进行GC,来节省所有与它相关的开销。

然而,仅仅偶尔运行GC意味着每当GC运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。 单个GC需要花更多时间来完成, 从而导致更高的平均和最大暂停时间。 因此,考虑到低暂停时间,最好频繁地运行GC以便更快速地完成。 这反过来又增加了开销并导致吞吐量下降,我们又回到了起点。
综上所述,在设计(或使用)GC算法时,我们必须确定我们的目标:一个GC算法只可能针对两个目标之一(即只专注于最大吞吐量或最小暂停时间),或尝试找到一个二者的折衷。

HotSpot虚拟机上的垃圾收集

该系列的第五部分我们已经讨论过年轻代的垃圾收集器。 对于年老代,HotSpot虚拟机提供两类垃圾收集算法(除了新的G1垃圾收集算法),第一类算法试图最大限度地提高吞吐量,而第二类算法试图最小化暂停时间。 今天我们的重点是第一类,”面向吞吐量”的垃圾收集算法。
我们希望把重点放在JVM配置参数上,所以我只会简要概述HotSpot提供的面向吞吐量(throughput-oriented)垃圾收集算法。 当年老代中由于缺乏空间导致对象分配失败时会触发垃圾收集器(事实上,”分配”的通常是指从年轻代提升到年老代的对象)。 从所谓的”GC根”(GC roots)开始,搜索堆中的可达对象并将其标记为活着的,之后,垃圾收集器将活着的对象移到年老代的一块无碎片(non-fragmented)内存块中,并标记剩余的内存空间是空闲的。 也就是说,我们不像复制策略那样移到一个不同的堆区域,像年轻代垃圾收集算法所做的那样。 相反地,我们把所有的对象放在一个堆区域中,从而对该堆区域进行碎片整理。 垃圾收集器使用一个或多个线程来执行垃圾收集。 当使用多个线程时,算法的不同步骤被分解,使得每个收集线程大多时候工作在自己的区域而不干扰其他线程。 在垃圾收集期间,所有的应用程序线程暂停,只有垃圾收集完成之后才会重新开始。 现在让我们来看看跟面向吞吐量垃圾收集算法有关的重要JVM配置参数。

-XX:+UseSerialGC

我们使用该标志来激活串行垃圾收集器,例如单线程面向吞吐量垃圾收集器。 无论年轻代还是年老代都将只有一个线程执行垃圾收集。 该标志被推荐用于只有单个可用处理器核心的JVM。 在这种情况下,使用多个垃圾收集线程甚至会适得其反,因为这些线程将争用CPU资源,造成同步开销,却从未真正并行运行。

-XX:+UseParallelGC

有了这个标志,我们告诉JVM使用多线程并行执行年轻代垃圾收集。 在我看来,Java 6中不应该使用该标志因为-XX:+UseParallelOldGC显然更合适。 需要注意的是Java 7中该情况改变了一点(详见本概述),就是-XX:+UseParallelGC能达到-XX:+UseParallelOldGC一样的效果。

-XX:+UseParallelOldGC

该标志的命名有点不巧,因为”老”听起来像”过时”。 然而,”老”实际上是指年老代,这也解释了为什么-XX:+UseParallelOldGC要优于-XX:+UseParallelGC:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集。 当期望高吞吐量,并且JVM有两个或更多可用处理器核心时,我建议使用该标志。
作为旁注,HotSpot的并行面向吞吐量垃圾收集算法通常称为”吞吐量收集器”,因为它们旨在通过并行执行来提高吞吐量。

-XX:ParallelGCThreads

通过-XX:ParallelGCThreads=<value>我们可以指定并行垃圾收集的线程数量。 例如,-XX:ParallelGCThreads=6表示每次并行垃圾收集将有6个线程执行。 如果不明确设置该标志,虚拟机将使用基于可用(虚拟)处理器数量计算的默认值。 决定因素是由Java Runtime。availableProcessors()方法的返回值N,如果N<=8,并行垃圾收集器将使用N个垃圾收集线程,如果N>8个可用处理器,垃圾收集线程数量应为3+5N/8。
当JVM独占地使用系统和处理器时使用默认设置更有意义。 但是,如果有多个JVM(或其他耗CPU的系统)在同一台机器上运行,我们应该使用-XX:ParallelGCThreads来减少垃圾收集线程数到一个适当的值。 例如,如果4个以服务器方式运行的JVM同时跑在在一个具有16核处理器的机器上,设置-XX:ParallelGCThreads=4是明智的,它能使不同JVM的垃圾收集器不会相互干扰。

-XX:-UseAdaptiveSizePolicy

吞吐量垃圾收集器提供了一个有趣的(但常见,至少在现代JVM上)机制以提高垃圾收集配置的用户友好性。 这种机制被看做是HotSpot在Java 5中引入的”人体工程学”概念的一部分。 通过人体工程学,垃圾收集器能将堆大小动态变动像GC设置一样应用到不同的堆区域,只要有证据表明这些变动将能提高GC性能。 “提高GC性能”的确切含义可以由用户通过-XX:GCTimeRatio和-XX:MaxGCPauseMillis(见下文)标记来指定。
重要的是要知道人体工程学是默认激活的。 这很好,因为自适应行为是JVM最大优势之一。 不过,有时我们需要非常清楚对于特定应用什么样的设置是最合适的,在这些情况下,我们可能不希望JVM混乱我们的设置。 每当我们发现处于这种情况时,我们可以考虑通过-XX:-UseAdaptiveSizePolicy停用一些人体工程学。

-XX:GCTimeRatio

通过-XX:GCTimeRatio=<value>我们告诉JVM吞吐量要达到的目标值。 更准确地说,-XX:GCTimeRatio=N指定目标应用程序线程的执行时间(与总的程序执行时间)达到N/(N+1)的目标比值。 例如,通过-XX:GCTimeRatio=9我们要求应用程序线程在整个执行时间中至少9/10是活动的(因此,GC线程占用其余1/10)。 基于运行时的测量,JVM将会尝试修改堆和GC设置以期达到目标吞吐量。 -XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间。

-XX:MaxGCPauseMillis

通过-XX:GCTimeRatio=<value>告诉JVM最大暂停时间的目标值(以毫秒为单位)。 在运行时,吞吐量收集器计算在暂停期间观察到的统计数据(加权平均和标准偏差)。 如果统计表明正在经历的暂停其时间存在超过目标值的风险时,JVM会修改堆和GC设置以降低它们。 需要注意的是,年轻代和年老代垃圾收集的统计数据是分开计算的,还要注意,默认情况下,最大暂停时间没有被设置。
如果最大暂停时间和最小吞吐量同时设置了目标值,实现最大暂停时间目标具有更高的优先级。 当然,无法保证JVM将一定能达到任一目标,即使它会努力去做。 最后,一切都取决于手头应用程序的行为。
当设置最大暂停时间目标时,我们应注意不要选择太小的值。 正如我们现在所知道的,为了保持低暂停时间,JVM需要增加GC次数,那样可能会严重影响可达到的吞吐量。 这就是为什么对于要求低暂停时间作为主要目标的应用程序(大多数是Web应用程序),我会建议不要使用吞吐量收集器,而是选择CMS收集器。 CMS收集器是本系列下一部分的主题。

Fluent Interface

    Fluent Interface是一种通过连续的方法调用以完成特定逻辑处理的API实现方式,在代码中引入Fluent Interface不仅能够提高开发效率,而且在提高代码可读性上也有很大的帮助。最早由Martin Fowler与Eric Evans于2005年提出,到今天日趋完善。
    private void makeFluent(Customer customer) {
        customer.newOrder()
                .with(6, “TAL”)
                .with(5, “HPK”).skippable()
                .with(3, “LGV”)
                .priorityRush();
    }
    这是一段fluent风格的代码。可以看出是借用了Method Chaining的形式,其实方法链通常来说是fluent interface的实现方式,他是借用了方法链来实现的一种Internal DSL(Domain-Specific Language), 但值得注意的是,fluent  interface不只用到方法链,还用到嵌套函数以及对象范围(object scoping)。
以上代码的正常形式是:
private void makeNormal(Customer customer) {
        Order o1 = new Order();
        customer.addOrder(o1);
        OrderLine line1 = new OrderLine(6, Product.find(“TAL”));
        o1.addLine(line1);
        OrderLine line2 = new OrderLine(5, Product.find(“HPK”));
        o1.addLine(line2);
        OrderLine line3 = new OrderLine(3, Product.find(“LGV”));
        o1.addLine(line3);
        line2.setSkippable(true);
        o1.setRush(true);
    }
    可以看出,fluent interface 具有更多的可读性,流畅性,同时也简化了代码 。尤其是在设计api的时候,这点尤为重要。Google Collections中的MapMaker就是一个很好的例子:
ConcurrentMap graphs = new MapMaker()
    .concurrencyLevel(32)
    .softKeys()
    .weakValues()
    .expiration(30, TimeUnit.MINUTES)
    .makeComputingMap(
        new Function() {
            public Graph apply(Key key) {
                return createExpensiveGraph(key);
            }
     });
    其他如java8 java.util.stream,AndroidSDK中的Builder设计模式相关代码,也提供了fluent风格的api。
    Fluent interface 在开发框架的配置功能上经常被应用,以简化框架的配置过程。还有另外一种情况是有明确的连续性上下文,可以用相关对象行为链接的方式展现出来。例如有起到配置功能的简单示例,用来给服务添加缓存配置,日志配置:
IService aService = new Service();
aService.UseCache(new AppfabricCache()).UseLogger(new ConsoleLogger());
前述后者的示例,表示一个餐馆客户进入吃饭的流程,制定参观名称name()以后,show()展示菜单,order()下订单,eat()吃饭,最后支付pay():
new Arsalan().name(“ARSALAN”).show().order(0).order(1).eat().pay();
    可以看出,fluent interface的每个中间方法都返回了下一步动作需要的引用,而不用重新指定实例。我们在设计fluent interface风格的api时,API应该能提示指导API用户下一步用什么,以及某个时刻用户可能采取的操作。具体来说,如何确定每个方法返回什么类型呢?其实这个答案取决于你下一个动作需要什么类型,需要具体到场景才能确定。而一般说来,直接返回this的情况很多,这适用于对调用次序和调用次数没有限制的场景,返回this的setter方法你可以调用多次,每次都会覆盖旧值,fluent interface本身不做限制。
    另外一种就是返回中间对象,这种方式有更多的好处,我们可以使用编译器来强制业务规则(比如:必需属性),可以通过限制链中下一个元素的可用选项,通过一个特殊途径引导我们的连贯接口用户,在用户可以(或必须)调用哪些方法、调用顺序、用户可以调用多少次等方面,给了API创建者更大的控制力。我们可以通过控制返回中间对象的类型区别,达到既有构造方法强制输入参数的限制功能,也有了setter方法的灵活。
    每个软件设计方案,都有其优缺点,我们需要具体到某个场景下,才能在优缺点的权衡中找到答案。很明显,fluent interface违背了“Command与Query分离”的原则,其setter也需要返回值,再者,一旦脱离了链式调用上下文,单个方法可能出现意义不明确的情况。
    所以尽管它看起来很“漂亮”,让代码更简洁可读,但也要认真考虑场景是否适用,最少也要考虑自己是否有时间去设计一套合理的fluent interface。

继续阅读Fluent Interface

反射调用方式性能对比

  • 反射调用方式性能对比

    概述

    本文对几种反射方式的执行性能进行了对比,确定了执行效率较高的反射调用方式。

    几种反射调用方式

    • JDK的Method调用
      Method method = MyClass.class.getMethod("methodName");
      method.invoke(Object obj, Object... args);
    • CGLIB的FastMethod调用
      FastClass fastClass = FastClass.create(Date.class);
      FastMethod fastMethod = fastClass.getMethod(getTime);
      fastMethod.invoke(Object obj, Object[] args);
    • 基于Javassist实现的自定义Invoker调用
      详见代码

    测试结果

    • 测试环境
      • JDK:1.7.0_79
      • 操作系统:Mac OSX, 2.5 GHz Intel Core i7
    • 测试结果
      ---------------固定参数调用------------------
      直接调用耗时:14ms
      javassist自定义Invokers调用耗时:115ms
      JDK的Method反射调用耗时:4171ms
      CGLIB的FastMethod调用耗时:2262ms
      ---------------可变参数调用------------------
      javassist自定义Invoker调用耗时:508ms
      JDK的Method反射调用耗时:2745ms
      CGLIB的FastMethod反射调用耗时:2239ms
      • 通过对比发现,基于Javassist实现的反射调用执行效率明显高于其他方式。

    测试代码

    import net.sf.cglib.reflect.FastClass;
    import net.sf.cglib.reflect.FastMethod;
    
    import java.lang.reflect.Method;
    import java.util.Date;
    
    public class InvokeTest {
    
        public static interface Getter {
            Object get(Object obj);
        }
    
        public static interface Setter {
            void set(Object obj, Object value);
        }
    
        public static void testInvoke() {
            try {
                // 创建getter调用器,用于调用getTime方法
                Getter getter = Invokers.newInvoker(Getter.class, Date.class,
                        "getTime", null, Long.TYPE);
                // 创建setter调用器,用于调用setTime方法
                Setter setter = Invokers.newInvoker(Setter.class, Date.class,
                        "setTime", new Class<?>[]{Long.TYPE}, null);
    
                Date date = new Date();
                //System.out.println("time=" + getter.get(date));
                setter.set(date, 33333333L);
                //System.out.println("time1=" + getter.get(date));
    
                Method getTime = Date.class.getMethod("getTime");
                Method setTime = Date.class.getMethod("setTime", Long.TYPE);
                getTime.setAccessible(true);
                setTime.setAccessible(true);
    
                FastClass fastClass = FastClass.create(Date.class);
                FastMethod fastGetTime = fastClass.getMethod(getTime);
                FastMethod fastSetTime = fastClass.getMethod(setTime);
                //System.out.println("time2=" + getTime.invoke(date));
    
                final int TIMES = 100000000;
    
                //直接调用
                long t = System.currentTimeMillis();
                for (int i = 0; i < TIMES; i++) {
                    date.setTime(33333333L);
                    date.getTime();
                }
                long t1 = System.currentTimeMillis();
                System.out.println("直接调用耗时:" + (t1 - t) + "ms");
    
                //javassist自定义Invokers调用
                t1 = System.currentTimeMillis();
                for (int i = 0; i < TIMES; i++) {
                    setter.set(date, 33333333L);
                    getter.get(date);
                }
                long t2 = System.currentTimeMillis();
                System.out.println("javassist自定义Invokers调用耗时:" + (t2 - t1) + "ms");
    
                //JDK的Method反射调用
                t2 = System.currentTimeMillis();
                for (int i = 0; i < TIMES; i++) {
                    setTime.invoke(date, 6666666L);
                    getTime.invoke(date);
                }
                long t3 = System.currentTimeMillis();
                System.out.println("JDK的Method反射调用耗时:" + (t3 - t2) + "ms");
    
                //CGLIB的FastMethod调用
                t3 = System.currentTimeMillis();
                for (int i = 0; i < TIMES; i++) {
                    fastSetTime.invoke(date, new Object[]{6666666L});
                    fastGetTime.invoke(date, new Object[]{});
                }
                long t4 = System.currentTimeMillis();
                System.out.println("CGLIB的FastMethod调用耗时:" + (t4 - t3) + "ms");
    
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    
    
        public static void testInvoker() {
            try {
                Date date = new Date();
                Method getMethod = Date.class.getMethod("getTime");
                getMethod.setAccessible(true);
                Method setMethod = Date.class.getMethod("setTime", Long.TYPE);
                setMethod.setAccessible(true);
    
                Invokers.Invoker get = Invokers.newInvoker(getMethod);
                Invokers.Invoker set = Invokers.newInvoker(setMethod);
    
                FastClass fastClass = FastClass.create(Date.class);
                FastMethod fastGetMethod = fastClass.getMethod(getMethod);
                FastMethod fastSetMethod = fastClass.getMethod(setMethod);
    
                //System.out.println(get.invoke(date, new Object[] {}));
                set.invoke(date, new Object[] { 333333L });
                //System.out.println(get.invoke(date, new Object[] {}));
    
                //javassist自定义Invoker调用
                long t0 = System.currentTimeMillis();
                for (int i = 0; i < 100000000; i++) {
                    get.invoke(date, new Object[] {});
                    set.invoke(date, new Object[] { 333333L });
                }
                long t1 = System.currentTimeMillis();
                System.out.println("javassist自定义Invoker调用耗时:" + (t1 - t0) + "ms");
    
                //JDK的Method反射调用
                t1 = System.currentTimeMillis();
                for (int i = 0; i < 100000000; i++) {
                    getMethod.invoke(date, new Object[] {});
                    setMethod.invoke(date, new Object[] { 333333L });
                }
                long t2 = System.currentTimeMillis();
                System.out.println("JDK的Method反射调用耗时:" + (t2 - t1) + "ms");
    
                //CGLIB的FastMethod反射调用
                t2 = System.currentTimeMillis();
                for (int i = 0; i < 100000000; i++) {
                    fastGetMethod.invoke(date, new Object[] {});
                    fastSetMethod.invoke(date, new Object[] { 333333L });
                }
                long t3 = System.currentTimeMillis();
                System.out.println("CGLIB的FastMethod反射调用耗时:" + (t3 - t2) + "ms");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            testInvoke();
            System.out.println("---------------------------------");
            testInvoker();
        }
    }
    

    自定义Invokers的代码实现

    import javassist.*;
    import org.apache.commons.lang3.reflect.MethodUtils;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Arrays;
    import java.util.Map;
    import java.util.WeakHashMap;
    
    
    public class Invokers {
        /**
         * 调用器池
         */
        final private static Map<Method, Invoker> INVOKER_MAP = new WeakHashMap<Method, Invoker>();
        /**
         * 公共调用器池
         */
        final private static Map<Integer, Invoker> PUBLIC_INVOKER_MAP = new WeakHashMap<Integer, Invoker>();
    
        /**
         * 调用器接口
         */
        public static interface Invoker {
            /**
             * 获取方法本身
             *
             * @return
             */
            Method method();
    
            /**
             * 调用方法
             *
             * @param host
             *            执行对象
             * @param args
             *            执行参数
             * @return
             */
            Object invoke(Object host, Object[] args);
        }
    
        /**
         * 输出类型信息
         *
         * @param argTypes
         * @return
         */
        private static String argumentTypesToString(Class<?>[] argTypes) {
            StringBuilder buf = new StringBuilder();
            buf.append("(");
            if (argTypes != null) {
                for (int i = 0; i < argTypes.length; i++) {
                    if (i > 0) {
                        buf.append(", ");
                    }
                    Class<?> c = argTypes[i];
                    buf.append((c == null) ? "null" : c.getName());
                }
            }
            buf.append(")");
            return buf.toString();
        }
    
        /**
         * 快捷调用公共方法(性能较差)
         *
         * @param host
         *            宿主对象
         * @param name
         *            方法名
         * @param args
         *            方法参数
         * @return 执行结果
         * @throws NoSuchMethodException
         *             如果没有相应的方法
         */
        public static Object invokePublic(Object host, String name, Object... args)
                throws NoSuchMethodException {
            final Class<?> clazz = host instanceof Class ? (Class<?>) host : host
                    .getClass();
            args = args == null ? new Object[] { null } : args;
            Class<?>[] paramTypes = new Class[args.length];
            for (int i = 0; i < paramTypes.length; i++) {
                paramTypes[i] = args[i] == null ? null : args[i].getClass();
            }
            int[] keys = new int[3];
            keys[0] = clazz.hashCode();
            keys[1] = name.hashCode();
            keys[2] = Arrays.hashCode(paramTypes);
            int key = Arrays.hashCode(keys);
            Invoker invoker = PUBLIC_INVOKER_MAP.get(key);
            if (invoker == null) {
                Method method = MethodUtils.getMatchingAccessibleMethod(clazz,
                        name, paramTypes);
                if (method == null) {
                    throw new NoSuchMethodException(clazz.getName() + "." + name
                            + argumentTypesToString(paramTypes));
                }
                invoker = newInvoker(method);
                PUBLIC_INVOKER_MAP.put(key, invoker);
            }
            return invoker.invoke(host, args);
        }
    
        /**
         * 根据传入的方法创建快速调用器。 比cglib的性能快4到40倍之间。
         *
         * @param method
         *            方法对象
         * @return 调用器
         */
        public static Invoker newInvoker(Method method) {
            Invoker invoker = INVOKER_MAP.get(method);
            if (invoker == null) {
                StringBuilder proxyClassNameBuilder = new StringBuilder();
                proxyClassNameBuilder.append("proxy.invoker.method$");
                proxyClassNameBuilder.append(method.hashCode());
                String proxyClassName = proxyClassNameBuilder.toString();
                try {
                    Class<?> proxyClass;
                    try {
                        proxyClass = Class.forName(proxyClassName);
                    } catch (Throwable e) {
                        ClassPool cp = new ClassPool(true);
                        CtClass cc = cp.makeClass(proxyClassName);
                        cc.addField(CtField.make(
                                "private java.lang.reflect.Method m;", cc));
                        CtConstructor ctConstructor = new CtConstructor(
                                new CtClass[] { cp.get(Method.class.getName()) },
                                cc);
                        ctConstructor
                                .setBody("{this.m=(java.lang.reflect.Method)$1;}");
                        cc.addConstructor(ctConstructor);
                        cc.addInterface(cp.get(Invoker.class.getName()));
                        cc.addMethod(CtMethod
                                .make("public java.lang.reflect.Method method(){return m;}",
                                        cc));
                        StringBuilder invokeCode = new StringBuilder();
                        invokeCode
                                .append("public Object invoke(Object host, Object[] args){");
                        StringBuilder parameterCode = new StringBuilder();
                        for (int i = 0; i < method.getParameterTypes().length; i++) {
                            if (i > 0) {
                                parameterCode.append(",");
                            }
                            Class<?> parameterType = method.getParameterTypes()[i];
                            parameterCode.append(generateCast("args[" + i + "]",
                                    Object.class, parameterType));
                        }
                        if (method.getParameterTypes().length > 0) {
                            invokeCode.append("if(args==null||args.length!=");
                            invokeCode.append(method.getParameterTypes().length);
                            invokeCode
                                    .append(")throw new IllegalArgumentException(\"wrong number of arguments\");");
                        }
    
                        StringBuilder executeCode = new StringBuilder();
    
                        executeCode.append("((");
                        executeCode.append(method.getDeclaringClass()
                                .getCanonicalName());
                        executeCode.append(")");
                        String objCode = Modifier.isStatic(method.getModifiers()) ? ""
                                : "host";
                        executeCode.append(objCode);
                        executeCode.append(").");
                        executeCode.append(method.getName());
                        executeCode.append("(");
                        executeCode.append(parameterCode);
                        executeCode.append(")");
    
                        if (!method.getReturnType().equals(Void.TYPE)) {
                            invokeCode.append("return ");
                            invokeCode.append(generateCast(executeCode.toString(),
                                    method.getReturnType(), Object.class));
                            invokeCode.append(";");
                        } else {
                            invokeCode.append(executeCode.toString());
                            invokeCode.append(";return null;");
                        }
                        invokeCode.append("}");
                        cc.addMethod(CtMethod.make(invokeCode.toString(), cc));
                        proxyClass = cc.toClass();
                    }
                    invoker = (Invoker) proxyClass.getConstructor(Method.class)
                            .newInstance(method);
                    INVOKER_MAP.put(method, invoker);
                } catch (Throwable e) {
                    if (e instanceof RuntimeException) {
                        throw (RuntimeException) e;
                    }
                    throw new RuntimeException(e);
                }
            }
            return invoker;
        }
    
        /**
         * 快速动态调用宿主方法。 如果指定了方法名,则执行方法时只会调用指定了的方法。 <br/>
         * 如果没有指定方法名,则调用宿主中对应接口类的同名方法。
         *
         * @param superClass
         *            接口类
         * @param hostClass
         *            宿主类
         * @param methodName
         *            宿主方法名(可选)
         * @param hostMethodParameterTypes
         *            宿主方法参数(可选)
         * @param hostMethodReturnType
         *            宿主方法返回类型(可选)
         * @return 代理实例
         */
        @SuppressWarnings("unchecked")
        public static <T> T newInvoker(Class<T> superClass, Class<?> hostClass,
                                       String methodName, Class<?>[] hostMethodParameterTypes,
                                       Class<?> hostMethodReturnType) {
            try {
                methodName = methodName == null ? null : methodName.trim();
                StringBuilder proxyClassNameBuilder = new StringBuilder();
                proxyClassNameBuilder.append("proxy.invoker$");
                proxyClassNameBuilder.append(superClass.hashCode() + 10000000000L);
                proxyClassNameBuilder.append("$");
                proxyClassNameBuilder.append(hostClass.hashCode() + 10000000000L);
                proxyClassNameBuilder.append("$");
                if (methodName != null && !methodName.equals("")) {
                    proxyClassNameBuilder.append(methodName);
                }
                proxyClassNameBuilder.append("$");
                if (hostMethodParameterTypes != null
                        && hostMethodParameterTypes.length > 0) {
                    proxyClassNameBuilder.append(10000000000L + Arrays
                            .hashCode(hostMethodParameterTypes));
                }
                proxyClassNameBuilder.append("$");
                if (hostMethodReturnType != null) {
                    proxyClassNameBuilder
                            .append(10000000000L + hostMethodReturnType.hashCode());
                }
                String proxyClassName = proxyClassNameBuilder.toString();
                Class<?> proxyClass;
                try {
                    proxyClass = Class.forName(proxyClassName);
                } catch (Exception ex) {
                    ClassPool cp = new ClassPool(true);
                    CtClass cc = cp.makeClass(proxyClassName);
                    if (superClass.isInterface()) {
                        cc.addInterface(cp.get(superClass.getName()));
                    } else {
                        cc.setSuperclass(cp.get(superClass.getName()));
                    }
                    Method[] methods = superClass.getMethods();
                    for (Method method : methods) {
                        int mod = method.getModifiers();
                        if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
                            continue;
                        }
                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length < 1
                                || (!hostClass.isAssignableFrom(parameterTypes[0]) && !parameterTypes[0]
                                .isAssignableFrom(hostClass))) {
                            throw new IllegalArgumentException(
                                    "The first argument is not a host instance");
                        }
                        if (hostMethodParameterTypes != null
                                && hostMethodParameterTypes.length != parameterTypes.length - 1) {
                            throw new IllegalArgumentException(
                                    String.format(
                                            "The host method parameter types'number should be %d",
                                            parameterTypes.length - 1));
                        }
                        Class<?> returnType = method.getReturnType();
                        StringBuilder methodCode = new StringBuilder();
                        StringBuilder paramCode = new StringBuilder();
                        methodCode.append("public ");
                        methodCode.append(returnType.getCanonicalName());
                        methodCode.append(" ");
                        methodCode.append(method.getName());
                        methodCode.append("(");
                        for (int i = 0; i < parameterTypes.length; i++) {
                            String canonicalName = parameterTypes[i]
                                    .getCanonicalName();
                            if (i > 0) {
                                methodCode.append(",");
                                if (i > 1) {
                                    paramCode.append(",");
                                }
    
                                if (hostMethodParameterTypes != null) {
                                    String param = generateCast("p" + i,
                                            parameterTypes[i],
                                            hostMethodParameterTypes[i - 1]);
                                    paramCode.append(param);
                                } else {
                                    String param = generateCast("p" + i,
                                            parameterTypes[i],
                                            parameterTypes[i - 1]);
                                    paramCode.append(param);
                                }
                            }
                            methodCode.append(canonicalName);
                            methodCode.append(" p");
                            methodCode.append(i);
                        }
                        methodCode.append("){");
                        StringBuilder executeCode = new StringBuilder();
                        executeCode.append("((");
                        executeCode.append(hostClass.getCanonicalName());
                        executeCode.append(")p0).");
                        if (methodName == null) {
                            executeCode.append(method.getName());
                        } else {
                            executeCode.append(methodName);
                        }
                        executeCode.append("(");
                        executeCode.append(paramCode);
                        executeCode.append(")");
                        if (!returnType.equals(Void.TYPE)) {
                            methodCode.append("return ");
                            hostMethodReturnType = hostMethodReturnType == null ? returnType
                                    : hostMethodReturnType;
                            String returnCode = generateCast(
                                    executeCode.toString(), hostMethodReturnType,
                                    returnType);
                            methodCode.append(returnCode);
                        } else {
                            methodCode.append(executeCode);
                        }
                        methodCode.append(";");
                        methodCode.append("}");
                        cc.addMethod(CtMethod.make(methodCode.toString(), cc));
                    }
                    proxyClass = cc.toClass();
                }
                return (T) proxyClass.newInstance();
            } catch (Exception e) {
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                }
                throw new RuntimeException(e);
            }
        }
    
        private static String generateCast(String arg, Class<?> fromClass,
                                           Class<?> toClass) {
            StringBuilder cast = new StringBuilder();
            if (fromClass.isPrimitive() && !toClass.isPrimitive()) {
                Class<?> wraperClass = toClass;
                if (!isWraper(toClass)) {
                    wraperClass = getWraper(fromClass);
                }
                cast.append("(");
                cast.append(toClass.getCanonicalName());
                cast.append(")");
                cast.append(wraperClass.getCanonicalName());
                cast.append(".valueOf((");
                cast.append(getPrimitive(wraperClass).getCanonicalName());
                cast.append(")");
                cast.append(arg);
                cast.append(")");
            } else if (!fromClass.isPrimitive() && toClass.isPrimitive()) {
                cast.append("(");
                cast.append(toClass.getCanonicalName());
                cast.append(")");
                Class<?> wraperClass = fromClass;
                if (!isWraper(fromClass)) {
                    wraperClass = getWraper(toClass);
                    cast.append("((");
                    if (Number.class.isAssignableFrom(wraperClass)) {
                        cast.append(Number.class.getCanonicalName());
                    } else {
                        cast.append(wraperClass.getCanonicalName());
                    }
                    cast.append(")");
                    cast.append(arg);
                    cast.append(")");
                } else {
                    cast.append(arg);
                }
                cast.append(".");
                cast.append(getPrimitive(wraperClass).getCanonicalName());
                cast.append("Value()");
            } else {
                cast.append("(");
                cast.append(toClass.getCanonicalName());
                cast.append(")");
                cast.append(arg);
            }
            return cast.toString();
        }
    
        private static Class<?> getPrimitive(Class<?> wraperClass) {
            if (wraperClass.equals(Integer.class)) {
                return Integer.TYPE;
            }
            if (wraperClass.equals(Short.class)) {
                return Short.TYPE;
            }
            if (wraperClass.equals(Long.class)) {
                return Long.TYPE;
            }
            if (wraperClass.equals(Float.class)) {
                return Float.TYPE;
            }
            if (wraperClass.equals(Double.class)) {
                return Double.TYPE;
            }
            if (wraperClass.equals(Byte.class)) {
                return Byte.TYPE;
            }
            if (wraperClass.equals(Character.class)) {
                return Character.TYPE;
            }
            if (wraperClass.equals(Boolean.class)) {
                return Boolean.TYPE;
            }
            if (wraperClass.equals(Void.class)) {
                return Void.TYPE;
            }
            return wraperClass;
        }
    
        private static Class<?> getWraper(Class<?> toClass) {
            if (toClass.equals(Integer.TYPE)) {
                return Integer.class;
            }
            if (toClass.equals(Short.TYPE)) {
                return Short.class;
            }
            if (toClass.equals(Long.TYPE)) {
                return Long.class;
            }
            if (toClass.equals(Float.TYPE)) {
                return Float.class;
            }
            if (toClass.equals(Double.TYPE)) {
                return Double.class;
            }
            if (toClass.equals(Byte.TYPE)) {
                return Byte.class;
            }
            if (toClass.equals(Character.TYPE)) {
                return Character.class;
            }
            if (toClass.equals(Boolean.TYPE)) {
                return Boolean.class;
            }
            if (toClass.equals(Void.TYPE)) {
                return Void.class;
            }
            return toClass;
        }
    
        private static boolean isWraper(Class<?> toClass) {
            if (toClass.equals(Integer.class)) {
                return true;
            }
            if (toClass.equals(Short.class)) {
                return true;
            }
            if (toClass.equals(Long.class)) {
                return true;
            }
            if (toClass.equals(Float.class)) {
                return true;
            }
            if (toClass.equals(Double.class)) {
                return true;
            }
            if (toClass.equals(Byte.class)) {
                return true;
            }
            if (toClass.equals(Character.class)) {
                return true;
            }
            if (toClass.equals(Boolean.class)) {
                return true;
            }
            if (toClass.equals(Void.class)) {
                return true;
            }
            return false;
        }
    
        /**
         * 快速动态调用宿主方法。 如果指定了方法名,则执行方法时只会调用指定了的方法。 <br/>
         * 如果没有指定方法名,则调用宿主中对应接口类的同名方法。
         *
         * @param superClass
         *            接口类
         * @param hostClass
         *            宿主类
         * @param methodName
         *            方法名(可选)
         * @return 代理实例
         */
        public static <T> T newInvoker(Class<T> superClass, Class<?> hostClass,
                                       String methodName) {
            return newInvoker(superClass, hostClass, methodName, null, null);
        }
    
        /**
         * 快速动态调用宿主方法。调用宿主中对应接口类的同名方法。
         *
         * @param superClass
         *            接口类
         * @param hostClass
         *            宿主类
         * @return 代理实例
         */
        public static <T> T newInvoker(Class<T> superClass, Class<?> hostClass) {
            return newInvoker(superClass, hostClass, null);
        }
    }
    

    参考资料:http://www.iteye.com/topic/1129340

一次关于缓存的面试

引言

我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案,可是他们不知道缓存是怎么构建的,或者没有告诉你应该采用什么标准去选择缓存框架。在这边文章,我们会去讨论缓存,缓存算法,缓存框架以及哪个缓存框架会更好。

面试

“缓存就是存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些。”

这就是 programmer one (programmer one 是一个面试者)在面试中的回答(一个月前,他向公司提交了简历,想要应聘要求在缓存,缓存框架,大规模数据操作有着丰富经验的 java 开发职位)。

programmer one 通过 hash table 实现了他自己的缓存,但是他知道的只是他的缓存和他那存储着150条记录的 hash table,这就是他认为的大规模数据(缓存 = hashtable,只需要在 hash table 查找就好了),所以,让我们来看看面试的过程吧。

面试官:你选择的缓存方案,是基于什么标准的?

programmer one:呃,(想了5分钟)嗯,基于,基于,基于数据(咳嗽……)

面试官:excese me ! 能不能重复一下?

programmer one:数据?!

面试官:好的。说说几种缓存算法以及它们的作用

programmer one:(凝视着面试官,脸上露出了很奇怪的表情,没有人知道原来人类可以做出这种表情  )

面试官:好吧,那我换个说法,当缓存达到容量时,会怎么做?

programmer one:容量?嗯(思考……hash table 的容量时没有限制的,我能任意增加条目,它会自动扩充容量的)(这是 programmer one 的想法,但是他没有说出来)

面试官对 programmer one 表示感谢(面试过程持续了10分钟),之后一个女士走过来说:谢谢你的时间,我们会给你打电话的,祝你好心情。这是 programmer one 最糟糕的面试(他没有看到招聘对求职者有丰富的缓存经验背景要求,实际上,他只看到了丰厚的报酬  )。

说到做到

programmer one 离开之后,他想要知道这个面试者说的问题和答案,所以他上网去查,programmer one 对缓存一无所知,除了:当我需要缓存的时候,我就会用 hash table。

在他使用了他最爱的搜索引擎搜索之后,他找到了一篇很不错的关于缓存文章,并且开始去阅读……

为什么我们需要缓存?

很久很久以前,在还没有缓存的时候……用户经常是去请求一个对象,而这个对象是从数据库去取,然后,这个对象变得越来越大,这个用户每次的请求时间也越来越长了,这也把数据库弄得很痛苦,他无时不刻不在工作。所以,这个事情就把用户和数据库弄得很生气,接着就有可能发生下面两件事情:

1.用户很烦,在抱怨,甚至不去用这个应用了(这是大多数情况下都会发生的)

2.数据库为打包回家,离开这个应用,然后,就出现了大麻烦(没地方去存储数据了)(发生在极少数情况下)

上帝派来了缓存

在几年之后,IBM(60年代)的研究人员引进了一个新概念,它叫“缓存”。

什么是缓存?

正如开篇所讲,缓存是“存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些。”

缓存可以认为是数据的池,这些数据是从数据库里的真实数据复制出来的,并且为了能别取回,被标上了标签(键 ID)。太棒了

programmer one 已经知道这点了,但是他还不知道下面的缓存术语。

命中:

当客户发起一个请求(我们说他想要查看一个产品信息),我们的应用接受这个请求,并且如果是在第一次检查缓存的时候,需要去数据库读取产品信息。

如果在缓存中,一个条目通过一个标记被找到了,这个条目就会被使用、我们就叫它缓存命中。所以,命中率也就不难理解了。

Cache Miss:

但是这里需要注意两点:

1. 如果还有缓存的空间,那么,没有命中的对象会被存储到缓存中来。

2. 如果缓存慢了,而又没有命中缓存,那么就会按照某一种策略,把缓存中的旧对象踢出,而把新的对象加入缓存池。而这些策略统称为替代策略(缓存算法),这些策略会决定到底应该提出哪些对象。

存储成本:

当没有命中时,我们会从数据库取出数据,然后放入缓存。而把这个数据放入缓存所需要的时间和空间,就是存储成本。

索引成本:

和存储成本相仿。

失效:

当存在缓存中的数据需要更新时,就意味着缓存中的这个数据失效了。

替代策略:

当缓存没有命中时,并且缓存容量已经满了,就需要在缓存中踢出一个老的条目,加入一条新的条目,而到底应该踢出什么条目,就由替代策略决定。

最优替代策略:

最优的替代策略就是想把缓存中最没用的条目给踢出去,但是未来是不能够被预知的,所以这种策略是不可能实现的。但是有很多策略,都是朝着这个目前去努力。

Java 街恶梦:

当 programmer one 在读这篇文章的时候,他睡着了,并且做了个恶梦(每个人都有做恶梦的时候)。

programmer one:nihahha,我要把你弄失效!(疯狂的状态)

缓存对象:别别,让我活着,他们还需要我,我还有孩子。

programmer one:每个缓存对象在失效之前都会那样说。你从什么时候开始有孩子的?不用担心,现在就永远消失吧!

哈哈哈哈哈……programmer one 恐怖的笑着,但是警笛打破了沉静,警察把 programmer one 抓了起来,并且控告他杀死了(失效)一个仍需被使用的缓存对象,他被押到了监狱。

programmer one 突然醒了,他被吓到了,浑身是汗,他开始环顾四周,发现这确实是个梦,然后赶紧继续阅读这篇文章,努力的消除自己的恐慌。

在programmer one 醒来之后,他又开始阅读文章了。

缓存算法

没有人能说清哪种缓存算法优于其他的缓存算法

Least Frequently Used(LFU):

大家好,我是 LFU,我会计算为每个缓存对象计算他们被使用的频率。我会把最不常用的缓存对象踢走。

Least Recently User(LRU):

我是 LRU 缓存算法,我把最近最少使用的缓存对象给踢走。

我总是需要去了解在什么时候,用了哪个缓存对象。如果有人想要了解我为什么总能把最近最少使用的对象踢掉,是非常困难的。

浏览器就是使用了我(LRU)作为缓存算法。新的对象会被放在缓存的顶部,当缓存达到了容量极限,我会把底部的对象踢走,而技巧就是:我会把最新被访问的缓存对象,放到缓存池的顶部。

所以,经常被读取的缓存对象就会一直呆在缓存池中。有两种方法可以实现我,array 或者是 linked list。

我的速度很快,我也可以被数据访问模式适配。我有一个大家庭,他们都可以完善我,甚至做的比我更好(我确实有时会嫉妒,但是没关系)。我家庭的一些成员包括 LRU2 和 2Q,他们就是为了完善 LRU 而存在的。

Least Recently Used 2(LRU2):

我是 Least Recently Used 2,有人叫我最近最少使用 twice,我更喜欢这个叫法。我会把被两次访问过的对象放入缓存池,当缓存池满了之后,我会把有两次最少使用的缓存对象踢走。因为需要跟踪对象2次,访问负载就会随着缓存池的增加而增加。如果把我用在大容量的缓存池中,就会有问题。另外,我还需要跟踪那么不在缓存的对象,因为他们还没有被第二次读取。我比LRU好,而且是 adoptive to access 模式 。

Two Queues(2Q):

我是 Two Queues;我把被访问的数据放到 LRU 的缓存中,如果这个对象再一次被访问,我就把他转移到第二个、更大的 LRU 缓存。

我踢走缓存对象是为了保持第一个缓存池是第二个缓存池的1/3。当缓存的访问负载是固定的时候,把 LRU 换成 LRU2,就比增加缓存的容量更好。这种机制使得我比 LRU2 更好,我也是 LRU 家族中的一员,而且是 adoptive to access 模式 。

Adaptive Replacement Cache(ARC):

我是 ARC,有人说我是介于 LRU 和 LFU 之间,为了提高效果,我是由2个 LRU 组成,第一个,也就是 L1,包含的条目是最近只被使用过一次的,而第二个 LRU,也就是 L2,包含的是最近被使用过两次的条目。因此, L1 放的是新的对象,而 L2 放的是常用的对象。所以,别人才会认为我是介于 LRU 和 LFU 之间的,不过没关系,我不介意。

我被认为是性能最好的缓存算法之一,能够自调,并且是低负载的。我也保存着历史对象,这样,我就可以记住那些被移除的对象,同时,也让我可以看到被移除的对象是否可以留下,取而代之的是踢走别的对象。我的记忆力很差,但是我很快,适用性也强。

Most Recently Used(MRU):

我是 MRU,和 LRU 是对应的。我会移除最近最多被使用的对象,你一定会问我为什么。好吧,让我告诉你,当一次访问过来的时候,有些事情是无法预测的,并且在缓存系统中找出最少最近使用的对象是一项时间复杂度非常高的运算,这就是为什么我是最好的选择。

我是数据库内存缓存中是多么的常见!每当一次缓存记录的使用,我会把它放到栈的顶端。当栈满了的时候,你猜怎么着?我会把栈顶的对象给换成新进来的对象!

First in First out(FIFO):

我是先进先出,我是一个低负载的算法,并且对缓存对象的管理要求不高。我通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去。我很快,但是我并不适用。

Second Chance:

大家好,我是 second chance,我是通过 FIFO 修改而来的,被大家叫做 second chance 缓存算法,我比 FIFO 好的地方是我改善了 FIFO 的成本。我是 FIFO 一样也是在观察队列的前端,但是很FIFO的立刻踢出不同,我会检查即将要被踢出的对象有没有之前被使用过的标志(1一个 bit 表示),没有没有被使用过,我就把他踢出;否则,我会把这个标志位清除,然后把这个缓存对象当做新增缓存对象加入队列。你可以想象就这就像一个环队列。当我再一次在队头碰到这个对象时,由于他已经没有这个标志位了,所以我立刻就把他踢开了。我在速度上比 FIFO 快。

CLock:

我是 Clock,一个更好的 FIFO,也比 second chance 更好。因为我不会像 second chance 那样把有标志的缓存对象放到队列的尾部,但是也可以达到 second chance 的效果。

我持有一个装有缓存对象的环形列表,头指针指向列表中最老的缓存对象。当缓存 miss 发生并且没有新的缓存空间时,我会问问指针指向的缓存对象的标志位去决定我应该怎么做。如果标志是0,我会直接用新的缓存对象替代这个缓存对象;如果标志位是1,我会把头指针递增,然后重复这个过程,知道新的缓存对象能够被放入。我比 second chance 更快。

Simple time-based:

我是 simple time-based 缓存算法,我通过绝对的时间周期去失效那些缓存对象。对于新增的对象,我会保存特定的时间。我很快,但是我并不适用。

Extended time-based expiration:

我是 extended time-based expiration 缓存算法,我是通过相对时间去失效缓存对象的;对于新增的缓存对象,我会保存特定的时间,比如是每5分钟,每天的12点。

Sliding time-based expiration:

我是 sliding time-based expiration,与前面不同的是,被我管理的缓存对象的生命起点是在这个缓存的最后被访问时间算起的。我很快,但是我也不太适用。

其他的缓存算法还考虑到了下面几点:

成本:如果缓存对象有不同的成本,应该把那些难以获得的对象保存下来。

容量:如果缓存对象有不同的大小,应该把那些大的缓存对象清除,这样就可以让更多的小缓存对象进来了。

时间:一些缓存还保存着缓存的过期时间。电脑会失效他们,因为他们已经过期了。

根据缓存对象的大小而不管其他的缓存算法可能是有必要的。

 

 

感想:

随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,且技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的(或者文件的读写也是有限的),如何能够有效利用有限的资源来提供尽可能大的吞吐量?一个有效的办法就是引入缓存,打破标准流程,每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少计算量,有效提升响应速度,让有限的资源服务更多的用户。

Orika-Java Bean 映射框架

Orika

今天主要介绍一款Java Bean 映射框架—Orika

在此之前,先抛出几个问题,以便加深对orika的理解

  • Q1:什么是Java Bean 映射框架?
    • A:简单地说,就是从一个Bean A映射(转换)成另一个Bean B。举个粟子,实际开发场景中,很多时候都用到VO、BO、PO、DTO等POJO,我们需将其中一个POJO转换成另一个POJO。
  • Q2:如果要从一个Bean转换成另一个Bean,使用b.setXXX(a.getXXX()),手动转换不可以吗?
    • A:当然可以,但可以试想一下,当Bean的属性非常多(十几二十个)的时候,手动get set 不旦麻烦,还容易出错,浪费时间,纯体力劳动。
  • Q3:我听说过BeanUtils.copyProperties(a,b)也可以实现Bean属性的拷贝,它跟Orika有什么区别?
    • A:无论spring的BeanUtils.copyProperties还是apache.commons的BeanUtils.copyProperties,其底层实现机制都是运行时反射,其性能可想而知。在映射场景不频繁的场景或许还可以用一用,但是在一个频繁转换的场景,这样的性能损失,是不能接受的。

Orika是什么?

Orika是一个Java Bean映射框架,它将数据从一个对象递归拷贝到另一个对象。在开发多层应用程序时可能非常有用。其目的在于简化一个对象到另一个对象之间的映射过程。

Orika能干什么?

Orika能提供如下的功能:

  1. 映射复杂和深层结构的对象
  2. 无论是“Flatten”(扁平的)或“Expand”(深层次的)对象,都可以将嵌套属性映射到顶级属性,反之亦然。
  3. 随时快速地创建映射器,并使用自定义来控制部分或全部映射
  4. 处理代理或增强对象(如Hibernate或各种模拟框架)
  5. 通过一个配置,就可以实现双向映射
  6. 可以将任意POJO属性映射到List、Arrays、Map

Orika实现原理

Orika的设计思路就是预先通过javaassist把Java Bean之间的映射关系一次性生成目标拷贝方法代码。这样就可以避免在Bean映射环节一次次的读取映射规则。这就是Orika性能提升根本的原因。因此,理论上以生成的目标Java代码来运行映射是拷贝模式所能取到性能最大值的方法

注:Orika是我做组卷服务时候新学的一个框架,当时发现BeanUtils.copyProperties太慢了,根本不能忍。偶然的机会发现了这么一个东西,这真是刚想瞌睡就有人送上枕头。但是网上关于这个框架的资料非常少,没什么太实用的资料。可能原因有俩,一是知道的人少,二是它太简单易用了,根本就不需要详细地了解它,简单地配置后就能使用。

在此附上一个不知道几年前的参考资料:http://wangbt5191-hotmail-com.iteye.com/blog/1632444

时间有限,今天只做简单的理论介绍,下次将给出实战性的代码。

                                                                                                                           周雄

                                                                                                           搞技术,我们是认真的

浅谈对“分布式事务”的理解

  • 什么是数据库“事务”?

一句话简述一组sql操作,要么都成功执行,要么都不执行。

  • “事务”存在的意义是什么?

保证数据一致性。
eg:A转账给B 100块钱,A余额减少100 并且B余额增加100。确保不出现 A减少100 B没增加 或 A没减少 B增加的情况。


~~~~~~~~~~上面讲的啰嗦了一些基础知识 下面步入正题~~~~~~~~~~


  • 什么是“分布式事务”?

一组sql操作,分别写在不同的系统中,甚至这些sql在操作不同的数据库,在这种情况下,要么都执行成功,要么都不执行。

  • 举例描述当前问题

eg:机构开教师账号。
这个动作在系统中分为两步。
步骤1:将教师账号的业务数据写入DB。
步骤2:将开账号这个动作以及消耗爱豆数记录到流水表中,并减少机构爱豆余额。

目前步骤1由业务系统实现,步骤2由财务系统实现,是两个微服务,这时想使用事务并不是简单的一个@Transactional就能实现了。

  • 数据一致性处理方案

1.强一致性:

a.业务整合:
将开账号业务与财务扣费逻辑写在一个服务中,这也是老系统的实现逻辑,虽然保证了数据一致性 也规避了分布式事务,但把多个相关性不大的业务放在同一个系统中,业务职责不清晰,不利于维护。(极不推荐)

b.使用分布式事务 3次握手方案:
(1)询问服务方是否可执行 得到回应(yes/no)。
(2)服务方回应可执行后,发起预备执行申请,得到预备申请是否成功(true/false)。
(3)服务方回应申请成功后,发起提交申请,得到提交申请是否成功(true/false)。

3次握手
优点:保证数据强一致性,执行成功后立即查询,即可获得正确数据。

缺点:这种实现方式会造成代码量庞大,耦合性高。而且非常有局限性,因为有很多的业务是无法很简单的实现回滚的,如果串行的服务很多,回滚的成本实在太高。

2.终一致性:

MQ消息队列

(由于本人对此方案了解较少,并且远没有达到对网上的技术“一看就懂”的地步,所以此方案的原理下周再续。)

技术与方案参考地址:

http://www.cnblogs.com/dinglang/p/5679542.html

http://blog.csdn.net/bluishglc/article/details/7612811

浅谈java设计模式

一、设计模式的分类

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:

 

 

二、设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

 

个人见解:在开发一些通用型工具时,或者一些特殊的业务场景,用一些设计模式会大大提高可扩展性和高可用性,通过复用已经公认的设计,我能够在解决问题时取得先发优势,而且避免重蹈前人覆辙。我可以从学习他人的经验中获益,用不着为那些总是会重复出现的问题再次设计解决方案了。设计模式还为我们提供了观察问题、设计过程和面向对象的更高层次的视角,这将使我们从“过早处理细节”的桎梏中解放出来。大多数设计模式还能使软件更容易修改和维护。其原因在于,它们都是久经考验的解决方案。所以,它们的结构都是经过长期发展形成的,比新构思的解决方案更善于应对变化。而且,这些模式所用代码往往更易于理解,从而使代码更易维护。

java内存区域介绍

1、什么是JDK、JRE、JVM

JVM(Java Virtual Machine):主要功能就把字节码解释成具体平台上的机器指令执行,目的是实现跨平台,一次编译,到处运行。
JRE(Java Runtime Environment):支持Java程序运行的标准环境,Java SE API + JVM。
JDK(Java Development Kit):java程序设计语言 + java虚拟机 + java API类库。
 lip_image002

2、Java内存区域

Jvm在执行java程序的过程中会把它所管理的内存划分成若干个不同的数据区域,他们有各自的用途和生命周期,如下图
ip_image004

程序计数器

它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变计算器的值里获取下一条要执行的指令的,分支、循环、跳转、异常处理等等都是依赖计数器来完成的。Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
ip_image006 ip_image008
  1. 局部变量表:是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量
  1. 操作数栈:JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,JVM会建立一个操作数栈,以供计算使用。和局部变量一样。操作数栈的最大深度也是编译的时候写入到方法表的code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long、double。
  1. 动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
  1. 返回地址:当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。     无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等
  1. 附加信息:虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。
JAVA虚拟机规范中,对这个区域规定了两种异常状况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2、如果虚拟机可动态扩展,如果扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。
如递归方法层级过多就会出现StackOverflowError

本地方法栈

本地方法栈( Native Method Stack) 与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和 OutOfMemoryError 异常。

Java堆

对于大多数应用来说,Java堆( Java Heap)是Java 虚拟机所管理的内存中最大的一块。 Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述 是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、 标量替换优化技术将会导致一些微妙的变化发生, 所有的对象都分配在堆上也渐渐变得不是那么“ 绝对”了。
Java 堆 是 垃圾 收集 器 管理 的 主要 区域, 因此 很多 时候 也 被 称做“ GC 堆” 从内 存 回收 的 角度 来看, 由于现在收集器基本都采用分代收算法, 所以 Java 堆 中 还可以细分为:新生代和老年代;再细致一点的有Eden空间、 From Survivor空间、 To Survivor 空间等。从内存分配的角度来看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区( Thread Local Allocation Buffer, TLAB)。不过无论如何划分,都与存放内容无关,无论 哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快 地分配内存。
根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是 连续的即可,就像我们的磁盘空间一样。 在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的( 通过- Xmx 和- Xms 控制)。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

方法区( Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时将抛出 OutOfMemoryError 异常。
运行时常量池( Runtime Constant Pool)是方法区的一部分。 Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外,还有一项信息是常量池( Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量 池中存放。运行期间也可能将新的常量放入池中,如 String 类的intern() 方法。

直接内存

直接 内存( Direct Memory) 并不是 虚拟 机 运行时 数据区 的 一部分, 也不 是 Java 虚拟 机 规范 中 定义 的 内存 区域。在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入 了 一种 基于 通道( Channel) 与 缓冲区( Buffer) 的 I/ O 方式, 它可 以 使用 Native 函数 库 直接 分配 堆 外 内存, 然后 通过 一个 存储 在 Java 堆 中的 DirectByteBuffer 对象 作为 这块 内存 的 引用 进行 操作。 这样 能在 一些 场景 中 显著 提高 性能, 因为 避免 了 在 Java 堆 和 Native 堆 中 来回 复制 数据。
本机 直接 内存 的 分配 不会 受到 Java 堆 大小 的 限制, 但是,既然 是 内存, 肯定 还是 会受 到 本机 总 内存( 包括 RAM 以及 SWAP 区 或者 分页 文件) 大小 以及 处理器 寻址 空间 的 限制。也可能 导致 OutOfMemoryError 异常 出现。

分布式系统事务一致性解决方案

原文链接:http://www.cnblogs.com/dinglang/p/5679542.html

我的理解:

首先传统的两阶段提交的分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。
考虑了阿里的GTS/TXC,发现使用的人太少,暂时先不用它。
考虑了阿里云ons的事务消息,发现功能不是很完善,而且实现本地事务的策略有点繁琐,担心会加重负担,所以先不用。
最后决定使用非事务消息,将本地业务和发送消息先后执行,用try catch包起来,或者用spring的本地事务注解。如果出错则执行回滚策略。可能的情况有三种:
1.操作数据库成功,向MQ中投递消息也成功。
2.操作数据库失败,不会向MQ中投递消息了。
3.操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚。
因为要保证最终一致性,因此有两个问题需要关注:
1.消息出列后,消费者对应的业务操作要执行成功。如果下游业务执行失败,消息不能失效或者丢失,要么重复执行到成功为止,要么通知人工介入。
2.尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果。
保证消息与下游业务操作一致,不丢失,主流的MQ产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制。其中阿里云的MQ可以自定义重试次数,我开发的消息中间件已经集成进来了,如果需要,可以后续加入超过重试上限后自动通知人工介入的功能。
避免消息被重复消费造成的问题,需要保证消费者调用业务的服务接口的幂等性,消息中间件已经通过全局唯一id的方式保证了幂等性,重复消息不会执行。
这种非事务消息的方式比较常见,如果MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。