Tag Archives: Java

java线程生命周期图

一图胜千言,不是吗?

thread_lifetime

一个有趣的类库使用统计

今天晚上闲得没事,忽然想起来统计一下一个java项目中,使用JDK原生的Lib库的情况。于是便拿最近刚做完的一个web项目来看看,结果如下:
star$ find . -name “*.java” | xargs grep “import java.*” | awk ‘{ print $2 }’|sed s/\;.*//g | sort |uniq -c | sort -nr
85 java.util.List
25 java.util.Date
25 java.io.Serializable
24 java.util.Map
23 java.util.ArrayList
22 java.util.HashMap
9 javax.servlet.http.HttpServletRequest
6 java.util.Iterator
6 java.io.IOException
4 javax.servlet.http.HttpServletResponse
4 java.text.SimpleDateFormat
4 java.lang.annotation.Target
4 java.lang.annotation.RetentionPolicy
4 java.lang.annotation.Retention
4 java.lang.annotation.ElementType
3 javax.servlet.http.HttpSession
3 java.util.Set
3 java.util.Calendar
3 java.text.DateFormat

然后再找了其它几个公司里面的web应用统计了一下,得出的结果都差不多。接着再找到开源的项目netty,统计的结果如下:
star:netty-3.2.2.Final star$ find . -name “*.java” | xargs grep “import java.*” | awk ‘{ print $2 }’|sed s/\;.*//g | sort |uniq -c | sort -nr
64 java.util.concurrent.Executor
54 java.io.IOException
53 java.net.InetSocketAddress
41 java.util.concurrent.Executors
38 java.net.SocketAddress
36 java.util.Map
32 java.nio.ByteBuffer
30 java.util.Set
29 java.util.List
23 java.util.concurrent.TimeUnit
23 java.nio.ByteOrder
23 java.io.InputStream
22 java.io.OutputStream
19 java.util.Iterator
19 java.util.ArrayList
16 java.util.logging.Level
15 java.util.logging.Logger
14 java.util.concurrent.ExecutorService
13 java.nio.channels.ScatteringByteChannel
12 java.util.Queue
12 java.nio.channels.GatheringByteChannel
11 java.util.concurrent.atomic.AtomicBoolean
11 java.util.concurrent.ConcurrentMap
11 java.util.NoSuchElementException
11 java.util.Collection
10 javax.net.ssl.SSLEngine
10 java.util.Random
10 java.nio.charset.Charset
10 java.nio.channels.Selector
10 java.net.Socket
情况有了非常明显的变化,netty是一个网络库,使用到的东西就已经完全变样了。
从上面的统计可以看出,写一般的web应用其实并不需要你了解太多JDK类库的东西,使用的大多是很常见的类。另外集合对象的使用次数,在大多数项目中都是非常普遍的。因此一门语言如果提供一套好的集合的操作api,可以大大提高使用者的效率,这方面很多动态语言就做得不错。

上面这些统计让我最欣慰的是,学习一门新的语言时,真的不需要为不了解很多类库而苦恼,大多数的工作只需要基本的库就能完成了。然后根据项目的需要,再学习一些相关的针对特定应用的库,如数据库相关库,网络相关的库等。

Java,Groovy实现python中的Decorator

