转自:
http://school.cnd8.com/java/jiaocheng/19286.htm
引言
Java语言自90年代出现以来,因为它的安全性和跨平台性(即所谓的”Write Once,Run Anywhere”)等特点,深得广大程序员的青睐,但是同时,Java程序的运行效率的低下也是程序员的心病。Java是介于解释型和编译型之间的一种语言,同样的程序,假如用编译型语言C来实现,其运行速度一般要比Java快一倍以上。怎样提高java应用程序的效率是广大程序员关心问题。本文将从与Java字节码的运行过程中影响性能的相关因素的分析入手,然后,探讨一些在Java代码的设计过程中具体的有助于提高性能的策略。
一、性能分析
JVM运行时的负载主要集中在字节码的执行,内存治理,线程治理和其他的操作几个方面。
1.1 JVM的结构
JVM中运行的是Java字节码(Bytecode).class文件,这种class文件除了准确定义一个类或接口的表示外,还定义了一些与平台相关的诸如字节顺序的具体信息。
Java的数据类型分为primitive和reference,对于不同的数据类型的运算在JVM中的有不同的指令去执行,比如iadd,ladd,fadd就是分别针对int,long,float的加法运算,当然,它们的执行效率也不一样, 运行时的数据区,在一个程序运行时,JVM都要为它定义不同的运行数据区,有些数据区在JVM启动时就创建好了,直到整个JVM退出时才释放掉,还有一些数据区的是属于每个线程的,它的生命周期与线程相等。
JVM中的逻辑结构有:
PC(program counter)寄存器,每个线程有自己的PC(program counter)寄存器,当JVM执行的方法不是本地(Native)的时,这里存放当前线程运行的指令的地址,假如是本地(Native)的,PC(program counter)寄存器的值没有定义。
JVM栈(stack),当创建线程时,每个线程都创建一个属于自己的栈,用来存放frames(见下面),它存有本地变量,方法调用中的部分结果。
堆(heap),JVM中所有线程共享这个堆,类的实例和数组都是从堆中分配内存的,堆是在整个JVM启动时初始化的。
方法区(Method Area),线程间共享,它存放每个类中的运行时常数池(runtime constant pool),域值和方法数据,以及方法和类的构造函数的代码,其中包括用于类的非凡方法,实例初始化和接口类型的初始化,
运行时常数池(runtime constant pool),是每个类或接口的class文件中的常数池表在运行时的表示,它包括各种常数如编译时就知道的数字常量,还有运行时才能确定的方法和域的引用,类似传统语言的符号表,
本地(Native )方法栈(Stack),用来支持本地(Native)方法调用,这些方法用非Java的语言编写,需要传统的"C"栈。
帧(Frames),存放方法调用中的数据和部分结果及返回值,执行动态连接,分派例外,一个新的Frame在方法被调用时创建,方法调用正常或非正常完成时销毁,Frame从每个线程创建的JVM的栈中分配内存,它属于每个线程,每个Frame有自己的本地变量组,自己的操作栈(Operand Stack)和指向当前方法的运行时常数池的引用,本地变量组和操作栈的大小在编译的时候就已经确定,在一个获得控制的线程中只有一个Frame是激活的,这个Frame为当前Frame,它的方法为当前方法,方法所属的类为当前类,当这个方法又调用别的方法或结束时,这个当前Frame不再激活,一个新的Frame被创建并成为当前Frame,直到当前方法调用完成后,这个Frame被释放并返回结果,前一个方法的Frame成为当前的Frame,
本地变量,每个方法的Frame包含一组在方法中定义的本地变量,它们的大小在Java编译时就已确定。
动态连接(Dynamic Linking),每个Frame包含一个指向当前方法的运行时常数池的引用,它通过符号引用(symbolic references)访问变量和指向被引用的方法,动态连接(Dynamic Linking)在运行时将这些方法的符号引用转为具体的方法引用,并加载相应的类,它还将变量影射到当前运行时的变量的内存偏移上。
1.2 字节码(Bytecode)的执行
JVM动态地加载(Loads),连接(Links)和初始化(Initializes)类和接口的字节码,加载(Loading)就是JVM发现具有某一特定名字的类或接口的二进制表示,并从这个二进制表示在内存中创建出一个类或接口,连接(Linking)就是使一个类或接口与JVM的运行时状态很好的结合,以便执行它,一个类或接口的初始化就是执行它的初始化方法。
1.3 内存治理
Java是一个面向对象的语言,因此,在JVM的内存中大部分是对象,从上面的分析我们知道,对象的内存是从堆(heap)分配的,对象内存的回收是由自动内存治理系统(由叫垃圾收集器-Garbage Collector)来完成的,编程人员是不用显式的释放内存的,垃圾收集器Garbage Collector通过记录指向对象的引用的数目来决定是否释放对象所占据的内存空间,当指向某个对象的引用数为零时,这个对象就可以释放了。
1.4 线程治理
Java是一个支持多线程的语言,因此线程的治理是JVM的一个主要工作,每个线程都有自己的工作内存,线程间的共享变量是存放在整个JVM的主内存中的,线程间数据的同步通过lock来共享数据并保证数据的一致性,线程间控制的转移通过对wait,notify等方法的调用来实现。
二、性能设计
通过以上的分析,我们就以下几个方面提出一些有关性能设计的策略。
2.1 对象的构造
从上面我们知道,Java对象的内存是自动治理的,因此,一般认为,程序员是不用担心内存的分配的,但这种想法是不完全正确的,java通过垃圾收集器(Garbage Collector)来处理内存分配与释放的底层操作,程序员不用直接治理内存,这样防止了由于内存的错误操作导致的数据破坏(corruption),但并不意味着程序员不用担心内存的使用,内存的使用不但会给系统带来很大的负担,比如,Java并不阻止程序占用过多的内存,当对象向堆所请求的内存不足时,垃圾收集器(Garbage Collector)就会自动启动,释放那些引用数为零的对象所占用的内存,Java也不会自动释放无用的对象的引用,假如程序忘记释放指向对象的引用,则程序运行时的内存随着时间的推移而增加,发生所谓内存泄漏(memory leaks),创建对象不但消耗CPU的时间和内存,同时,为释放对象内存JVM需不停地启动垃圾收集器(Garbage Collector),这也会消耗大量的CPU时间。
策略:尽量避免在被经常调用的代码中创建对象。
对于集合类(collection),应尽量初始化它的大小,假如不初始化它的大小,JVM自动给它一个缺省的大小,当你的要求大于这个缺省的大小时,JVM就会重新创建一个新的collection对象,原来的对象就释放掉,这样必然会增加JVM的负担。
当一个类的多个实例在其本地的变量里访问一个特定的对象时,最好将这个变量设计为静态(static)的,而不是每个实例中变量里都存放那个对象的引用。
因为对象的创建是非常昂贵的,所以应尽量重用,少用new来获得对象的引用,尽量重用容器对象(Vector,Hashtable)等而不总是创建新的对象抛弃旧的对象,但一定要注重释放容器对象中所保存的指向别的对象的引用。
尽量使用primitive数据类型。
当只是访问一个类的某个方法时,不要创建该类的对象,而是将该方法设计成一个static的方法。
尽量简化类的继续关系和设计简单的构造函数。
创建简单数据类型的数组要比初始化一个这样的数组快,创建一个复杂类型的数组要比克隆一个这样的数组快。
2.2 字符串(String)
String在Java程序中被广泛使用,String对象是不可改变的,例如: String str="testing"; str=str+"string"; 这个"testing"String一旦创建,就不能更改,但指向这个String的引用str可以改变,str原来指向"testing",经过第二个运算后,改为指向新的String"testingstring"了。针对String的这个特性,对于String的使用,我们有如下策略:
假如字符串在程序中可能被改变,比如增加,接或删除字符,就应使用StringBuffer,创建具有初始大小的StringBuffer对象,尽量重用该对象,而不使用"+"操作。
当我们要分析字符串中的字符时,就不要使用String或StringBuffer,而是使用字符(cbar)数组,别是在循环中分析字符时,更应如此。
尽量少用StringTokenizer,它的方法的性能比较差。
2.3 输入输出(Input/Output)
程序的I/O往往是性能的瓶颈所在,java io定义了两个基本的抽象类:InputStream和OutputStream,对于不同的数据类型比如磁盘,网络又提供了不同的实现,javaio也提供了一些缓冲流(Buffered Stream),使硬盘可以很快的读写一大块的数据, 而Java基本的I/O类一次只能读写一个字节,但缓冲流(Buffered Stream)可以一次读写一批数据,,缓冲流(Buffered Stream)大大提高了I/O的性能,对象的序列化(serialization)是一个将处于生成期的对象序列化成可以在流(stream)中读写的数据的过程,象的序列化是一个非常复杂,昂贵的过程,要一个类implements接口 java io Serializable,它就可以被自动的序列化,针对以上分析,我们对I/O有如下对策:
·小块小块的读写数据会非常慢,因此,尽量大块的读写数据
·使用BufferedInputStream和BufferedOutputStream来批处理数据以提高性能
·对象的序列化(serialization)非常影响I/O的性能,尽量少用
·对不需序列化的类的域使用transient要害字,以减少序列化的数据量
2.4 循环(Loop)
因为循环中的代码会被反复的执行,所以循环中经常是寻找有关性能问题的地方,嵌套的循环更轻易产生性能问题, 在循环中,我们应该注重如下问题:
·循环常量(Loop Constant),在循环中它的值不会改变,因此,它的值应该在循环外先计算出来。
·本地变量(Local Variable),从上面的分析可知,在方法中使用本地变量比使用对象的属性消耗较少的资源,在循环中却不一样, 因为循环中的代码要反复地被运行,因此,尽量少地在循环中创建对象和变量。
·尽早结束循环,假如循环体在满足一定条件就可以结束,就应尽快结束。
2.5 集合类(Collections)
集合类在此Java编程中被广泛地使用,大致上,一个集合类就是将一组对象组装成一个对象,Java的集合类框架由一些接口和一些为通用目的而实现(implementation)的类组成,集合类的基本结构由六个在java.util包内的接口组成,主要有如下结构:
Collection 这是集合类的基本接口,它为一组对象提供了一些简单的方法,
List 具有可以控制的顺序,但并没有定义或限制按什么排序。
Set 不能包含重复的元素,
Map 将一个键(Key)影射到一个值(Value),不答应有重复的键,
除了上述接口之外,java.util还提供了一些为通用目的而实现的类,如Vector,ArrayList,Hashtable等等,这些类里,有些提供了某种排序算法,有的提供了同步的方法,有如此多的集合类,在具体使用过程中,我们如何根据自己的需要选择合适的集合类,将对程序的性能产生很大的影响,下面将一些常用的类进行比较, Vector和ArrayList Vector和ArrayList在使用上非常相似,都可用来表示一组数量可变的对象应用的集合,并且可以随机地访问其中的元素。
它们的区别如下:
Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
Hashtable和HashMap
它们的性能方面的比较类似 Vector和ArrayList,比如Hashtable的方法是同步的,而HashMap的不是。
ArrayList和LinkedList
对于处理一列数据项,Java提供了两个类ArrayList和LinkedList,ArrayList的内部实现是基于内部数组Object[],所以从概念上讲,它更象数组,但LinkedList的内部实现是基于一组连接的记录,所以,它更象一个链表结构,所以,它们在性能上有很大的差别。
(1)从上面的分析可知,在ArrayList的前面或中间插入数据时,你必须将其后的所有数据相应的后移,这样必然要花费较多时间,所以,当你的操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能。
(2)而访问链表中的某个元素时,就必须从链表的一端开始沿着连接方向一个一个元素地去查找,直到找到所需的元素为止,所以,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
(3)假如在编程中,1,2两种情形交替出现,这时,你可以考虑使用List这样的通用接口,而不用关心具体的实现,在具体的情形下,它的性能由具体的实现来保证。
设置集合类的初始大小
在Java集合框架中的大部分类的大小是可以随着元素个数的增加而相应的增加的,我们似乎不用关心它的初始大小,但假如我们考虑类的性能问题时,就一定要考虑尽可能地设置好集合对象的初始大小,这将大大提高代码的性能,比如,Hashtable缺省的初始大小为101,载入因子为0.75,即假如其中的元素个数超过75个,它就必须增加大小并重新组织元素,所以,假如你知道在创建一个新的Hashtable对象时就知道元素的确切数目如为110,那么,就应将其初始大小设为110/0.75=148,这样,就可以避免重新组织内存并增加大小。
2.6 方法(Methods)
从上面的JVM的结构分析可以看出,Java程序在执行的过程中就是一个初始化对象和调用其方法过程,其中对方法的调用花费了很多资源,这些资源都用来转移线程控制,传递参数,返回结果和创建用于存放本地变量及中间结果的帧栈(stack frame)。
代码嵌入(Inlining)
由于方法的调用需要消耗大量的资源,因此,Java编译器可以将一些方法调用转化为代码嵌入(Inlining),就是将一段代码对一个方法的调用转化为将该方法的代码在编译时嵌入到调用处,这样,由于减少了方法的调用,就可以大大提高代码的性能,当将一个方法声明为final,static,private时,编译器就会自动的使用代码嵌入技术将该方法代码在编译时嵌入到调用处。
同步(Synchronized)方法
在多线程访问共享数据时,为了保证数据的一致性,就必然要使用同步技术,但从上面的分析可知,使用同步方法比使用非同步方法的性能要低,因此,我们应尽量少使用同步方法?调用同步方法的代码本身就不需要再同步了。
分享到:
相关推荐
在本项目中,标题"JAVA制作火狐内核浏览器源代码"揭示了主要知识点是使用Java编程语言构建一个基于火狐浏览器内核的定制化浏览器。这个项目可能涉及到对Mozilla Firefox浏览器内核的理解,以及如何利用Java进行...
2. 嵌入式Java的传统解决方案和Java操作系统的基本结构,并分析它们对YJVM设计的影响及其与YJVM的异同。 3. Java虚拟机的工作原理和结构,以及YJVM中Java虚拟机结构和实现方案之间的具体映射。 4. YJVM中程序装载、...
在《大话JAVA性能优化》的PDF文档中,你将找到这些方面的详细讨论和实例分析,它不仅涵盖了理论知识,还提供了实战技巧和工具使用,是Java开发者进行性能优化的宝贵参考资料。通过深入学习和实践,开发者可以提升...
Java虚拟机(JVM)是Java程序运行的基础,它的内核设计直接影响到程序的性能表现。本教程聚焦于“深入JVM内核—原理、诊断与优化”,特别关注Java堆的分析,这对于开发者来说至关重要,因为堆是Java应用程序中对象的...
《Android内核与标准Linux内核对比分析》 Android系统是Google公司开发的一款基于Linux内核的开源操作系统,尤其在移动设备领域广泛应用。其系统架构包括四个主要层次:基于Linux的内核模块、运行时库与其他库、...
### Android内核的简单分析 #### 一、概述 随着Google的Android操作系统的成功发布,其基于Linux-2.6.25内核的特点引起了广泛关注。由于所有源代码的完全开放,这为开发者们提供了宝贵的资源,使得对Android内核...
从架构上看,Greenplum的分布式数据库设计支持线性扩展能力,使得系统能够处理上百个物理节点,同时保持高性能。它采用了无共享架构,将数据分布在多个Segment节点上,而主节点则负责整个集群的协调工作。主节点和从...
PCA(Personal Internet Client Architecture)是由Intel提出的下一代个人Internet设备架构的参考模型,旨在为移动...研究和分析PCA架构下的Linux内核结构,对于提升设备性能、降低功耗和增强用户体验具有重要意义。
* Windows 操作系统的设计理念和实现机理与 Linux 内核不同。 * Windows 操作系统对比 Linux 内核可以帮助读者更好地理解两者之间的异同。 五、数学和算法 * 数学和算法是计算机科学的基础, 涵盖了数论、代数、...
首先,从实例开始,我们可以看到一个典型的性能分析流程,包括客户端发起请求,服务器端的缓存检查,数据交换与记录,日志记录,以及最终的响应。这个过程中涉及的主要性能指标有网络I/O(NET IO)、文件I/O和CPU...
5. **perf**:性能分析工具,可以用来分析内核的性能瓶颈,包括CPU周期、指令执行、内存访问等。 6. **LKM(Loadable Kernel Module)调试**:对于可加载的内核模块,可以利用insmod、rmmod命令进行调试。 7. **...
它提供了代码编辑、调试和性能分析等强大功能,并集成了Android SDK,是开发Android应用的首选工具。 4. Android SDK:Android SDK(Software Development Kit)是一套工具和文档的集合,用于帮助开发者构建Android...
Java内核编程主要涉及Java语言的核心机制,包括语法特性、类型系统、异常处理以及面向对象设计等。这些基础知识是每个Java开发者必须掌握的,它们构成了Java程序的基础架构。 1. **Java语法与类型系统**:Java是一...
《深入JVM内核—原理、诊断与优化》是一份全面涵盖Java虚拟机核心知识的教程,共计11个章节,旨在帮助读者深入理解JVM的工作原理,掌握故障诊断技巧,并能进行有效的性能优化。这份资料是每一个Java开发者进阶的必备...
- **分析架构与设计**:评估应用是否使用分布式对象(例如EJB)、数据库连接方式、同步或异步调用等。 - **性能术语理解**:了解关键性能指标的含义,比如负载(峰值或平均值)、点击(页面访问或HTTP请求)、响应...
- **应用程序接口(API)**:分析Android SDK提供的API是如何与底层内核进行交互的。 - **性能优化**:提供具体的优化策略,帮助开发者更好地利用Android内核的功能。 #### 四、结论 通过上述内容的阐述,我们可以...
该游戏的设计和实现涉及到多个方面,包括 Android 技术简介、Java 技术简介、需求分析与系统概要设计、系统详细设计、系统测试与性能分析等。 首先,该游戏的设计和实现需要了解 Android 技术简介,包括 Android 的...