完美汉化 2008-8-13 00:44
JAVA系列:Core Java FAQ
声明
[color=blue] 这是一片别人写的教程,我看着比较实际就做了些修正和整理发到这里来,原文章分为多篇文章,我在这里用楼层加以区分,希望斑竹不会因此而误会我灌水,谢谢。[/color]
[color=#0000ff][/color]
相信对Java有一定了解的朋友都知道,Java不只是一种编程语言,它更是一种技术平台,其内容之广,令人惊讶。现在市面上关于Java的书真是数不胜数,比如Java,J2EE,EJB,DesignPatterns,ANT,Maven,CVS,Servlets,JSP,AOP,IOC,TDD,Multi-Threadingt等等,这些还只是基本功,还没列框架方面的呢,当然你跳过这些去研究什么框架,只能对你说,请珍爱生命啊。有人这时可能会说,买来全部看完,对,我也是这么过来的,差不多也都看了,不得不说痛苦啊,上面这些书平均可能要在300页以上,关键是等你花费大量时间后,能吸收多少呢。
我写这个系列的目的是要把全部这些内容的核心以问答的形式抽出来,即Core Java FAQ,你无须通读上面这些厚厚的书,只要看我的Blog,花有限的时间,学习更多更深的东西。如果你坚持看到我写完这个系列的话,相信你可以跟你的senior team members深入讨论诸如事务,多线程(并行并发),性能调节,安全,内存,架构等等方面的内容,在你Team Lead面前大放异彩。费话不多说,我们直奔主题。
[b]Q1[/b]:解释Java的类加载体系及动态加载?
[b]A1[/b]:Java Class loaders 是继承的,而且有hooks在里面(接口),所以用户可以很容易的定义自己的类加载器,如图所示:
[align=center][attach]6447[/attach][/align]
我们来看一下当你编写一个Helloworld.java编译后,打入java Helloworld的运行过程,首先JVM启动,创建一个bootstrap class loader,这是嵌在JVM内部的,一般也是C实现的,它加载JDK内部类,如java.lang.*,然后创建一个Extensions loader作为它的子加载器,它负责加责JDK目录当中lib/ext下面的*.jar类,接着又创建一个System的loader它是Extensions的子类,它负责加载我们熟悉的CLASSPATH下面的类,就像我们例子中的Helloworld,因为它是启动类,所以是第一个加载进JVM的客户类,必须具备一个static main函数,你创建它一个实例再调用main方法是不现实的,因为总有第一个,所以由JVM调用这个静态方法,一般我们称之为入口函数,这也正是为什么main函数必须是static的。
Class Loaders 用的是一个代理模型,也就是说,当请求加载一个类的时候,Class loader 首先要求它的parent加载,parent找不到才自己加载,如果parent加载了,它永远也不会再次加载,这就保证了唯一性。值的注意的是,child class loader 对parent 加载的类是可见的,反过来是不成立的。在上图当中,Sibling1和Sibling2是二个兄弟loader,它们的parent是System,也就是说,它们对System加载的所有类都是可见的,但是System对于Sibling1和Sibling2加载的类是完全不知道的,而且Sibling1与Sibling2加载的类也是互不影响的,这也是为什么Tomcat下的Web应用你的jar放在哪里它的访问范围是不一样的,各个应用自已WEB-INF/lib下的jar都是独立的。
[b] 注意:[/b]JVM在创建类加载器的时候,都有一个namespace,所以如果二个对象是由二个不同loader加载进来的话是永远不会相等的,换句话说,一个类名(全限定名)只能在一个class loader的namespace里才可以唯一定位。比如我务熟悉的单例模式,严格来讲,每个类加载器有属于它的单例。所以在遇到ClassNotFoundException,ClassCastException的时候就要分析了,比如有时明明子类与父类的关系,为什么从子类转型到父类型会报ClassCastException。总之,要清楚在JVM当中是以[classloader,classname]组合来唯一定位一个类的。
[b]动态加载VS静态加载[/b]
class MyClass {
public static void main(String[] args){
Car c = new Car();//static load a class
Class c = Class.forName(String classname);//Dynamic a class
Object obj = c.newInstance();
}
}
静态加载是编译期就决定加载的类,New后面的就是类名,如果找不到这个类就会报NoClassDefFoundException。
动态加载是可以运行期决定加载哪个类,类名由参数传入,动态创建实例。如果找不到这个类,则会报ClassNotFoundException。
时间关系,今天就写这么一个问题,要想容易的读懂JBOSS,TOMCAT它的类加载机制的实现,这个问题是基础。大家一定要深入理解,如果你的项目在部署中出了问题,对分析是很有帮助的。
[[i] 本帖最后由 完美汉化 于 2008-8-13 00:48 编辑 [/i]]
完美汉化 2008-8-13 00:55
[b]第二篇[/b]
Java Input/Output 是提升Java程序性能首先要考虑的部份。对操作大量数据的Java程序来说,大部份时间花在了IO操作上。这就是说,当我们想对程序进行性能优化,首先应该考虑是不是有哪些IO操作可以优化的。
提高IO速度的基本规则:
最小化访问硬盘
最小化访问底层操作系统
最小化个别的处理字节和字符
下面让我们看看几种提高IO速度的方法:利用缓存最小化硬盘和底层操作系统访问。就像下面代码,缓存磁盘大块数据然后每次读一个字节或一个字符。
Without buffering : inefficient code
try{
File f = new File(”myFile.txt”);
FileInputStream fis = new FileInputStream(f);
int count = 0;
int b = 0;
while((b = fis.read()) != -1){
if(b== ‘\n’) {
count++;
}
}
// fis should be closed in a finally block.
fis.close() ;
}
catch(IOException io){}
注意: fis.read() 是一个底层方法,每次会调用操作系统。
With Buffering: yields better performance
try{
File f = new File(”myFile.txt”);
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
int count = 0;
int b = 0;
while((b = bis.read()) != -1){
if(b== ‘\n’) {
count++;
}
}
//bis should be closed in a finally block.
bis.close() ;
}
catch(IOException io){}
注意: bis.read() 从输入缓存中读每个字节,减少了直接调用操作系统方法次数。
上面的使用缓存的程序每次只读一个字节,
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
While (br.readLine() != null) count++;
默认System.out是行缓存的,也就是说每结束一行,输入缓存就会刷新一次。我们可以改变默认行为,不使用行缓存以减少刷新次数来提高IO性能,如下。
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos, 2048);
PrintStream ps = new PrintStream(bos,false);
System.setOut(ps);
while (booleanCondition){
System.out.println(“blah…blah…”);
}
强烈建议使用Log4j或Apache commons logging,它们都用缓存取代默认的行缓存,对性能有很好提升。
使用NIO库,提供了如Buffer,文件内存映射,无阻塞IO操作等特性以提高程序性能。
如果可能将文件所有行一次读入Java容器,如ArrayList,HashMap,随后从内存中读取,避免多次访问磁盘。
完美汉化 2008-8-13 01:05
[b]第三篇[/b]
上篇文章写了如何优化Java IO操作,这篇打算综合考虑下Java程序性能问题。
对于那些创建昻贵的系统资源应尽量放入对象池,比如线程,数据库连接,Socket连接等。不断地创建线程使用后又销毁对性能相当不利,通过线程池重复利用线程以提高性能。单线程的程序应该考虑用多线程以提高性能。根据系统及程序的需求调整对象池的大小。
优化IO操作(见Core Java FAQ(二)):读写文件或流时使用缓存。避免用Writers/Readers处理纯ASCII字符,应用流操作代替那样会更快。避免频繁的刷新缓存。推荐使用NIO无阻塞异步IO,文件内存映射等新特性提高IO性能。
减少远程方法调用次数,一次远程调用能够完成的业务操作不要分成多个远程接口。远程方法调用是一个网络round-trip, marshalling and unmarshalling of parameters,如果远程接口设计不当会造成很大的性能问题。
有效的管理你的对象,时刻关注潜在的内存问题(用Java如果一点不考虑内存,你迟早会吃到苦头)。及时删除Collection当中没用的对象引用,避免出现内存问题。避免创建不必要的对象,尽可能的重用已创建对象,重用对象比新建对象要便宜的多,因为创建与销毁对象会占用相当的JVM时间。只要可能,你就要想办法在你的代码当中最少的创建对像,比如以下情况:
[b]1.[/b]避免在循环当中创建对象,应考虑是否可以在进入Loop前创建对象,然后使用或重用它。
[b]2.[/b]如果频繁的使用一些复杂对象的话,应考虑使用对象池,而不是每次使用每次重新创建,虽然管理对象池会带来一定的复杂性,但是对提高性能很有帮 助。
[b]3.[/b]需要加载大量对象时,可以考虑延迟加载。
一些性能方面代码编写最佳实践:
[b]1.[/b]用ArrayList,HashMap代替Vector,Hashtable,ArrayList,HashMap中的方法不是线程安全的,性能较优。只用纯数组更好。
[b]2.[/b]尽可能设置容器对象的初始化容量(如ArrayList,HashMap,StringBuffer等),因为这些对象自己会增长以容纳新的对象,所以如果你有一个很大的Map,List or StringBuffer,而且大概知道它的它的长度,你可以设置它的初始容量而减少自动的增长操作以提高速度。Map的一个增长操作是比较耗时的,它需要rehash。
[b]3.[/b]减少对象转型及instanceof操作符进行运行期对象类型检查(final Class可能会快点),使用instanceof的代码不但难看而且不易维护,参考访问者模式以避免使用instanceof。
[b]4.[/b]避免在大循环中计算常量。
[b]5.[/b]异常创建是非常昂贵的,因为它要创建详细的stack trace,所以避免使用异常来控制程序流程。建议抛预创建的异常对角,一个有效的做法是在你的异常类中定义public static final Exception 方法。
[b]6.[/b]避免使用System.out.println()
[b]7.[/b]最小化调用Date,Calendar等相关类。
[b]8.[/b]最小化在你的代码中调用JNI方法。
完美汉化 2008-8-13 01:11
[b]第四篇[/b]
Java中的内存泄漏主要出现在长生命周期的对象引用短生命周期的对象,以致垃圾收集器无法清理这些短生命周期的对象。所以开发人员应该及时从长生命周期的对象中删除短生命周期的对象。相同生命周期的对象不会出现问题,因为JAVA垃圾收集器很聪明的处理这种循环引用。
[b]检测内存泄漏:[/b]
使用工具,如JProbe, OptimizeIt等等。
使用操作系统监视器如ps,vmstat,iostat,netstat等等。
用Java Runtime 中的totalMemory(),freeMemory()写一个实用类。放在需要观察内存情况的方法前后。可以使用动态代理或AOP实现。
[b]最小化内存泄漏[/b]:
设计程序时考虑对象的生命周期,不要总是依靠JVM的特性。
Collection中的短生命周期可能会引起内存泄漏,将Collection设成NULL,垃圾收集器会清理容器内所有对象。
如果只有你使用的对象考虑使用weak references,WeakHashMap是HashMap和WeakReferences的结合。这个类在你需要一个HashMap存储信息,但是你想这些信息只有被你一个引用的时候可以被垃圾收集器清理时使用。
释放使用后的本地资源,如AWT FRAME,files,JNI等等。
完美汉化 2008-8-13 01:15
[b]第五篇[/b]
Java程序都会抛出Java Exception 或者 Error。Java exception or error不会引起Core dump(on UNIX System)或a Dr.Watson error (on WIN32systems)。一些严重的Java问题会抛出OutOfMemoryError然后JVM退出,这些堆栈信息对于确定JVM为什么异常退出很有帮助,所以我们得知道为什么OutOfMemoryError会发生?jdk1.5中有个包叫java.lang.management,其中的JMX beans可以用来管理JVM,其中一个叫MemoryMXBean。
[b]抛出OutOfMemoryError的四个原因:[/b]
JVM中的堆内存管理存在BUG,可能性不大。
没有足够的堆内存运行程序。你可以给它更多的堆内存,使用JVM参数Java -Xms1024M -Xmx1024M,注意分配太多堆内存给程序反而会降低程序运行速度。
另外一个不太常见的OutOfMemoryError发生在“PERM”内存区。程序所有运行的二进制类代码全部放在这个内存区,如果程序当有或在第三方jar中使用动态类生成技术,比如,XSLT模版动态编译成java类,J2EE 服务器,JasperReport,Jaxb等等应用反射动态生成java类,或者程序有具大数目的类都可能引起PERM区的OutOfMemoryError。Java -XX:PermSize=256M -XX:MaxPermSize=256M可以加大PERM内存区。
第四个原因也是比较常见的,就是你的程序有内存泄漏。
[b]那么,为什么JVM会 crash with a core dump or Dr.Watson error?[/b]
UNIX上的Core dump和Win32上的Dr.Watson是同一回事,JVM和其它进程一样,crash的时候core dump会创建。core dump是正在运行进程的内存映像。发生原因可能是如下其中之一:
使用JNI,本地代码中有致命BUG。
你的操作系统可能需PATCH以运行你的JVM。
你所使用的JVM实现在翻译线程,文件,SOCKET等等java byte code成本地平台操作时存在BUG。当JVM生成的本地代码中有非法操作时,操作系统会杀死JVM进程并生成一个Core dump,它是一个十六进制文件存储着进程出错时候的内存状态。这个Core dump文件是操作系统响应相关信号而生成的。操作系统用信号通知道线程或进程有适当事件发生。JVM当然也可以中断信号如SIGQUIT,打印一些堆栈信息然后继续运行,JVM能够继续运行是因为JVM有内置的debug routine,可以trap 信号SIGQUIT。但是SIGSTOP,SIGKILL会使JVM停止或终止。下面的JVM参数可以让JVM在接收到SIGQUIT信号时继续运行:Java –Xsqnopause。
完美汉化 2008-8-13 01:19
[b]第六篇[/b]
Java当中不是每个类都需要分开定义,一个类可以定义在其它类内部。如果类A定义在类B的内部,那么类A就叫内部类,类B就叫外部类。所以当你定义一个类部类,它就是外部类的成员,就像它的成员函数,成员属性等等。
什么地方应该使用内部类呢?使用内部类会使代码的维护性和阅读性下降,当你访问外部类的私有成员时,JDK编译器会在外部类创建包访问权限的成员方法供内部类访问它的私有成员,这样就留下了安全漏洞,所以一般情况下我们应避免使用内部类。只有当内部类与外部类的上下文紧密相关或者只有外部类可以访问内部类的时候使用。常见的内部类主要实现一些辅助类,就像Iterators,Comparators等等。
Member inner class
[code]public class MyStack {
private Object[] items = null;
…
public Iterator iterator() {
return new StackIterator();
}
//inner class
class StackIterator implements Iterator{
…
public boolean hasNext(){…}
}
}[/code]
Anonymous inner class
[code]public class MyStack {
private Object[] items = null;
…
public Iterator iterator() {
return new Iterator {
…
public boolean hasNext() {…}
}
}
}[/code]
Explain outer and inner classes?
1、static nested inner class or interface
只能访问外部类的静态成员,跟外部类对象没有关系。
例:
[code]//package scope
class Outside {
static class Inside{ }
}
Outside.class ,Outside$Inside.class[/code]
2、Member inner class
外部类对象创建前不能创建内部类对象。
例:
[code]class Outside{
class Inside(){}
}
Outside.class , Outside$Inside.class
[/code]
3、Local inner class
在代码块中定义,可以使用 final local variables and final method parameters.只对定义它的代码块可见。
例:
[code]class Outside {
void first() {
final int i = 5; class Inside{}
}
}
Outside.class , Outside$1$Inside.class[/code]
4、Anonymous inner class
跟Local inner class类似,只是没有类名,方法中只需一个实例时比较有用,通常用在AWT的事件模型中。例:
[code]class Outside{
void first() {
button.addActionListener ( new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.out.println(“The button was pressed!”);
}
});
}
}
Outside.class , Outside$1.class[/code]
[[i] 本帖最后由 完美汉化 于 2008-8-13 01:32 编辑 [/i]]
完美汉化 2008-8-13 01:41
[b]最后一讲[/b]
JMX(Java Management Extensions),JAVA管理扩展接口,是一个非常有用的架构,可以用于应用程序架构,如JBOSS,也可用于网络软硬件管理。如果你不熟悉JMX架构,可以到SUN的网站上去了解其技术细节。本文主要从应用角度出发,简单阐述如何构建一个基于JMX的分布式Linux服务器进程管理系统。
[b]目标[/b]
以Apache Http Server(httpd)为例,现在有四台Linux主机组成的WEB Server集群,如果没有统一的管理机制,管理员必须经常对每台服务器进行检查,根据系统负载信息,重启,关闭服务,或开一些其它的辅助服务程序。现设想有这样一种机制,管理员可以随时随地通过PC,PDA等等设备远程监视与管理这些服务器,既方便又能及时反应异常情况。
[b]架构
[/b][align=center][attach]6450[/attach][/align]
如上图所示,其中四台Linux Server是我们要管理的服务器,这里还有个Manage Server,这是我们远程管理的登录点,这样不便增加了透明性更加强了系统的安全性。其中Manage Server通过RMI与其它四台服务器进行通信。
实现
在每台Linux Server上我们运行着一个JMX Agent, 内部注册ProcessMBean,其实现具体管理逻辑并暴露相关接口。
Manage Server通过RMI与远程Agent建立连接,并调用相关MBean远程方法以达到管理目的,并以HTTP形式发布WEB服务。
Manage Server与各Agent通过JMX URL 建立连接。如:
service:jmx:rmi://localhost:5000/jndi/rmi://localhost:6000/jmxrmi
这个JMX URL可以分为如下几个部分:
[b] service:jmx:[/b] 这个是JMX URL的标准前缀,所有的JMX URL都必须以该字符串开头。
[b]rmi:[/b] 这个是connector server的传输协议,在这个url中是使用rmi来进行传输的。JSR 160规定了所有connector server都必须至少实现rmi传输,是否还支持其他的传输协议依赖于具体的实现。比如MX4J就支持soap、soap+ssl、hessian、burlap等等传输协议。
[b] localhost:5000:[/b] 这个是connector server的IP和端口,该部分是一个可选项,可以被省略掉。
[b]/jndi/rmi://localhost:6000/jmxrmi:[/b] 这个是connector server的路径,具体含义取决于前面的传输协议。比如该URL中这串字符串就代表着该connector server的stub是使用jndi api绑定在rmi://localhost:6000/jmxrmi这个地址。
如果在服务器端,也就是图上的Linux Server Agent,我们用该URL创建一个connector server,则大概流程如下:
将connect server(javax.management.remote.rmi.RMIConnectorServer)内部的server对象 (javax.management.remote.rmi.RMIJRMPServerImpl)的rmi stub export到本地的5000端口,接收外部连接通过jndi api将该stub绑定在rmi://localhost:6000/jmxrmi这个地址上,这需要在本地的6000端口上运行着一个rmiregistry,如果不存在则会抛出异常。可以看到,如果在服务器端创建connector server时,该URL的第三部分(即localhost:5000)如果省略的话,则connector server会随机任意选择一个可用的端口。
如果在客户端,也就是上图中的Manage Server,我们通过该URL创建一个connector,则大概按照如下的流程:
通过jndi api到rmi://localhost:6000/jmxrmi这个地址取回stub,stub中已经包含了真实服务器的地址,所以可以直接根据该stub连接到真实的服务器。从这个示例中我们也可以 看到,如果采用rmi作为传输协议的话,客户端需要进行两个连接。首先客户端连接到rmiregistry上得到真实服务器的stub(如 rmi://localhost:6000/rmxrmi),然后客户端再根据该stub连接到真实的服务器上(如rmi: //localhost:5000)。
[b]安全性[/b]
[code]Map env = new HashMap();
env.put(JMXConnectorServer.AUTHENTICATOR, JMXAuthenticator子类的实例);[/code]
比如在MX4J中就提供了一个mx4j.tools.remote.PasswordAuthenticator的类,通过配 置文件来匹配用户名和密码;Sun也提供了一个com.sun.jmx.remote.security.JMXPluggableAuthenticator的类通过JAAS来配置验证和权限。用户可以参照这两个类来提供自定义的实现。这里提供一个简单的示例(不带任何效验机制,仅用于示例)
[code]env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() {
public Subject authenticate(Object credentials) {
String[] sCredentials = (String[]) credentials;
String userName = sCredentials[0];
String password = sCredentials[1];
if (”test”.equals(userName) && “test”.equals(password)) {
Set principals = new HashSet();
principals.add(new JMXPrincipal(user));
return new Subject(true, principals, Collections.EMPTY_SET,
Collections.EMPTY_SET);
}
throw new SecurityException(”Authentication failed! ”
+ message);
}
});[/code]
加入这段代码后,真实的服务器必须要验证后才能访问。
[[i] 本帖最后由 完美汉化 于 2008-8-13 01:43 编辑 [/i]]