昨天去参加杭州一些朋友组织的圆桌技术交流,鱼哥 @nnfish分享了一个关于python的话题
,另外一位兄弟分享了一下Vim的一些使用心得,最后升哥分享了Redhat Linux安装过程背
背后所执行的逻辑。三个分享都给我不少的收获,经常参与这样的交流是学习的最佳途径。
鱼哥的分享中提到python中的一个Decorator的特性,这个特性和我最近在看的一篇文章中
提到的有异曲同工之秒。 如下是Java的实现:
public void wrapInTransaction(Command c) throws SQLException {
setupDataInfrastructure();
try {
c.execute();
completeTransaction();
} catch (RuntimeException ex) {
rollbackTransaction();
throw ex;
} finally {
cleanUp();
}
}
public void addOrderFrom(final ShoppingCart cart, final String userName,
final Order order) throws SQLException {
wrapInTransaction(new Command() {
public void execute() throws SQLException{
add(order, userKeyBasedOn(userName));
addLineItemsFrom(cart, order.getOrderKey());
}
});
}
再让我们看看groovy是如何做的:
public class OrderDbClosure {
def wrapInTransaction(command) {
setupDataInfrastructure()
try {
command()
completeTransaction()
} catch (RuntimeException ex) {
rollbackTransaction()
throw ex
} finally {
cleanUp()
}
}
def addOrderFrom(cart, userName, order) {
wrapInTransaction {
add order, userKeyBasedOn(userName)
addLineItemsFrom cart, order.getOrderKey()
}
}
}
最近我们再来看看python中通过Decorator实现的代码:
def transaction(func):
def _wrapper(*args,**kwargs):
setupDataInfrastructure()
func(*args,**kwargs)
completeTransaction()
return _wrapper
@transaction
def addOrderFrom(cart,userName,order):
add(order,userKeyBasedOn(userName))
addLineItemsFrom(cart,order.getOrderKey())

昨天参加了杭州一些朋友组织的圆桌技术交流。鱼哥 @nnfish分享了一个关于python的话题,另外一位兄弟分享了一下Vim的一些使用心得,最后升哥分享了Redhat Linux安装过程背后所执行的逻辑,明白了自己定制Linux时的基本原则。三个分享都有收获,经常参与这样的交流是学习的最佳途径。

鱼哥的分享中提到python中的一个Decorator的特性,这个特性和我最近在看的一篇文章《组合方法与SLAP》中讲述SLAP提到的设计有异曲同工之秒。 如下是Java的实现,使用了匿名内部类来包装要执行的逻辑:

