• Feeds

  • Archive for the ‘Java’ Category


    使用脚本引擎增加程序运行时动态执行能力(Java篇)

    对于非Web的后台服务程序,经常会碰到这样的需求:

    1. 动态改变程序运行时参数的能力。如Config.limitValue = 50
    2. 动态查看运行时候变量状态的能力,如 print(userMap.size())
    3. 执行代码的能力,如 userMap.clear()

    对于需求1,非Web的程序没法像PHP/JSP那样直接改就生效,往往改了某个值,即使是一个配置参数都需要服务器重启一下。这对于很多线上服务来说成本太高。

    对于上述2的需求,通常是编写庞大的后台管理程序来满足。或者是通过增加日志输出,但问题是某些变量只需要偶尔才看一下,因此很多系统中大部分日志开销基本上是无用的。

    对于需求3,只有通过在IDE的debug模式下才能达到。

    我觉得所有的后台程序都应该具备这样的能力,为了满足以上需求,于是考虑在所有程序里面嵌入一个小型的解释脚本引擎来实现。就像很多游戏程序嵌入Lua/Python来动态执行代码目的一样。

    今天先说下Java中的实现方法,几年前用过BeanShell, 可以在Java VM里面解释执行多种类型脚本语言。但是发现已经多年不更新,原来是Java 6已经内建Scripting的支持,所以这些第三方的工具也都结束了历史使命。

    Java 6自带的实际上是 Mozilla Rhino 的Java Script引擎,它使用JavaScript的语法,可以调用任意Java代码。更多介绍见Java Scripting Programmer’s Guide. Rhino除了import写法有点特殊外,基本语法和Java代码基本一致。如:

    engine.eval("importClass(java.lang.System)");
    engine.eval("println(System.currentTimeMillis())");
    engine.eval("println('Source: http://timyang.net/java/java-scriptingjava-scripting/')");

    于是做了一个简单的socket服务器,把发过来的文本直接执行,然后再把脚本执行产生的内容返回给发送方。目标达成。共40余行代码(包括注释和花括号),无需任何第三方library。代码如下:

    // create a script engine manager
    ScriptEngineManager factory = new ScriptEngineManager();
    // create a JavaScript engine
    ScriptEngine engine = factory.getEngineByName("JavaScript");
    
    while (true) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(4444, 50, InetAddress.getByName("localhost"));
        } catch (IOException e) {
            System.err.println("Could not listen on port: 4444.");
            System.exit(1);
        }
    
        Socket clientSocket = null;
        try {
            clientSocket = serverSocket.accept();
        } catch (IOException e) {
            System.err.println("Accept failed.");
            System.exit(1);
        }
    
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(),
                true);
        BufferedReader in = new BufferedReader(new InputStreamReader(
                clientSocket.getInputStream()));
        String inputLine, outputLine;
    
        while ((inputLine = in.readLine()) != null) {
            if ("quit".equals(inputLine))
                    break;
            try {
                // evaluate JavaScript code from String
                out.println(engine.eval(inputLine));
            } catch (Exception e) {
                out.println("error:" + e.getMessage());
            }
        }
        out.close();
        in.close();
        clientSocket.close();
        serverSocket.close();
    }

    启动程序之后只要 telnet localhost 4444 就可以执行任意代码并实时查看返回结果。

    注意上面为了安全起见,只绑定了localhost地址,防止此功能被外部用户恶意使用。

    Java垃圾回收调优

    在Java中,通常通讯类型的服务器对GC(Garbage Collection)比较敏感。通常通讯服务器每秒需要处理大量进出的数据包,需要解析,分解成不同的业务逻辑对象并做相关的业务处理,这样会导致大量的临时对象被创建和回收。同时服务器如果需要同时保存用户状态的话,又会产生很多永久的对象,比如用户session。业务越复杂的应用往往用户session包含的引用对象就越多。这样在极端情况下会发生两件事情,long gc pause time 或 out of memory。

    一,要解决long pause time首先要了解JVM中heap的结构

    java gc heap

    java gc heap

    • Java Heap为什么要分成几个不同的代(generation)? 由于80%-98%的对象的生存周期很短,大部分新对象存放在young generation可以很高效的回收,避免遍历所有对象。
    • young与old中内存分配的算法完全不同。young generation中由于存活的很少,要mark, sweep 然后再 compact 剩余的对象比较耗时,干脆把 live object copy 到另外一个空间更高效。old generation完全相反,里面的 live object 变化较少。因此采用 mark-sweep-compact更合适。

    二,Java中四种垃圾回收算法

    Java中有四种不同的回收算法,对应的启动参数为
    –XX:+UseSerialGC
    –XX:+UseParallelGC
    –XX:+UseParallelOldGC
    –XX:+UseConcMarkSweepGC

    1. Serial Collector
    大部分平台或者强制 java -client 默认会使用这种。
    young generation算法 = serial
    old generation算法 = serial (mark-sweep-compact)
    这种方法的缺点很明显,stop-the-world, 速度慢。服务器应用不推荐使用。

    2. Parallel Collector
    在linux x64上默认是这种,其他平台要加 java -server 参数才会默认选用这种。
    young = parallel,多个thread同时copy
    old = mark-sweep-compact = 1
    优点:新生代回收更快。因为系统大部分时间做的gc都是新生代的,这样提高了throughput(cpu用于非gc时间)
    缺点:当运行在8G/16G server上old generation live object太多时候pause time过长

    3. Parallel Compact Collector (ParallelOld)
    young = parallel = 2
    old = parallel,分成多个独立的单元,如果单元中live object少则回收,多则跳过
    优点:old old generation上性能较 parallel 方式有提高
    缺点:大部分server系统old generation内存占用会达到60%-80%, 没有那么多理想的单元live object很少方便迅速回收,同时compact方面开销比起parallel并没明显减少。

    4. Concurent Mark-Sweep(CMS) Collector
    young generation = parallel collector = 2
    old = cms
    同时不做 compact 操作。
    优点:pause time会降低, pause敏感但CPU有空闲的场景需要建议使用策略4.
    缺点:cpu占用过多,cpu密集型服务器不适合。另外碎片太多,每个object的存储都要通过链表连续跳n个地方,空间浪费问题也会增大。

    几条经验:
    1. java -server
    2. 设置Xms=Xmx=3/4物理内存
    3. 如果是CPU密集型服务器,使用–XX:+UseParallelOldGC, 否则–XX:+UseConcMarkSweepGC
    4. 新生代,Parallel/ParallelOld可设大于Xmx1/4,CMS可设小,小于Xmx1/4
    5. 优化程序,特别是每个用户的session中的集合类等。我们的一个模块中session中曾经为每个用户使用了一个ConcurrentHashMap, 里面通常只有几条记录,后来改成数组之后,每台机大概节约了1~2G内存。

    不过总的说来,Java的GC算法感觉是业界最成熟的,目前很多其他语言或者框架也都支持GC了,但大多数都是只达到Java Serial gc这种层面,甚至分generation都未考虑。JDK7里面针对CMS又进行了一种改进,会采用一种G1(Garbage-First Garbage Collection)的算法。实际上Garbage-First paper(PDF) 2004年已经出来了,相信到JDK7已经可以用于严格生产环境,有时间也会进一步介绍一下G1。
    另外在今年的Sun Tech Days上Joey Shen讲的Improving Java Performance(PDF)也是一个很好的Java GC调优的入门教程。