转载:http://www.ibm.com/developerworks/cn/java/buildhp/index.html
由于 Java 的可移植性、易用性和与 HTML的紧密结合, Java已成为动态网页内容的首选编程语言。 Java被用来产生网页上的动画效果,在服务器端动态的选择、格式化网页内容,并用作面向交易应用软件的前端来检查终端用户的输入信息。 Java 还是作为在虚拟现实模型(VRML)[HW96]语言中设计三维动作和在这种虚拟环境[VRML97]中提供多用户交互的重要语言。
<!----><!----><!---->
Java除了以上提及功用以外的,还很少被用来开发需要高性能和高吞吐量的应用程序与服务器。开发人员很少在这些情况下选择 Java的主要原因是由 Java代码编译出来的程序的运行速度无法与传统的编译语言(如C或 C++)相比。 大规模的程序中的垃圾收集和JIT (Just-In-Time)编译方面的局限性也造成开发人员对 Java性能的怀疑。
我们结合了精心的程序设计和有针对性的优化,目前已克服了许多这些方面的不足。本文介绍了利用 Java开 发高吞吐量的应用程序和服务器,并说明了 Java应用程序能够及其方便的得到优化以达到用传统语言,如C和 C++,编写的代码的性能。这些结果都是建立在我们使用 Java开发InVerse (Interactive Universer) 服务器所获得的经验之上。
我们的测试环境是一部拥有48MB内存、运行Windows 95的奔腾150MHz计算机,使用由Sun公司提供的Java 1.1.4编译器和虚拟机,并带-O标志进行优化编译。计时是通过在一紧缩循环(tight loop)中执行目标代码会和调 用 System.currentTimeMillis() 捕获计时信息而得到的。我们还利用 Just-In-Time (JIT) 代码生成器 ( 在运行阶段将 Java byte 代码转换为本地指令 ) 来进行测试。
其它的章节介绍了 Java 应用程序开发者会遇到的一些主要的性能问题,以及优化这些问题的方法:内存访问方式、额外同步化、额外对象创建和垃圾收集、额外错误处理和对一般类库的依赖性。虽然这些内容很多都适应与其它的面向对象编程语言,但与 Java开发者有着密切的关系。文章的结尾介绍了 InVerse 服务器应用程序的整体性能将如何通过优化这些方面得到改进,并讨论了这一领域的今后工作。
内存访问方式
在当前处理器速度与内存访问时间差距越来越大的情况下,高性能应用程序的开发者们越来越清醒的认识到最小化内存访问时间的重要性。在诸如C++之类的编译型语言来说,因为内存地址是预先算出的,因此对不同的变量来说内存访问时间几乎是相同的。最坏的情况也只是经过一次间接寻址。但对Java来说,应用程序的效率则相当程度上要受到其访问的变量的类型以及访问方式的影响。例如,栈变量可以直接寻址,而实例变量则一般需要经过额外的间接寻址过程。
|
读 |
写 |
|
无 |
JIT |
本地方法变量
对象的实例变量
父类实例变量
超父类实例变量
类的静态变量 |
1.00
1.92
1.96
1.83
1.23 |
1.00
2.14
2.14
2.14
1.52 |
|
有 |
JIT |
本地方法变量
对象的实例变量
父类实例变量
超父类实例变量
类的静态变量 |
1.00
1.64
1.73
1.73
1.23 |
1.00
1.99
2.14
2.21
1.62 |
表 1: 不同变量类型及访问模式的访问时间
额外同步
在Java中,一个方法( method)或者代码块可能用 synchronized 这个关键字标注。在同一个类的实例中,任何一个同步的方法都不能执行多线程的同时操作,而对于一个作为参数的对象,一个同步的代码段被视为一个同步的方法。为了支持这种能力, Java虚拟机在每一个拥有同步方法的对象上连接了一个监视器(monitor)。当有线程进入一个标为同步的代码段时,它都需要先从该监视器处获得一个锁定标记
表2:是否同步及有无线程抢占的不同组合下的方法调用时间(微秒)
|
非同步. |
同步. |
|
Without |
JIT |
无线程抢占线程抢占 |
0.54
0.55 |
3.96
8.87 |
|
With |
JIT |
无线程抢占线程抢占 |
0.50
0.57 |
5.55
11.00 |
如表2所示,同步的方法能使应用程序的运行减慢20%左右。表中显示的是在不同的组合下调用一个空方法所用的时间。在有线程抢占的情况下,两个线程在一个紧密的循环中同时访问了这个对象的方法。
表中的数据揭示了这样一个现象:在程序中添加同步标记使性能下降8%--11%。这还是在单线程的情况下(即监视器从不打断该线程)。由此可见,同步的方法耗时并非仅仅是获得锁定标记,更多的时间耗费在检查锁定标记上。
还应该注意,JIT编译器并不能增强对锁定标记的访问和抢占损耗。这是因为它并没有获得关于该应用结构的信息,事实上,因为JIT编译器的损耗,程序的运行反而减慢了。与此相反,在非同步调用的情况下,它可以稍稍增强程序的效率。
同步对程序效率的影响是微妙的,例如,当内存分配同步时,就意味着创建一个对象会带来效率的降低。
Java类库中的许多通用函数是设计为线程安全的,因此也就是同步的。例如,在向量中访问一个索引的元素就需要调用一个同步的方法。就象在关联的枚举类型中调用nextItem() 一样。以我们的经验,许多集合都只有一个线程去访问,因此这样做显得因小失大。为了让我们的应用程序能够有选择的屏蔽同步,我们创建了象Java Vector的子类来提供非同步的访问方法。在I/O stream库中情况也是类似的。在使用DataOutputStream读写格式化的数据时86%的损耗来自对同步的OutputStream方法的调用。在这里我们不能取消同步,但有时能用在一个同步的代码块中加入多个同步方法调用的方法提高效率
例如将
for (i=0; i
x.f(); // synchronized on x
|
改写成
synchronized(x) {
for (i=0; i
x.f(); // synchronized on x }
|
替换的代码允许Java虚拟机的同步方法“短路”。这种方法只是在同步方法持续时间很短时使用。否则将阻塞其他线程。或者在不存在线程抢占时使用。但是,Java虚拟机对锁定标记的回写效率会造成差别很大的结果。
额外的对象创建和垃圾收集
有了Java的内置垃圾收集机制,程序员再也不需要顾虑他们的应用所需的内存是如何分配以及何时释放的了。尽管这种机制简化了OO开发的任务,但它也带来了明显的效率问题。程序员喜欢随意创建对象,因为他们误以为这样做很“便宜”,而事实上,Java虚拟机要他们为对象内存分配和释放付出代价。对象的创建和销毁在Java中是及其昂贵的。表3显示了不同的对象创建操作所需时间和从一个同步的数组中抽取条目(就象是在一个自由的对象池中所做的那样)所需时间的比较。该表揭示了对象的创建要比重用已存在的对象昂贵得多。差别有1/10左右。这是因为,从我们的经验来看,许多临时类在程序中并不需要同步的点上被实例化了。
无 有 JIT JIT 创建对象 (无构造函数 ) 创建对象 (有构造函数) 创建子类 (无构造函数) 创建子类 (基类构造函数) 12.57 12.97 12.79 12.80 12.36 13.45 12.47 12.53 (同步) 从数组中去除元素 4.06 3.84 |
Table 3: 创建Java对象的时间 (微秒) |
在运算密集的应用中,如服务器,对象的创建有着明显的影响。垃圾收集器应当仅仅在没有其他线程运行时执行,以此来使它对应用效率的影响减到最低。但是,一个高性能的应用也许并不会造成这样的等待孔隙,因此垃圾收集器也在应用执行时“窃取”时间。
为了减少创建和销毁的对象,我们使用了如下两种技巧:
当一个对象重复被分配时(例如在一个特定的方法中),在这个方法的循环之间声明一个静态变量来储存这个对象。在方法中对储存的对象调用reinitialize()方法来初始化。当一个对象同时存在多个活跃的实例,并且它们的生命周期不能被当做应用中的一个单一部分时,使用一个矢量或者一个可扩充的数组来当作一个自由实例库。当一个对象不再需要时,则用一个静态方法将其加入自由对象池。相似的,当要获得一个新的类实例时,则调用另一个静态方法从库中释放一个现有的实例并对它调用reinitialize()方法。将它的构造函数声明为private以放应用不慎对它直接分配一个实例。
两种方法都依赖于在最优化的类中的reinitialize()方法。偶尔我们也需要在不支持该技巧的Java类中构造子类来创建这种支持
错误检查过度
Java虚拟机提供了对例外处理的本地支持。而C++则与此不同,它依靠编译器产生的指令来存储和处理例外信息。Java这些内置的指令使得Java的例外处理速度较快。如表4所示,加入try-catch从句所引起的时间消耗是微不足道的。真正出现例外时处理所需的时间虽然不小,但这种成本只是会偶然出现。
无 JIT JIT 函数调用 try-catch中的函数调用 (无例外抛出) try-catch中的函数调用 (有例外抛出) 0.2 0.2 191.7 0.2 0.2 186.2 If-test array index bounds Array dereference Array dereference throwing bounds exception 0.5 0.4 1613.2 0.5 0.4 1548.9 If-test using instanceof Object type cast Object type cast throwing casting exception 0.8 0.7 1581.9 0.7 0.7 1469.8 |
表4: Java例外处理耗时表 (微秒) |
try-catch从句接近于零的时间消耗告诉我们,在错误不多的情况下我们应该依赖于例外处理而不是进行明确的错误检查。例如如下的例子:
if ((idx >=0) && (idx < array.length))
x = array[idx];
else
// error
|
如果依赖于例外处理,则程序会是如下的样子:
try {
x = array[idx]
}
catch (ArrayOutOfBoundsException e) {
// error
}
|
普通情况下(索引在取值范围内),面向例外处理的程序会比普通方法快50%左右。只要通常情况多于例外情况,应用的效率就有所提高。这在循环的情况下显得尤其正确。在进行强制类型转换时情况也类似,使用instanceof不如捕捉ClassCastException效率高。
try-catch让我们重新定义在高性能应用中例外的使用。除了简单地指示错误之外,例外还用于指示所有非通常的情况,即便这些非通常情况有时并非错误。这种方法[C96]与 "making the common case fast."的方法通用。
通用类库
标准Java类库支持很多类型的应用,从简单的网页Applet到较为复杂的系统。但是就象所有的软件一样,这个通用类库并不能对某些特定应用提供最佳性能的支持。认识到可以通过改进JDK的通用构架来提高性能对应用程序员是大有好处的。在对应用的存取形式和类之间交互的了解的基础上对某些类进行优化则可以获得性能的提高。
Java类库中某些方法的优化要牺牲其他方法的性能。设计者要根据应用对这些方法的使用情况进行取舍。如果该应用主要调用未被优化的方法,则应该改变类的实现来提高性能。例如,我们写了一个用插入的元素连接到list的Hashtable以此来支持高速串行存取。
象其他的类库一样,JDK严重依赖于它的界面。这种方法增强了模块化,但牺牲了一些总体性能。例如,因为类之间只能通过通用界面互动,它们就不能利用在实现上的彼此相似之处以提高性能。当特定的类通过这样的界面互动时,可以通过改变互动点的设定的方法来提高效能。例如,我们合并了ObjectOutputStream和ByteArrayOutputStream 以提供快速 非同步的输出到byte数组。
结论和前瞻
在我们开发高性能服务器的过程中我们发现了数种针对Java虚拟机特性的优化方法:
- 依靠重新实现标准Java类来去除应用中的同步
- 开发用户化的标准Java类来配合应用中的内存访问模式。
- 当互动能够优化时合并Java类
- 对普通的case执行,有选择的使用例外处理来加快速度。
- 在可能时重用对象的实例,不创建新的临时实例。
- 在可能时创建栈变量的引用作为对象变量的引用的缓冲onclusion and Future Work
表5:对InVerse性能的优化 (Packets/Second Processing Throughput) and Comparison to Zero-Processing Capabilities
|
Before |
After |
InVerse pkts/sec processed:
1 User
10 Users
100 Users
|
InVerse pkts/sec processed:
1 User
10 Users
100 Users
|
InVerse pkts/sec processed:
145
99
N/A
|
InVerse pkts/sec processed:
810
505
N147
|
Base limits (Java)43
1 User
10 Users
100 Users
|
|
InVerse pkts/sec processed:
930
93
9
|
Base limits (C)
1 User
10 Users
100 Users
|
|
1080
1080
11
|
在此向James Clinton先生对我们上一版本的反馈表示感谢。
分享到:
相关推荐
它们支持部署和运行Java Servlet及相关的Web应用程序。心跳检测在这些服务器上的实现可以确保服务的连续性和可靠性。 对于Tomcat,我们可以利用其内置的Manager组件来进行心跳检测。Manager组件允许管理员通过HTTP...
本资源集合包含了关于“JAVA高性能高并发服务器架构”的PDF文档和视频,旨在帮助开发者深入理解如何利用Java技术设计和实现稳定、高效的服务器系统。 首先,"JAVA高性能"是指Java平台在处理大量请求和运算时,能够...
在Java世界中,开发高性能、高并发的Web应用程序是一项关键任务,这关乎到系统的稳定性、扩展性和用户体验。本文将深入探讨如何借助Java技术栈来实现这一目标。 首先,选择合适的框架至关重要。Spring Boot是一个...
Java EE 7与HTML5是现代企业级应用开发中的关键技术,它们的结合使得开发者能够构建出既适应桌面环境又能无缝支持移动设备的高性能应用程序。本文将深入探讨这两个技术领域,以及如何利用它们来创建动态且高效的企业...
Java分布式应用程序设计是一种构建大型、可扩展且高可用性系统的方法。在Java中,通过利用网络中的多台计算机,可以创建能够跨越多个节点共享资源、处理任务和存储数据的复杂应用。这种技术允许应用程序的不同部分在...
总结来说,Java NIO通过引入非阻塞I/O和选择器,优化了服务器的并发处理能力,使得Java在构建高性能网络服务时有了更强的竞争力。理解并熟练运用NIO,对于提升Java应用的性能和可扩展性至关重要。通过合理设计和使用...
Spring Boot简化了创建独立的、生产级别的基于Spring的应用程序的过程。它集成了大量常用的第三方库配置,如数据访问/集成(JDBC、MyBatis、Hibernate)、云服务、安全等。 在文件列表中提到的"java高级应用"可能...
Java的多线程特性使得它非常适合构建能够处理大量并发请求的网络应用程序,比如代理服务器。此外,Java的Socket编程接口提供了网络通信的基础,允许程序创建TCP连接并收发数据。 【Web代理原理】 Web代理程序是...
Java Socket 开发高并发小型服务器涉及的核心概念是网络编程中的Socket技术,以及如何利用Java语言构建能够处理大量并发连接的服务端。首先,Socket是网络通信中的一个基础概念,它为两台计算机之间的通信提供了接口...
在"hyk-proxy"项目中,我们可以推测它利用了Java的Socket编程来建立和管理网络连接,同时可能使用了NIO(非阻塞I/O)或Netty这样的高性能网络库来提升并发处理能力。NIO允许一个线程处理多个通道,从而极大地提高了...
### 基于Java NIO开发高性能并发型服务器程序的研究 #### 一、引言 在互联网技术迅速发展的背景下,服务器程序面临着...因此,深入理解和掌握Java NIO的相关知识和技术,对于开发高性能的网络应用程序具有重要意义。
本教程将重点讲解如何利用Java实现一个简单的TCP文件上传到服务器的程序。 首先,我们来看`uploadServer.java`。这个文件代表服务器端的程序,主要任务是监听客户端的连接请求,并接收客户端发送过来的文件。在Java...
7. **高性能和可伸缩性**:通过负载均衡、集群、缓存策略(如使用 Ehcache 或 Hazelcast)以及线程池优化等手段,提升Java服务器应用程序的性能和可伸缩性。 8. **安全性**:理解如何利用Java EE的安全特性,如JAAS...
首先介绍了Java语言的基础及其在网络编程中的优势,随后详细分析了C/S架构的工作原理,并具体讲解了在C/S模式下Java网络应用程序开发的关键步骤和技术要点,包括开发模型、数据库设计、服务器端和客户端的设计方法。...
综上所述,“基于Java+chromium的桌面程序开发框架”结合了Java的稳定性和Chromium的高性能,为开发现代、跨平台的桌面应用提供了一种创新的方式。这个框架可以帮助开发者充分利用Web技术的便利性,同时享受Java带来...
Ratpack的应用部署也非常灵活,它可以运行在独立的Ratpack服务器上,也可以嵌入到其他Java应用服务器中,或者利用Docker等容器技术进行部署。这为开发者提供了极大的灵活性,可以根据项目的规模和需求选择最适合的...
其特点包括简洁性、安全性、可移植性和高性能。 2. **多线程**:在Java中,通过实现`Runnable`接口或继承`Thread`类来创建线程。多线程下载意味着将大文件分成多个部分,每个部分由单独的线程负责下载,这样可以...
在Java平台上进行P2P(Peer-to-Peer)应用程序的开发是一项技术含量高且充满挑战的任务。P2P网络架构打破了传统的客户端-服务器模式,允许网络中的每个节点既是服务的提供者也是消费者,这种分布式特性使得P2P系统在...