public void wrapInTransaction(Command c) throws SQLException {

    setupDataInfrastructure();

    try {
        c.execute();
        completeTransaction();

    } catch (RuntimeException ex) {
        rollbackTransaction();
        throw ex;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,final Order order) throws SQLException {

    wrapInTransaction(new Command() {
        public void execute() throws SQLException{
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });

}

再让我们看看文中提到的使用groovy是如何做的:

public class OrderDbClosure {

    def wrapInTransaction(command) {
         setupDataInfrastructure()

         try {
             command()

             completeTransaction()
         } catch (RuntimeException ex) {
             rollbackTransaction()
             throw ex
         } finally {
             cleanUp()
         }
    }

    def addOrderFrom(cart, userName, order) {

        wrapInTransaction {
            add order, userKeyBasedOn(userName)
            addLineItemsFrom cart, order.getOrderKey()
        }

    }

}

Groovy的做法明显比Java简洁了,不过对业务代码的侵入性还是存在的。

最后再来看看python中通过Decorator 实现上述设计的代码(下面代码只为阐述Decorator):

def transaction(func):
    def _wrapper(*args,**kwargs):
        setupDataInfrastructure()
        func(*args,**kwargs)
        completeTransaction()
    return _wrapper

@transaction
def addOrderFrom(cart,userName,order):
    add(order,userKeyBasedOn(userName))
    addLineItemsFrom(cart,order.getOrderKey())

也可能通过如下方法调用:

transaction(addOrderFrom(cart,userName,order))

从上面三个例子可以看出语言层提供的特性深深影响着开发者的思唯方式,也这是为什么如何成为优秀的程序员的书中提倡开发者学会多种语言。目前正努力学习Python中,之前看这的Groovy又都快忘记了,要熟悉工作之外的编程语言并不容易。

Java应用优化方法

最近负责的系统接入了监控系统,发现系统的性能很不乐观。主要问题表现在JVM内存一直在增长,系统响应时间很长。这些问题的原因有很多,比如前期根本没有仔细设计,代码中存在许多的重复逻辑,执行了许多无用逻辑。为了优化系统性能,特别是JVM内存持续增长的问题,去网上找了找资料。发现有一篇文章讲的优化性能的6步非常有用,具备非常好的操作性。

Improving Performance
step 1. You must make the application function correctly and solve the memory leak issues.
step 2. You should build a test environment that resembles your deployment environment.
step 3. You will need a way to simulate the clients accessing the application so that you will be able to measure for your chosen performance goals.
step 4. Discover the specific execution code paths that need to be changed to improve performance ofr a single user. Make one code change at a time and repeat the tests to determine if the change improved the performance.
step 5. To indentify the major bottlenecks that need to be eliminated to improve performance. Take a snapshot of each Java Thread stack trace .
What you want to do is start the client load test and check to see if any client requests are blocked by a bottleneck in the application. You will capture snapshots of the application stack traces while the client load test is submitting requests. What you will want to see in the stack traces is that each client request is performing work on behalf of the client.
step 6. Run the application under the client load test created in step 3. To see if the desired goals have been meet

step 1. You must make the application function correctly and solve the memory leak issues.

step 2. You should build a test environment that resembles your deployment environment.

step 3. You will need a way to simulate the clients accessing the application so that you will be able to measure for your chosen performance goals. [目前我只会使用httpd提供的ab命令向系统发送HTTP请求]

step 4. Discover the specific execution code paths that need to be changed to improve performance ofr a single user. Make one code change at a time and repeat the tests to determine if the change improved the performance. [个人觉得这一步非常重要,了解程序的主要执行路径,优化这部分代码]

step 5. To indentify the major bottlenecks that need to be eliminated to improve performance. Take a snapshot of each Java Thread stack trace .

What you want to do is start the client load test and check to see if any client requests are blocked by a bottleneck in the application. You will capture snapshots of the application stack traces while the client load test is submitting requests. What you will want to see in the stack traces is that each client request is performing work on behalf of the client.

[昨天试着使用Thread Dump的方式查看Thread stack trace来分析系统当前的执行情况,确实非常有效。可采取不同时间使用kill -3 pid的方式多次Dump出当前的Thread stack trace,通过对这几次dump结果的对比,可以找到系统执行最频繁的类或方法,然后对这部分进行优化。对于Thread Stack Trace,SUN有一篇专门的文章讲述这个问题。]

step 6. Run the application under the client load test created in step 3. To see if the desired goals have been meet.

具体的JVM的诊断方法SUN官方提供了一篇很好的文章,介绍了各个工具的使用方法。

http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/preface.html

初试Grovvy

因为接下来的工作中可能会用到Groovy,于是最近开始了解一下。主要看的书是Groovy Recipes,这本书非常不错。从装上到现在还不到一周,目前只写过一些简单的例子,记录一下学习过程中的体会。
由于之前学过一点Python,最近又刚系统地看了一下JavaScript语言,所以在看Groovy时感觉许多特性与它们两者非常相似。在看Groovy过程中没有遇到什么困难,因为Groovy全面支持Java API,对于Java程序员来说学习曲线很平,只要了解其中的语法,就可以开始写了。Groovy中许多的特性弥补了Java的缺憾,或者是Java中设计得不好的地方,在看的过程中也能了解到Java语言存在的问题。
与Java比较了一下,总结一下Groovy的特点:

  1. 内置的List 和Map 支持,并提供类似Python中的List及Map一样的特性,操作起来更加方便了;
  2. 支持闭包,在处理一些事情时不再需要Java中的内部类这种很Ugly的方法来模拟闭包了;
  3. 不再区分checked Exception和unchecked Exception,groovy后台处理这些这些烦人的东西,其实Java异常体系的设计一直被人诟病。
  4. 在Object这个类上实现了each方法,比Java中的For in更方便。
  5. 封装了一些Java API的类,这些些类增加了更多有趣且有用的方法。
  6. 内置 正则表达式 的支持
  7. 作为动态语言,具体动态语言的一切特征,比如说执行期才进行类型检查等
  8. Groovy在后台为field添加Setter和Getter,不再需要用IDE生成那些看起来不美观的代码了。
  9. 支持运算符重载,更容易实现DSL;
  10. Truth的判断与Javascript中类似,自动处理NullPointerException问题,不用写那些烦人的 if( xx != null)之类的判断了;
  11. GString,比Java中的String有用得多,不再需要用StringBuilder或者+号等拼装字符串了;
  12. 支持Range操作,和Python中的Range类似;
  13. 支持可选参数,和Python类似,避免了Java中的那些不必要的方法重载(如,两个方法只是参数数量不一样);
  14. 支持直接运行Shell脚本,但是对Shell的通配符支持有点难看,一种程序上可以不用学Bash那有点怪异的语法了;
  15. 与Java完全兼容,现有的Java代码在Groovy全部可用…

还有许多其它的一些东西没有列出来。总的来讲Groovy作为一门语言比Java要优秀许多,毕竟它的诞生就是为了弥补Java的不足。Groovy借鉴了其它优秀动态语言的许多优点,比如它的很多语法特性就是照抄Python和JavaScript的。这也使得它的学习曲线很平,不像Scala那些难学,这也是它比Scala更让人接受的原因之一吧。

好用的工具-Findbugs

若干天之前看了一个InfoQ的视频:10 Ways to Improve Your CodeFindbugs对代码进行分析在最近的做的这个项目中,开始尝试着用Findbugs对代码进行一些分析,发现这一个非常有用的工具.

我使用的是Findbugs的Eclipse插件,使用起来非常方便,这里是一个Findbugs插件的使用说明.http://andrei.gmxhome.de/findbugs/index.html.  Findbugs还有一些其它的版本,如命令行和GUI的方式,不过有Eclipse插件已经够方便了.Findbugs是一个基于字节码的分析工具,因此可以发现许多在浏览代码时难于发现的一些很隐晦的Bugs.从官方网站上的Manual可以找到更多的关于Findbugs的一些用法.>

在使用Findbugs的过程中,发现他生成的一些Bug描述不太明白,比如 “Dead store to local variable”就不明白是怎么回事 在查看了一下这个项目的官方主页,发现在这里有专门的一个章节对这些Bug描述的说明. http://findbugs.sourceforge.net/bugDescriptions.html 这个列表基本上把常见的Bug都列举出来了.这个列表里面包括了许多非常典型的错误,也有一些常常在写代码中容易被忽视的问题. 比如”Dead store to local variable”这个Bug在最近的这个项目每个人写的代码中都出现过. 长期使用FindBugs对于代码质量的提升肯定是有益的.

虽然Findbugs这么好用,但是最近做的项目中并没有完全发挥出它的能力, 一直在赶代码,导致开始测试时,才能记起用它来查一下代码的质量, 其中许多的Bug是通过服务器测试时发现并解决的.如果能使用Findbugs进行一下分析,就不会浪费那些查Bugs的时间了. 接下来其它的项目中,要好好利用一下这个利器了.

Java Puzzle (4) shutdown hook

Puzzle 39
public class HelloGoodbye{
try{
System.out.println(“Hello World”);
System.exit(0);
} finally {
System.out.println(“Goodbye World”);
}
}
上面这段代码会打印出什么呢? 要知道答案则需要了解System.exit(0)这个方法在执行过程中到底发生了哪些事情。System.exit方法会导致当前运行线程停止并使其它所以线程都终止,因此finally这代码不会被执行。
System.exit被调用后,在虚拟机在退出前会执行两个清除任务。第一,它会执行所以通过Runtime.addShutdownHook注册的shutdown hooks.它能有效的释放JVM之外的资源。第二个清除任务是运行相关的finalizer方法。
如果需要在调用System.exit方法后仍然打印出”Goodbye World”,可采用如下的方法:
System.out.println(“Hello World”);
Runtime.getRuntime().addShutdownHook(
new Thread(){
public void run(){
System.out.println(“Goodbye world”);
}
}
)
System.exit(0);
前段时间在看Yurii在和篇文章中提到shutdown hook,当时不知道这是干什么的,也没深究,通过这个Puzzle终于明白了。其实shutdown hook可以做许多的事情,在JVM使用到外部资源时都可以保证JVM在退出时正确的释放了这些资源。最极端的变量数据库连接了,以前通过sql plus连上Oralce后,sqlplus不正常退出后,Oracle上面那个链接并不会关闭。不知道使用Java的数据库线程池在JVM崩溃时会不会有这问题。总之如Yurii所说:在编写Java服务程序的时候,记得设置退出的钩子函数(RunTime.getRunTime.addShutdownHook)是一个非常好的习惯。

Puzzle 39

public class HelloGoodbye{

try{

System.out.println(“Hello World”);

System.exit(0);

} finally {

System.out.println(“Goodbye World”);

}

}

上面这段代码会打印出什么呢? 要知道答案则需要了解System.exit(0)这个方法在执行过程中到底发生了哪些事情。System.exit方法会导致当前运行线程停止并使其它所以线程都终止,因此finally这代码不会被执行。

System.exit被调用后,在虚拟机在退出前会执行两个清除任务。第一,它会执行所以通过Runtime.addShutdownHook注册的shutdown hooks.它能有效的释放JVM之外的资源。第二个清除任务是运行相关的finalizer方法终结对象。

如果需要在调用System.exit方法后仍然打印出”Goodbye World”,可采用如下的方法:

System.out.println(“Hello World”);

Runtime.getRuntime().addShutdownHook(

new Thread(){

public void run(){

System.out.println(“Goodbye world”);

}

}

)

System.exit(0);

前段时间在看Yurii这篇文章中提到shutdown hook,当时不知道这是干什么的,也没深究,通过这个Puzzle终于明白了。其实shutdown hook可以做许多的事情,在JVM使用到外部资源时,可以保证JVM在退出时正确的释放了这些资源。比较典型的应用是数据库连接,文件锁等。以前试过通过sql plus连上Oralce,然后sqlplus不正常退出,Oracle服务上面对应的那个链接并不会关闭。不知道使用Java的数据库线程池在JVM崩溃时会不会有这问题。总之如Yurii所说:在编写Java服务程序的时候,记得设置退出的钩子函数(RunTime.getRunTime.addShutdownHook)是一个非常好的习惯。

在了解shutdown hook时找到这篇文章:Use JVM Shutdown Hooks to Optimize Resources,展示如何使用它。在Tomcat的源代码中也有用到shutdownHook。在Catalina.java这个类中的start和stop方法有使用到shutdownHook。

零拷贝IO

上周看了一篇文章–通过零拷贝实现数据传输,讲的是通过Java nio中提供的FileChannel和SocketChannel实现零拷贝传输数据。这篇文章中讲的零拷贝是指数据不拷贝到用户空间中,实现直接在内核空间中进行数据拷贝。实现这种零拷贝的方式就是使用FileChannel直接映射到内核空间的读入的文件,为了验证这篇文章中的一些说法,今天自己做了一下实验。下面是实验的过程:

下面是程序执行前的系统状态:

star@star:~$ vmstat 5 20
procs ———–memory———- —swap– —–io—- -system– —-cpu—-
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 0 1383324 106776 307612 0 0 1076 80 190 375 6 3 55 36
0 0 0 1383316 106784 307644 0 0 0 9 59 132 0 0 100 0
0 0 0 1379016 108048 307692 0 0 250 53 166 279 1 1 95 3
0 0 0 1379016 108060 307692 0 0 0 15 56 126 0 0 100 0
0 0 0 1379016 108060 307692 0 0 0 0 65 142 0 0 100 0
0 0 0 1378892 108068 307692 0 0 0 11 62 142 0 0 100 0
0 0 0 1378892 108068 307692 0 0 0 17 65 134 0 0 100 0
0 0 0 1378892 108068 307692 0 0 0 0 55 123 0 0 100 0

接下来通过Java传统的IO方法复制一个大概700M的文件,下面是执行过程中系统的情况:

star@star:~$ vmstat 5
procs ———–memory———- —swap– —–io—- -system– —-cpu—-
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 0 1343840 108796 336816 0 0 536 42 111 250 3 2 78 17
0 1 0 1262916 108912 412864 0 0 8301 2 725 1088 3 4 73 21
0 2 0 1155796 109308 515724 0 0 10906 907 1088 1744 11 6 34 49
6 3 0 1080112 109608 588068 0 0 5722 2561 705 1061 10 5 11 74
3 1 0 1047328 109656 620316 0 0 3442 4457 529 930 3 3 35 58
2 1 0 1016900 109728 655252 0 0 4539 5758 624 1308 3 4 25 67
0 1 0 971212 109784 701504 0 0 4204 4107 512 620 1 3 52 44
0 2 0 899196 109876 772888 0 0 7559 8298 716 734 2 4 50 44
0 2 0 849384 109944 822104 0 0 4922 2460 531 616 2 3 20 76
0 2 0 787204 110008 883928 0 0 5766 1626 639 742 2 4 21 73
0 4 0 745104 110064 924704 0 0 4510 9200 554 652 1 3 18 77
0 5 0 691704 110112 977748 0 0 4100 4203 658 1758 5 5 10 80
0 1 0 676940 110148 992576 0 0 2691 4562 480 1037 3 3 40 54
0 4 0 616340 110220 1053284 0 0 6074 2460 668 803 3 5 13 79
0 5 0 575380 110260 1093628 0 0 3613 3781 475 645 2 2 3 93
0 3 0 526956 110316 1140876 0 0 5150 11004 577 667 1 3 13 83
1 0 0 475192 110380 1192528 0 0 5178 5060 622 697 2 3 46 49
0 3 0 354256 110500 1312436 0 0 11583 8437 968 1025 2 7 49 41
0 1 0 310304 110552 1355976 0 0 4779 5401 556 610 1 3 36 60
3 2 0 251840 110616 1413764 0 0 5650 2329 782 1466 9 6 15 70
0 4 0 203412 110676 1461572 0 0 4511 4374 566 869 4 3 27 66
0 2 0 172488 110724 1492224 0 0 3461 6240 492 633 1 3 23 72
0 2 0 143680 110764 1520836 0 0 2870 3714 516 846 4 2 36 58
0 3 0 93536 110820 1570420 0 0 4076 1748 724 1691 6 5 36 52
0 2 0 54352 99620 1619288 0 0 5638 2530 754 1230 4 5 29 63
0 3 0 51676 80096 1642204 0 0 4408 3478 714 1355 3 4 31 62
2 2 0 57676 58260 1657796 0 0 2870 8251 648 1416 8 5 15 72
0 2 0 53480 52584 1668976 0 0 5062 2951 781 1240 7 5 25 63
2 2 0 53880 52636 1668752 0 0 4548 7816 568 709 2 3 28 68
0 0 0 56908 52684 1669808 0 0 1142 830 461 1365 11 3 62 24
0 0 0 94428 53180 1632276 0 0 1174 22 645 1246 11 3 70 15
0 0 0 94676 53204 1632284 0 0 0 62 206 803 1 1 98 0
0 0 0 94428 53216 1632180 0 0 0 12812 121 276 1 1 98 0
0 0 0 94312 53216 1632252 0 0 0 2 167 672 1 0 98 0

接下来运用Java nio中的FileChannel机制实现的复制文件程序,程序大小仍然为700M。程序运行过程中系统的情况如下:
star@star:~$ vmstat 5
procs ———–memory———- —swap– —–io—- -system– —-cpu—-
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 0 92212 53308 1635444 0 0 901 557 151 301 3 2 70 25
0 1 0 52532 53428 1669804 0 0 6405 75 485 851 2 3 77 18
0 3 0 53088 53592 1668684 0 0 16106 1019 916 981 2 7 38 53
0 2 0 53696 53652 1668944 0 0 5663 1324 638 1132 7 5 19 69
0 2 0 53012 53688 1669832 0 0 2666 2160 587 986 8 3 28 61
0 3 0 52140 53716 1671276 0 0 2690 2455 442 401 0 1 21 77
0 3 0 52384 53752 1670980 0 0 2691 3246 438 352 0 1 33 66
0 2 0 52780 53904 1670336 0 0 5297 14489 458 686 3 4 42 52
0 1 0 54560 54004 1669440 0 0 6192 7413 703 1199 6 5 45 44
0 1 0 51432 54164 1674176 0 0 16032 15893 756 1496 4 8 45 42
0 1 0 54152 54292 1672620 0 0 12044 11534 621 944 2 4 52 42
0 1 0 54304 54432 1674624 0 0 14069 14776 852 1525 12 8 42 37
0 1 0 52592 54540 1678512 0 0 11250 10667 744 1047 2 5 55 39
0 1 0 52504 54628 1679308 0 0 9481 9030 703 1227 3 4 52 41
0 2 0 58200 54740 1674244 0 0 11737 12308 603 630 1 5 56 38
1 2 0 53372 54832 1679972 0 0 9174 9333 592 489 0 4 49 47
0 2 0 52340 54904 1681052 0 0 5412 19216 433 400 1 3 49 47
0 0 0 61916 54340 1680332 0 0 7147 3 599 461 1 4 80 15
0 0 0 62040 54352 1680224 0 0 0 26 71 154 0 0 100 0
0 0 0 60924 54364 1681144 0 0 0 19 164 702 2 1 97 0
0 0 0 59016 54364 1683332 0 0 0 0 213 939 1 1 98 0

下面是两个程序执行完成生打印出的复制文件消耗的时间:

star@star:~/workspace$ java TraditionalCopy
total bytes transferred–733544448 and time taken in MS–139949

star@star:~/workspace$ java ZeroCopy
total bytes transferred–735938560 and time taken in MS–79667

为了避免后面的实验会使用到前一个实验剩下的缓存数据,在实验中复制了两个不同的文件。首先从时间上来看,nio零拷贝的方式比传统的IO时间消耗少了40%多,而上下文切换(cs)次数减少并不特别明显。比较有趣的一个数据是bi(从磁盘读入内存), bo(从内存写回磁盘),在采用nio零拷贝的方式下bi,bo都在长时间里达到10M。因此在Java中要进行大块的数据量的迁移时,零拷贝的方式应该是首选方案。

最后将零拷贝的程序与系统中的cp进行一下对比,发现性能还是没得比啊,也可能是我程序写得烂。下面是数据
star@star:~/workspace$ time java ZeroCopy
total bytes transferred–735938560 and time taken in MS–89160

real 1m29.712s
user 0m0.120s
sys 0m3.904s
star@star:~$ time cp zerocopy.avi Documents/

real 0m57.719s
user 0m0.032s
sys 0m3.116s

最后附上测试的代码:
零拷贝:
public long copy(String srcFilename,String destFilename) throws Exception{
File srcFile = new File(srcFilename);
long size = srcFile.length();

FileChannel fileChannel = new FileInputStream(srcFile).getChannel();

FileChannel outChannel = new FileOutputStream(new File(destFilename)).getChannel();
fileChannel.transferTo(0, size, outChannel);

fileChannel.close();
outChannel.close();

return size;
}

传统拷贝代码:

public long copy(String srcFilename,String destFilename) throws Exception{
long length = 0l;
FileInputStream srcInput = new FileInputStream(new File(srcFilename));

FileOutputStream destOutput = new FileOutputStream(new File(destFilename));

byte[] bytes = new byte[8192];
int size = 0;
while((size = srcInput.read(bytes)) > 0){
destOutput.write(bytes);
length =length + size;
}

return length;
}