当前位置:   article > 正文

java精华(4)

snmp releaseresource
5: Synchronizer:同步装置

Java 5.0里新加了4个协调线程间进程的同步装置,它们分别是Semaphore, CountDownLatch, CyclicBarrier和Exchanger.

Semaphore:

用来管理一个资源池的工具,Semaphore可以看成是个通行证,线程要想从资源池拿到资源必须先拿到通行证,Semaphore提供的通行证数量和资源池的大小一致。如果线程暂时拿不到通行证,线程就会被阻断进入等待状态。以下是一个例子:

public class Pool {

ArrayList pool = null;

Semaphore pass = null;

public Pool(int size){

//初始化资源池

pool = new ArrayList();

for(int i=0; i

pool.add("Resource "+i);

}

//Semaphore的大小和资源池的大小一致

pass = new Semaphore(size);

}

public String get() throws InterruptedException{

//获取通行证,只有得到通行证后才能得到资源

pass.acquire();

return getResource();

}

public void put(String resource){

//归还通行证,并归还资源

pass.release();

releaseResource(resource);

}

private synchronized String getResource() {

String result = pool.get(0);

pool.remove(0);

System.out.println("Give out "+result);

return result;

}

private synchronized void releaseResource(String resource) {

System.out.println("return "+resource);

pool.add(resource);

}

}

SemaphoreTest:

public class SemaphoreTest {

public static void main(String[] args){

final Pool aPool = new Pool(2);

Runnable worker = new Runnable() {

public void run() {

String resource = null;

try {

//取得resource

resource = aPool.get();

} catch (InterruptedException ex) {

ex.printStackTrace();

}

//用resource做工作

System.out.println("I worked on "+resource);

//归还resource

aPool.put(resource);

}

};

ExecutorService service = Executors.newCachedThreadPool();

for(int i=0; i<20; i++){

service.submit(worker);

}

service.shutdown();

}

}

CountDownLatch:

CountDownLatch是个计数器,它有一个初始数,等待这个计数器的线程必须等到计数器倒数到零时才可继续。比如说一个Server启动时需要初始化4个部件,Server可以同时启动4个线程去初始化这4个部件,然后调用CountDownLatch(4).await()阻断进入等待,每个线程完成任务后会调用一次CountDownLatch.countDown()来倒计数, 当4个线程都结束时CountDownLatch的计数就会降低为0,此时Server就会被唤醒继续下一步操作。CountDownLatch的方法主要有:

await():使调用此方法的线程阻断进入等待

countDown(): 倒计数,将计数值减1

getCount(): 得到当前的计数值

CountDownLatch的例子:一个server调了三个ComponentThread分别去启动三个组件,然后server等到组件都启动了再继续。

public class Server {

public static void main(String[] args) throws InterruptedException{

System.out.println("Server is starting.");

//初始化一个初始值为3的CountDownLatch

CountDownLatch latch = new CountDownLatch(3);

//起3个线程分别去启动3个组件

ExecutorService service = Executors.newCachedThreadPool();

service.submit(new ComponentThread(latch, 1));

service.submit(new ComponentThread(latch, 2));

service.submit(new ComponentThread(latch, 3));

service.shutdown();

//进入等待状态

latch.await();

//当所需的三个组件都完成时,Server就可继续了

System.out.println("Server is up!");

}

}



public class ComponentThread implements Runnable{

CountDownLatch latch;

int ID;

/** Creates a new instance of ComponentThread */

public ComponentThread(CountDownLatch latch, int ID) {

this.latch = latch;

this.ID = ID;

}

public void run() {

System.out.println("Component "+ID + " initialized!");

//将计数减一

latch.countDown();

}

}

运行结果:

Server is starting.

Component 1 initialized!

Component 3 initialized!

Component 2 initialized!

Server is up!

CyclicBarrier:

CyclicBarrier类似于CountDownLatch也是个计数器,不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数,当线程数达到了CyclicBarrier初始时规定的数目时,所有进入等待状态的线程被唤醒并继续。CyclicBarrier就象它名字的意思一样,可看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍。CyclicBarrier初始时还可带一个Runnable的参数,此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

CyclicBarrier提供以下几个方法:

await():进入等待

getParties():返回此barrier需要的线程数

reset():将此barrier重置

以下是使用CyclicBarrier的一个例子:两个线程分别在一个数组里放一个数,当这两个线程都结束后,主线程算出数组里的数的和(这个例子比较无聊,我没有想到更合适的例子)

public class MainThread {

public static void main(String[] args)

throws InterruptedException, BrokenBarrierException, TimeoutException{

final int[] array = new int[2];

CyclicBarrier barrier = new CyclicBarrier(2,

new Runnable() {//在所有线程都到达Barrier时执行

public void run() {

System.out.println("Total is:"+(array[0]+array[1]));

}

});

//启动线程

new Thread(new ComponentThread(barrier, array, 0)).start();

new Thread(new ComponentThread(barrier, array, 1)).start();

}

}



public class ComponentThread implements Runnable{

CyclicBarrier barrier;

int ID;

int[] array;

public ComponentThread(CyclicBarrier barrier, int[] array, int ID) {

this.barrier = barrier;

this.ID = ID;

this.array = array;

}

public void run() {

try {

array[ID] = new Random().nextInt();

System.out.println(ID+ " generates:"+array[ID]);

//该线程完成了任务等在Barrier处

barrier.await();

} catch (BrokenBarrierException ex) {

ex.printStackTrace();

} catch (InterruptedException ex) {

ex.printStackTrace();

}

}

}

Exchanger:

顾名思义Exchanger让两个线程可以互换信息。用一个例子来解释比较容易。例子中服务生线程往空的杯子里倒水,顾客线程从装满水的杯子里喝水,然后通过Exchanger双方互换杯子,服务生接着往空杯子里倒水,顾客接着喝水,然后交换,如此周而复始。

class FillAndEmpty {

//初始化一个Exchanger,并规定可交换的信息类型是DataCup

Exchanger exchanger = new Exchanger();

Cup initialEmptyCup = ...; //初始化一个空的杯子

Cup initialFullCup = ...; //初始化一个装满水的杯子

//服务生线程

class Waiter implements Runnable {

public void run() {

Cup currentCup = initialEmptyCup;

try {

//往空的杯子里加水

currentCup.addWater();

//杯子满后和顾客的空杯子交换

currentCup = exchanger.exchange(currentCup);

} catch (InterruptedException ex) { ... handle ... }

}

}

//顾客线程

class Customer implements Runnable {

public void run() {

DataCup currentCup = initialFullCup;

try {

//把杯子里的水喝掉

currentCup.drinkFromCup();

//将空杯子和服务生的满杯子交换

currentCup = exchanger.exchange(currentCup);

} catch (InterruptedException ex) { ... handle ...}

}

}



void start() {

new Thread(new Waiter()).start();

new Thread(new Customer()).start();

}

}

6: BlockingQueue接口

BlockingQueue是一种特殊的Queue,若BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态直到BlocingkQueue进了新货才会被唤醒。同样,如果BlockingQueue是满的任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有新的空间才会被唤醒继续操作。BlockingQueue提供的方法主要有:

add(anObject): 把anObject加到BlockingQueue里,如果BlockingQueue可以容纳返回true,否则抛出IllegalStateException异常。

offer(anObject):把anObject加到BlockingQueue里,如果BlockingQueue可以容纳返回true,否则返回false。

put(anObject):把anObject加到BlockingQueue里,如果BlockingQueue没有空间,调用此方法的线程被阻断直到BlockingQueue里有新的空间再继续。

poll(time):取出BlockingQueue里排在首位的对象,若不能立即取出可等time参数规定的时间。取不到时返回null。

take():取出BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。

根据不同的需要BlockingQueue有4种具体实现:

ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。

LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO(先入先出)顺序排序的。LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue。

PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。

下面是用BlockingQueue来实现Producer和Consumer的例子:

public class BlockingQueueTest {

static BlockingQueue basket;

public BlockingQueueTest() {

//定义了一个大小为2的BlockingQueue,也可根据需要用其他的具体类

basket = new ArrayBlockingQueue(2);

}

class Producor implements Runnable {

public void run() {

while(true){

try {

//放入一个对象,若basket满了,等到basket有位置

basket.put("An apple");

} catch (InterruptedException ex) {

ex.printStackTrace();

}

}

}

}

class Consumer implements Runnable {

public void run() {

while(true){

try {

//取出一个对象,若basket为空,等到basket有东西为止

String result = basket.take();

} catch (InterruptedException ex) {

ex.printStackTrace();

}

}

}

}

public void execute(){

for(int i=0; i<10; i++){

new Thread(new Producor()).start();

new Thread(new Consumer()).start();

}

}

public static void main(String[] args){

BlockingQueueTest test = new BlockingQueueTest();

test.execute();

}

}

7:Atomics 原子级变量

原子量级的变量,主要的类有AtomicBoolean, AtomicInteger, AotmicIntegerArray, AtomicLong, AtomicLongArray, AtomicReference ……。这些原子量级的变量主要提供两个方法:

compareAndSet(expectedValue, newValue): 比较当前的值是否等于expectedValue,若等于把当前值改成newValue,并返回true。若不等,返回false。

getAndSet(newValue): 把当前值改为newValue,并返回改变前的值。

这些原子级变量利用了现代处理器(CPU)的硬件支持可把两步操作合为一步的功能,避免了不必要的锁定,提高了程序的运行效率。

8:Concurrent Collections 共点聚集

在Java的聚集框架里可以调用Collections.synchronizeCollection(aCollection)将普通聚集改变成同步聚集,使之可用于多线程的环境下。 但同步聚集在一个时刻只允许一个线程访问它,其它想同时访问它的线程会被阻断,导致程序运行效率不高。Java 5.0里提供了几个共点聚集类,它们把以前需要几步才能完成的操作合成一个原子量级的操作,这样就可让多个线程同时对聚集进行操作,避免了锁定,从而提高了程序的运行效率。Java 5.0目前提供的共点聚集类有:ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList和CopyOnWriteArraySet.

1.1 Java Socket编程

第一步 充分理解Socket
  1.什么是socket
  所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
  以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
  重要的Socket API:
  java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。
  . Accept方法用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后程序继续;通常"阻塞"是由循环产生的。
  . getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例,。
  . getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。
  注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
  2.如何开发一个Server-Client模型的程序
  开发原理:
  服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
  客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
  {建立服务器}
import java.net.*;
import java.io.*;
public class Server
{
private ServerSocket ss;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
public Server()
{
try
{
ss = new ServerSocket(10000);
while (true)
{
socket = ss.accept();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
String line = in.readLine();
out.println("you input is :" + line);
out.close();
in.close();
socket.close();
}
ss.close();
}
catch (IOException e)
{}
}
public static void main(String[] args)
{
new Server();
}
}
  这个程序建立了一个服务器,它一直监听10000端口,等待用户连接。在建立连接后给客户端返回一段信息,然后结束会话。这个程序一次只能接受一个客户连接。
  {建立客户端}
import java.io.*;
import java.net.*;
public class Client
{
Socket socket;
BufferedReader in;
PrintWriter out;
public Client()
{
try
{
socket = new Socket("xxx.xxx.xxx.xxx", 10000);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
BufferedReader line = new BufferedReader(new InputStreamReader(System.in));
out.println(line.readLine());
line.close();
out.close();
in.close();
socket.close();
}
catch (IOException e)
{}
}
public static void main(String[] args)
{
new Client();
}
}
  这个客户端连接到地址为xxx.xxx.xxx.xxx的服务器,端口为10000,并从键盘输入一行信息,发送到服务器,然后接受服务器的返回信息,最后结束会话。


第二步 多个客户同时连接
  在实际的网络环境里,同一时间只对一个用户服务是不可行的。一个优秀的网络服务程序除了能处理用户的输入信息,还必须能够同时响应多个客户端的连接请求。在java中,实现以上功能特点是非常容易的。
  设计原理:
  主程序监听一端口,等待客户接入;同时构造一个线程类,准备接管会话。当一个Socket会话产生后,将这个会话交给线程处理,然后主程序继续监听。运用Thread类或Runnable接口来实现是不错的办法。
  {实现消息共享}
import java.io.*;
import java.net.*;
public class Server extends ServerSocket
{
private static final int SERVER_PORT = 10000;
public Server() throws IOException
{
super(SERVER_PORT);
try
{
while (true)
{
Socket socket = accept();
new CreateServerThread(socket);
}
}
catch (IOException e)
{}
finally
{
close();
}
}
//--- CreateServerThread
class CreateServerThread extends Thread
{
private Socket client;
private BufferedReader in;
private PrintWriter out;
public CreateServerThread(Socket s) throws IOException
{
client = s;
in = new BufferedReader(new InputStreamReader(client.getInputStream(), "GB2312"));
out = new PrintWriter(client.getOutputStream(), true);
out.println("--- Welcome ---");
start();
}
public void run()
{
try
{
String line = in.readLine();
while (!line.equals("bye"))
{
String msg = createMessage(line);
out.println(msg);
line = in.readLine();
}
out.println("--- See you, bye! ---");
client.close();
}
catch (IOException e)
{}
}
private String createMessage(String line)
{
xxxxxxxxx;
}
}
public static void main(String[] args) throws IOException
{
new Server();
}
}
  这个程序监听10000端口,并将接入交给CreateServerThread线程运行。CreateServerThread线程接受输入,并将输入回应客户,直到客户输入"bye",线程结束。我们可以在createMessage方法中,对输入进行处理,并产生结果,然后把结果返回给客户。


 第三步 实现信息共享:在Socket上的实时交流
  网络的伟大之一也是信息共享,Server可以主动向所有Client广播消息,同时Client也可以向其它Client发布消息。下面看看如何开发一个可以实时传递消息的程序。
  设计原理:
  服务器端接受客户端的连接请求,同时启动一个线程处理这个连接,线程不停的读取客户端输入,然后把输入加入队列中,等候处理。在线程启动的同时将线程加入队列中,以便在需要的时候定位和取出。
  {源码}
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.*;
public class Server extends ServerSocket
{
private static ArrayList User_List = new ArrayList();
private static ArrayList Threader = new ArrayList();
private static LinkedList Message_Array = new LinkedList();
private static int Thread_Counter = 0;
private static boolean isClear = true;
protected static final int SERVER_PORT = 10000;
protected FileOutputStream LOG_FILE = new FileOutputStream("d:/connect.log", true);
public Server() throws FileNotFoundException, IOException
{
super(SERVER_PORT);
new Broadcast();
//append connection log
Calendar now = Calendar.getInstance();
String str = "[" + now.getTime().toString() + "] Accepted a connection\015\012";
byte[] tmp = str.getBytes();
LOG_FILE.write(tmp);
try
{
while (true)
{
Socket socket = accept();
new CreateServerThread(socket);
}
}
finally
{
close();
}
}
public static void main(String[] args) throws IOException
{
new Server();
}
//--- Broadcast
class Broadcast extends Thread
{
public Broadcast()
{
start();
}
public void run()
{
while (true)
{
if (!isClear)
{
String tmp = (String)Message_Array.getFirst();
for (int i = 0; i < Threader.size(); i++)
{
CreateServerThread client = (CreateServerThread)Threader.get(i);
client.sendMessage(tmp);
}
Message_Array.removeFirst();
isClear = Message_Array.size() > 0 ? false : true;
}
}
}
}
//--- CreateServerThread
class CreateServerThread extends Thread
{
private Socket client;
private BufferedReader in;
private PrintWriter out;
private String Username;
public CreateServerThread(Socket s) throws IOException
{
client = s;
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
out.println("--- Welcome to this chatroom ---");
out.println("Input your nickname:");
start();
}
public void sendMessage(String msg)
{
out.println(msg);
}
public void run()
{
try
{
int flag = 0;
Thread_Counter++;
String line = in.readLine();
while (!line.equals("bye"))
{
if (line.equals("l"))
{
out.println(listOnlineUsers());
line = in.readLine();
continue;
}
if (flag++ == 0)
{
Username = line;
User_List.add(Username);
out.println(listOnlineUsers());
Threader.add(this);
pushMessage("[< " + Username + " come on in >]");
}
else
{
pushMessage("<" + Username + ">" + line);
}
line = in.readLine();
}
out.println("--- See you, bye! ---");
client.close();
}
catch (IOException e)
{}
finally
{
try
{
client.close();
}
catch (IOException e)
{}
Thread_Counter--;
Threader.remove(this);
User_List.remove(Username);
pushMessage("[< " + Username + " left>]");
}
}
private String listOnlineUsers()
{
String s ="-+- online list -+-\015\012";
for (int i = 0; i < User_List.size(); i++)
{
s += "[" + User_List.get(i) + "]\015\012";
}
s += "-+---------------------+-";
return s;
}
private void pushMessage(String msg)
{
Message_Array.addLast(msg);
isClear = false;
}
}
}
java精华(5) - java world - Java Worldscreen.width-333)this.width=screen.width-333;" border=0>
  这就是程序运行后,多用户登陆并且输入信息后的屏幕。实现了信息的实时广播。用户输入"l"就可以列出在线人员表。

1.2 Java的内存泄漏

一 问题的提出
Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。
随着越来越多的服务器程序采用Java技术,例如JSP,Servlet, EJB等,服务器程序往往长期运行。另外,在很多嵌入式系统中,内存的总量非常有限。内存泄露问题也就变得十分关键,即使每次运行少量泄漏,长期运行之后,系统也是面临崩溃的危险。
二 Java是如何管理内存
为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
三 什么是Java中的内存泄露
下面,我们就可以描述什么是内存泄漏。在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。
下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
四 如何检测内存泄漏
最后一个重要的问题,就是如何检测Java的内存泄漏。目前,我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,我们将简单介绍Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,并且可以支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,该软件是由Java编写,因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。
当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。
在运行过程中,我们还可以从不同视角观查内存的使用情况,Optimizeit提供了四种方式:
堆视角。 这是一个全面的视角,我们可以了解堆中的所有的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化情况。
方法视角。通过方法视角,我们可以得知每一种类的对象,都分配在哪些方法中,以及它们的数量。
对象视角。给定一个对象,通过对象视角,我们可以显示它的所有出引用和入引用对象,我们可以了解这个对象的所有引用关系。
引用图。 给定一个根,通过引用图,我们可以显示从该顶点出发的所有出引用。
在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件非常麻烦的事情,它需要程序员对整个程序的代码比较清楚,并且需要丰富的调试经验,但是这个过程对于很多关键的Java程序都是十分重要的。
综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。

1.3 抽象类与接口的区别

有以下几个方面:

1 自身的定义不同

抽象类可以有属性,接口即使有属性也必须是为常数

抽象类是用来继承的,接口是用来实现的

2 与使用他们的类的关系不同

抽象类的方法必须实现,而接口则可以不实现

抽象类与子类是父子关系,而接口跟类是没有任何关系的,接口可以让任何类去实现

他们的本质区别体现在他们对于一个系统的模型的理解不同

1.4 Java变量类型间的相互转换

我们知道,Java的数据类型分为三大类,即布尔型、字符型和数值型,而其中数值型又分为整型和浮点型;相对于数据类型,Java的变量类型为布尔型 boolean;字符型char;整型byte、short、int、long;浮点型float、double。其中四种整型变量和两种浮点型变量分别 对应于不同的精度和范围。此外,我们还经常用到两种类变量,即String和Date。对于这些变量类型之间的相互转换在我们编程中经常要用到,在我们今 天的这篇文章中,我们将来看看如何实现这些转换。
一、 整型、实型、字符型变量中的相互转换
在Java中整型、实型、字符型被视为同一类数据,这些类型由低级到高级分别为(byte,short,char)??int??long??float??double,低级变量可以直接转换为高级变量,例如,下面的语句可以在Java中直接通过:
byte b;
int i=b;
而将高级变量转换为低级变量时,情况会复杂一些,你可以使用强制类型转换。即你必须采用下面这种语句格式:
int i;
byte b=(byte)i;
可以想象,这种转换肯定可能会导致溢出或精度的下降,因此我们并不推荐使用这种转换。
二、Java的包装类
在 我们讨论其它变量类型之间的相互转换时,我们需要了解一下Java的包装类,所谓包装类,就是可以直接将简单类型的变量表示为一个类,在执行变量类型的相 互转换时,我们会大量使用这些包装类。Java共有六个包装类,分别是Boolean、Character、Integer、Long、Float和 Double,从字面上我们就可以看出它们分别对应于 boolean、char、int、long、float和double。而String和 Date本身就是类。所以也就不存在什么包装类的概念了。
三、简单类型变量和包装类之间的相互转换
简单类型的变量转换为相应的包装类,可以利用包装类的构造函数。即:
Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long value)、Float(float value)、Double(double value)
而 在各个包装类中,总有形为××Value()的方法,来得到其对应的简单类型数据。利用这种方法,也可以实现不同数值型变量间的转换,例如,对于一个双精 度实型类,intValue()可以得到其对应的整型变量,而doubleValue()可以得到其对应的双精度实型变量。
四、String类和其它数据类型的相互转换
对于上面的这些包装类,除了Character以外,都有可以直接使用字符串参数的构造函数,这也就使得我们将String类转换为这些数据类型变得相当之简单,即:
Boolean(String s)、Integer(String s)、Long(String s)、Float(String s)、Double(String s)
而将String类转换为Date类也可以使用这样的构造函数:Date(String s)
现 在我们还剩下一个字符型变量,事实上String类可以理解为一个char型数组,因此我们可以在String类中找到这样的方法来实现这种转换: charAt(int index)可以得到String类中某一位置上的字符,toCharArray()更可以将整个String类转换成一个 char的数组。
对于所有的包装类都存在一个名为toString()的方法可以将其转换成对应的String类,而对于整型类和长整型类,还可 以使用toBinaryString(int i)、toHexString(int i)、toOctalString(int i)分别以二进制、十 六进制和八进制的形式进行到String类的转换。
五、将字符型直接做为数值转换为其它数据类型
将字符型变量转换为数值型变量实 际上有两种对应关系,在我们在第一部分所说的那种转换中,实际上是将其转换成对应的ASCII码,但是我们有时还需要另一种转换关系,例如,‘1’就是指 的数值1,而不是其ASCII码,对于这种转换,我们可以使用Character的getNumericValue(char ch)方法。
六、Date类与其它数据类型的相互转换
整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就在两者之间建立了一个对应关系,在作这种转换时,你可以使用Date类构造函数的三种形式:
Date(int year, int month, int date):以int型表示年、月、日
Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分
Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒
在长整型和Date类之间有一个很有趣的对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。对于这种对应关系,Date类也有其相应的构造函数:Date(long date)
获 取Date类中的年、月、日、时、分、秒以及星期你可以使用Date类的getYear()、getMonth()、getDate()、 getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以将其理解为将Date类转换成int。
而Date类的getTime()方法可以得到我们前面所说的一个时间对应的长整型数,与包装类一样,Date类也有一个toString()方法可以将其转换为String类。
在Java的数据类型转换中,你还有一些其它方法可用,但是,上面所介绍的这些方法对于你的实际编程已经足够了,不是吗?

2 JAVA与WEB
2.1 JMX规范
2.1.1 JMX概述

JMX--Java Management Extensions,即Java管理扩展,是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
JMX体系结构分为以下四个层次:
1)设备层(Instrumentation Level):主要定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册。该层还定义了通知机制以及一些辅助元数据类。
2)代理层(Agent Level):主要定义了各种服务以及通信模型。该层的核心是一个MBean服务器,所有的管理构件都需要向它注册,才能被管理。注册在MBean服务器上管理构件并不直接和远程应用程序进行通信,它们通过协议适配器和连接器进行通信。而协议适配器和连接器也以管理构件的形式向MBean服务器注册才能提供相应的服务。
3)分布服务层(Distributed Service Level):主要定义了能对代理层进行操作的管理接口和构件,这样管理者就可以操作代理。然而,当前的JMX规范并没有给出这一层的具体规范。
4)附加管理协议API:定义的API主要用来支持当前已经存在的网络管理协议,如SNMP、TMN、CIM/WBEM等。

2.1.2 设备层(Instrumentation Level)

该层定义了如何实现JMX管理资源的规范。一个JMX管理资源可以是一个Java应用、一个服务或一个设备,它们可以用Java开发,或者至少能用Java进行包装,并且能被置入JMX框架中,从而成为JMX的一个管理构件(Managed Bean),简称MBean。管理构件可以是标准的,也可以是动态的,标准的管理构件遵从JavaBeans构件的设计模式;动态的管理构件遵从特定的接口,提供了更大的灵活性。
该层还定义了通知机制以及实现管理构件的辅助元数据类。

2.1.2.1 管理构件(MBean)

在JMX规范中,管理构件定义如下:它是一个能代表管理资源的Java对象,遵从一定的设计模式,还需实现该规范定义的特定的接口。该定义了保证了所有的管理构件以一种标准的方式来表示被管理资源。
管理接口就是被管理资源暴露出的一些信息,通过对这些信息的修改就能控制被管理资源。一个管理构件的管理接口包括:
1)能被接触的属性值;
2)能够执行的操作;
3)能发出的通知事件;
4)管理构件的构建器。
管理构件通过公共的方法以及遵从特定的设计模式封装了属性和操作,以便暴露给管理应用程序。例如,一个只读属性在管理构件中只有Get方法,既有Get又有Set方法表示是一个可读写的属性。
其余的JMX的构件,例如JMX代理提供的各种服务,也是作为一个管理构件注册到代理中才能提供相应的服务。
JMX对管理构件的存储位置没有任何限制,管理构件可以存储在运行JMX代理的Java虚拟机的类路径的任何位置,也可以从网络上的任何位置导入。
JMX定义了四种管理构件:标准、动态、开放和模型管理构件。每一种管理构件可以根据不同的环境需要进行制定。
1.标准管理构件
标准管理构件的设计和实现是最简单的,它们的管理接口通过方法名来描述。标准管理构件的实现依靠一组命名规则,称之为设计模式。这些命名规则定义了属性和操作。检查标准管理构件接口和应用设计模式的过程被称为内省(Introspection)[22]。JMX代理通过内省来查看每一个注册在MBean 服务器上的管理构件的方法和超类,看它是否遵从一定设计模式,决定它是否代表了一个管理构件,并辨认出它的属性和操作。
2.动态管理构件
动态管理构件提供了更大的灵活性,它可以在运行期暴露自己的管理接口。它的实现是通过实现一个特定的接口DynamicMBean(如下图)。

java精华(5) - java world - Java World


JMX代理通过getMBeanInfo方法来获取该动态管理构件暴露的管理接口,该方法返回的对象是MbeanInfo类的实例,包含了属性和操作的签名。由于该方法的调用是发生在动态管理构件向MBean服务器注册以后,因此管理接口是在运行期获取的。不同于标准管理构件,JMX代理不需要通过内省机制来确定动态管理构件的管理接口。由于DynamicMBean的接口是不变的,因此可以屏蔽实现细节。由于这种在运行期获取管理接口的特性,动态管理构件提供了更大的灵活性。
3.开放管理构件
开放管理构件是一种专门化的动态管理构件,其中所有的与该管理构件相关的参数、返回类型和属性都围绕一组预定义的数据类型(String、Integer、Float 等)来建立,并且通过一组特定的接口来进行自我描述。JMX代理通过获得一个OpenMBeanInfo对象来获取开放管理构件的管理接口,OpenMBeanInfo是MbeanInfo的子类。
4.模型管理构件
模型管理构件也是一种专门化的动态管理构件。它是预制的、通用的和动态的 MBean 类,已经包含了所有必要缺省行为的实现,并允许在运行时添加或覆盖需要定制的那些实现。JMX规范规定该类必须实现为javax.management.modelmbean.RequiredModelMBean,管理者要做的就是实例化该类,并配置该构件的默认行为并注册到JMX代理中,即可实现对资源的管理。JMX代理通过获得一个ModelMBeanInfo对象来获取管理接口。
模型管理构件具有以下新的特点[23]:
1)持久性
定义了持久机制,可以利用Java的序列化或JDBC来存储模型MBean的状态。
2)通知和日志功能
能记录每一个发出的通知,并能自动发出属性变化通知。
3)属性值缓存
具有缓存属性值的能力。

2.1.2.2 通知模型

一个管理构件提供的管理接口允许代理对其管理资源进行控制和配置。然而,对管理复杂的分布式系统来说,这些接口只是提供了一部分功能。通常,管理应用程序需要对状态变化或者当特别情况发生变化时作出反映。
为此,JMX定义了通知模型。通知模型仅仅涉及了在同一个JMX代理中的管理构件之间的事件传播。JMX通知模型依靠以下几个部分:
1)Notification,一个通用的事件类型,该类标识事件的类型,可以被直接使用,也可以根据传递的事件的需要而被扩展。
2)NotificationListener接口,接受通知的对象需实现此接口。
3)NotificationFilter接口,作为通知过滤器的对象需实现此接口,为通知监听者提供了一个过滤通知的过滤器。
4)NotificationBroadcaster接口,通知发送者需实现此接口,该接口允许希望得到通知的监听者注册。
发送一个通用类型的通知,任何一个监听者都会得到该通知。因此,监听者需提供过滤器来选择所需要接受的通知。
任何类型的管理构件,标准的或动态的,都可以作为一个通知发送者,也可以作为一个通知监听者,或两者都是。

2.1.2.3 辅助元数据类

辅助元数据类用来描述管理构件。辅助元数据类不仅被用来内省标准管理构件,也被动态管理构件用来进行自我描述。这些类根据属性、操作、构建器和通告描述了管理接口。JMX代理通过这些元数据类管理所有管理构件,而不管这些管理构件的类型。
部分辅助元类如下:
1)MBeanInfo--包含了属性、操作、构建器和通知的信息。
2)MBeanFeatureInfo--为下面类的超类。
3)MBeanAttributeInfo--用来描述管理构件中的属性。
4)MBeanConstructorInfo--用来描述管理构件中的构建器。
5)MBeanOperationInfo--用来描述管理构件中的操作。
6)MBeanParameterInfo--用来描述管理构件操作或构建器的参数。
7)MBeanNotificationInfo--用来描述管理构件发出的通知。

2.1.3 代理层

代理层是一个运行在Java虚拟机上的管理实体,它活跃在管理资源和管理者之间,用来直接管理资源,并使这些资源可以被远程的管理程序所控制。代理层由一个MBean服务器和一系列处理被管理资源的服务所组成。下图表示了代理层的组成:

java精华(5) - java world - Java World


2.1.3.1 MBean服务器

Mbean服务器为代理层的核心,设备层的所有管理构件都在其注册,管理者只用通过它才能访问管理构件。
管理构件可以通过以下三种方法实例化和注册:
1)通过另一个管理构件
2)管理代理本身
3)远程应用程序
注册一个管理构件时,必须提供一个唯一的对象名。管理应用程序用这个对象名进行标识管理构件并对其操作。这些操作包括:
1)发现管理构件的管理接口
2)读写属性值
3)执行管理构件中定义的操作
4)获得管理构件发出的通告
5)基于对象名和属性值来查询管理构件

2.1.3.2 协议适配器和连接器

MBean服务器依赖于协议适配器和连接器来和运行该代理的Java虚拟机之外的管理应用程序进行通信。协议适配器通过特定的协议提供了一张注册在MBean服务器的管理构件的视图。例如,一个HTML适配器可以将所有注册过的管理构件显示在Web 页面上。不同的协议,提供不同的视图。
连接器还必须提供管理应用一方的接口以使代理和管理应用程序进行通信,即针对不同的协议,连接器必须提供同样的远程接口来封装通信过程。当远程应用程序使用这个接口时,就可以通过网络透明的和代理进行交互,而忽略协议本身。
适配器和连接器使MBean服务器与管理应用程序能进行通信。因此,一个代理要被管理,它必须提供至少一个协议适配器或者连接器。面临多种管理应用时,代理可以包含各种不同的协议适配器和连接器。
当前已经实现和将要实现的协议适配器和连接器包括:
1)RMI连接器
2)SNMP协议适配器
3)IIOP协议适配器
4)HTML协议适配器
5)HTTP连接器

2.1.3.3 代理服务

代理服务可以对注册的管理构件执行管理功能。通过引入智能管理,JMX可以帮助我们建立强有力的管理解决方案。代理服务本身也是作为管理构件而存在,也可以被MBean服务器控制。
JMX规范定义了代理服务有:
1)动态类装载--通过管理小程序服务可以获得并实例化新的类,还可以使位于网络上的类库本地化。
2)监视服务--监视管理构件的属性值变化,并将这些变化通知给所有的监听者。
3)时间服务--定时发送一个消息或作为一个调度器使用。
4)关系服务--定义并维持管理构件之间的相互关系。
1.动态类装载
动态类装载是通过m-let(management applet)服务来实现的,它可以从网络上的任何URL处下载并实例化管理构件,然后向MBean服务器注册。在一个M-let服务过程中,首先是下载一个m-let文本文件,该文件是XML格式的文件,文件的内容标识了管理构件的所有信息,比如构件名称、在MBean服务器中唯一标识该构件的对象名等。然后根据这个文件的内容,m-let服务完成剩余的任务。下图例示这一过程:

java精华(5) - java world - Java World


2.监视服务
通过使用监视服务,管理构件的属性值就会被定期监视,从而保证始终处于一个特定的范围。当监视的属性值的变化超出了预期定义的范围,一个特定的通告就会发出。JMX规范当前规定了三种监视器:
1)计数器监视器,监视计数器类型的属性值,通常为整型,且只能按一定规律递增。
2)度量监视器,监视度量类型的属性值,通常为实数,值能增能减。
3)字符串监视器,监视字符串类型的属性值。
每一个监视器都是作为一个标准管理构件存在的,需要提供服务时,可以由相应的管理构件或远程管理应用程序动态创建并配置注册使用。
下图例示了计数器监视器的使用情况:

java精华(5) - java world - Java World


3.时间服务
时间服务可以在制定的时间和日期发出通告,也可以定期的周期性的发出通告,依赖于管理应用程序的配置。时间服务也是一个管理构件,它能帮助管理应用程序建立一个可配置的备忘录,从而实现智能管理服务。
4.关系服务
JMX规范定义了管理构件之间的关系模型。一个关系是用户定义的管理构件之间的N维联系。
关系模型定义如下一些术语:
1)角色:就是是一个关系中的一类成员身份,它含有一个角色值。
2)角色信息:描述一个关系中的一个角色。
3)关系类型:由角色信息组成,作为创建和维持关系的模板。
4)关系:管理构件之间的当前联系,且必须满足一个关系类型的要求。
5)角色值:在一个关系中当前能满足给定角色的管理构件的列表。
6)关系服务:是一个管理构件,能接触和维持所有关系类型和关系实例之间的一致性。
在关系服务中,管理构件之间的关系由通过关系类型确定的关系实例来维护。仅仅只有注册到MBean服务器上并且能被对象名标识的管理构件才能成为一个关系的成员。关系服务从来就不直接操作它的成员--管理构件,为了方便查找它仅仅提供了对象名。
关系服务能锁定不合理关系类型的创建,同样,不合理的关系的创建也会被锁定。角色值的修正也要遵守一致性检查。
由于关系是定义在注册的管理构件之间的联系,所以当其中的管理构件卸载时,就会更改关系。关系服务会自动更改角色值。所有对关系实例的操作比如创建、更新、删除等都会使关系服务发出通告,通告会提供有关这次操作的信息。
JMX关系模型只能保证所有的管理构件满足它的设计角色,也就是说,不允许一个管理构件同时出现在许多关系中。

2.1.4 分布服务层

当前,SUN并没有给出这一层的具体规范,下面给出的只是一个简要描述。
该层规定了实现JMX应用管理平台的接口。这一层定义了能对代理层进行操作的管理接口和组件。这些组件能:
1)为管理应用程序提供一个接口,以便它通过一个连接器能透明和代理层或者JMX管理资源进行交互。
2)通过各种协议的映射(如SNMP、HTML等),提供了一个JMX代理和所有可管理组件的视图。
3)分布管理信息,以便构造一个分布式系统,也就是将高层管理平台的管理信息向其下众多的JMX代理发布。
4)收集多个JMX 代理端的管理信息并根据管理终端用户的需要筛选用户感兴趣的信息并形成逻辑视图送给相应的终端用户。
5)提供了安全保证。
通过管理应用层和另一管理代理和以及他的设备层的联合,就可以为我们提供一个完整的网络管理的解决方案。这个解决方案为我们带来了独一无二的一些优点:轻便、根据需要部署、动态服务、还有安全性。

2.1.5 附加管理协议API

该层提供了一些API来支持当前已经存在的一些管理协议。
这些附加的协议API并没有定义管理应用的功能,或者管理平台的体系结构,他们仅仅定义了标准的Java API和现存的网络管理技术通信,例如SNMP。
网络管理平台和应用的开发者可以用这些API来和他们的管理环境进行交互,并将这个交互过程封装在一个JMX管理资源中。例如,通过SNMP可以对一个运行有SNMP代理的交换机进行管理,并将这些管理接口封装成为一个管理构件。在动态网络管理中,可以随时更换这些管理构件以适应需求。
这些API可以帮组开发者根据最通常的工业标准来部署他们的管理平台和应用。新的网路管理的解决方案可以和现存的基础结构合为一体,这样,现存的网络管理也能很好的利用基于Java技术的网络管理应用。
这些API目前在JCP(Java Community Process)内作为独立的JSR(Java Specification Request)开发。
他们包括:
1)SNMP Manager API
2)CIM/WBEM manager and protocol API

2.1.6 JMX的当前实现及应用

自从SUN发布了JMX规范,许多大公司纷纷行动起来,实现规范或者实现相应的基于JMX的网络管理系统,下面列出了当前的主要实现及应用情况:
1)SUN为JMX规范了作出了相应的参考实现,并在此基础上开发了一个全新的用于网络管理的产品JDMK(Java动态管理工具集),其中定义了资源的开发过程和方法、动态JMX代理的实现、远程管理应用的实现。同时,JDMK也提供了一个完整的体系结构用来构造分布式的网络管理系统,并提供了多种协议适配器和连接器,如SNMP协议适配器、HTML协议适配器、HTTP连接器、RMI连接器。
2)IBM Tivoli实现了JMX规范的产品为TivoliJMX,它为JAVA管理应用程序和网络提供了架构、设计模式、一些API集和一些服务。
3)Adventnet开发的关于JMX的产品为AdventNet Agent Toolkit,它使得定义新的SNMP MIB、开发JMX和Java SNMP Agent的过程自动化。
4)JBoss实现的J2EE应用服务器以JMX为微内核,各个模块以管理构件的形式提供相应的服务。
5)BEA的Weblogic应用服务器也将JMX技术作为自己的管理基础。
6)金蝶的Apusic也是一个以JMX为内核开发出的J2EE应用服务器。

2.1.7 小结

本文详细介绍了JMX规范。JMX体系结构分为四层,即设备层、代理层、分布服务层和附加协议API。但SUN当前只实现了前两层的具体规范,其余的规范还在制定当中。JMX代理要和远程应用程序通信,需要提供至少一个连接器和协议适配器。(源码网整理:www.codepub.com)

2.2 应用 JMX 最佳实践

构建 Java 应用程序需要许多复杂的分布式组件。现今,几乎所有的应用程序都要连接到遗留系统或其他 IT 资源。这些应用程序的分布式本质,为 IT 提出了一个难以置信的挑战,即一旦开发出一个应用程序,就要担负起维护该应用程序及其所有相关程序的重担。



  由于企业开始采用面向服务的体系结构( Service-Oriented Architectures , SOA ),问题变得进一步复杂化了。 SOA 引入了一种设计风格,即把应用程序公开为,以松散耦合方式连接的服务。在 SOA 方法中,通常使用不同的编程语言和平台,来开发应用程序组件。在某些情况下,客户端和服务提供者之间的连接,直到运行时都无法确定。

  寄希望于利用 SOA 的企业,现在需要一种更好的方式,来管理它们的分布式应用程序和服务。对处理现今应用程序异构和动态的本质来说,使用既定的底层管理技术(如 SNMP )已经不能满足需求。管理需要面向服务的风格—— META Group 将此称为面向服务的管理体系结构( Service-Oriented Management Architecture )或 (SOMA) 。( 参见 参考资料 )

  SOMA 允许异构的托管系统和管理应用程序和平共处。面向服务的管理风格,可以消除现存的人工屏障,这些屏障,是由于依赖特定平台上的特定管理 API 而造成的。让我们进一步考察,如何通过 Java 管理扩展( Java Management Extension , JMX ) API ,而在 Java 中实现 SOMA 。

  除了支持在管理产品之间进行更好的集成之外, SOMA 还使得开发自定义管理应用程序变得更加轻松。尽管企业通常依赖于开箱即用的管理解决方案,但也需要构建用于监控管理数据的,特定子集的自定义工具板。 SOA 风格的管理将使一个团队,能够使用反馈自 Web 服务的数据,来快速构建管理应用程序。 Web 服务是 SOA 实现中使用的常见技术。

SOMA 表述
  区分管理接口和管理实现是相当重要的。 SOMA 中提出的设计原则,主要与托管系统和被托管应用程序之间的接口有关。关于在公开一项托管资源的过程中,所使用的底层实现, SOMA 没有任何表述。一个应用程序可能需要使用特定的 API ,并借助不同的管理接口进行公开。例如, Java 开发人员可以使用 JMX ,在他们的应用程序中增加易管理性。 JMX 使开发人员可以在他们的应用程序中使用 JMX MBean ,这样 JMX Mbean 服务器就可以发现并访问这些托管资源。

  JMX 不仅仅是一个编程 API ,它还定义了一个包括监控和管理服务的体系结构,以及一个包括连接器和适配器的分布层。开发人员可以使用标准的 RMI 连接器,来外部公开管理接口。然而,如果您希望公开更多面向服务的管理接口,那么使用 RMI 连接器并非最佳方法。

  现在已经提出了几个用于解决 SOMA 问题的标准。 Hewlett-Packard (HP) 所进行的早期工作,导致了第一批基于 SOA 的管理标准的出现,其中之一是: Web 服务管理框架( Web Services Management Framework , WSMF )。 HP 把 WSMF 提供给了 Web Services Distributed Management (WSDM) , WSDM 是一个 OASIS 技术委员会,创建它的目的是,为用于可管理资源的 Web 服务接口定义规范。

  我们相信,业界会为 JMX 和其他基于 SOA 的管理标准开发 WSDM 协议适配器的。一般性的概念是, WSDM 协议适配器将支持 WSDM 客户端或管理应用程序,来使用 Web 服务协议与 JMX Mbean 连接(参见 图 1 )。

java精华(5) - java world - Java World
图 1. 从 JMX 到 WSDM 的连通性

  通常, JMX-WSDM 协议适配器会使用 Web 服务协议,来支持 WSDM 客户端或管理应用程序,与 JMX Mbean 连接。

  如果开发人员希望通过 JMX 来实现易管理性,那么他们是如何实现 SOMA 的呢?幸运的是,有许多良好的证据点,演示了从 JMX 到 SOA 管理风格的映射。例如, HP 发布了 HP OpenView Smart Plug-In (SPI) ,它可以管理和监控 WebLogic Integration 中的业务流程(参见参考资料) SPI 不得不包括由 BEA Weblogic Integration 公开的 JMX Mbean 和基于 WSMF 的接口之间的一座桥梁。

  从我们自己的经验出发,我们已经找出了,在把 JMX 映射为 Web 服务过程中的几处障碍。要想克服这些难题,我们需要考虑服务和松散耦合体系结构方面的问题。我们必须应用一些设计原则,比如简单性、模块性和互操作性。

  例如,假定一个典型的 JMX Mbean 与客户端有着十分紧密的耦合,公开了有关可管理资源的许多底层细节。 JMX Mbean 可能映射为 Java 类或 Enterprise JavaBean (EJB) 。我们不想把这个 Mbean 映射为单个的 Web 服务端点。相反, SOA 需要一个用于公开易管理性的、更加粗粒度的方法。

  Java 和 Web 服务使用的数据类型之间的互操作性,也是必须解决的一个主要难题。我们发现, JMX 接口使用的数据类型,不能自动转换为 Web 服务的数据类型。另外, JMX Mbean 可以向 Java 对象返回一个远程引用,这在 Web 服务世界中没有相对应的部分。

管理挑战
  我们研究 JMX 定义的编程模型和体系结构时,发现了编程接口和 管理模型 之间的明显区别。 JMX 提供了一个非常灵活且功能强大的 API ,用于实现应用程序的易管理性,但是单独使用 JMX 无法定义或利用任何特定的管理模型。

  建立良好的管理模型,对于功能丰富的管理应用程序来说是必不可少的。如果没有这种管理模型,跨应用程序一致地提取和处理管理数据,就将成为一大挑战。

  最后,在 JMX 中尚未完全支持的新兴 Web 服务管理规范中,定义了几种重要的管理抽象。例如, WSDM Management Using Web Services (MUWS) 包括一个线级规范,用于基于 Web 服务技术的交换管理信息。这个规范对 Metrics, ResourceState 和 Relationships 的管理功能进行了建模(参见 图 2 )。

java精华(5) - java world - Java World
图 2. 管理功能

  这个规范对 Metrics, ResourceState 和 Relationships 的管理功能进行了建模。

  JMX 无法充分地对许多此类功能建模。例如,尽管 JMX 为 Mbean 之中的关系定义了一个 Relation 服务,但它使用起来还是相当的复杂,而且很少在实践中实现。 JMX 也不直接支持像 Metrics 和 State 这样的功能。例如, JMX 中并没有预定义的类,来代表不同类别的量度。

  WSDM 的这些缺点,使从 JMX 到 SOA 的易管理性接口的自动转换,成为了一个大大的难题。为了帮助您克服一部分此类难题,我们给出了一系列的 5 个最佳实践,用于为 WSDM 作准备的 JMX 开发。我们相信,结合这些实践,将会开发出更加易于管理的应用程序,而与您是否计划使用 WSDM 无关。

  最佳实践 1 :从管理模型开始。 管理模型 定义了要交换的管理信息,以及这些信息的底层语义。使用管理模型,对于确保易于发现、标识和监控托管资源的健康和可用性是必不可少的。管理模型对于确保,可以在运行时统一地配置和控制应用程序来说,同样很重要。

  应用程序是不会孤立存在的,它们和其他应用程序、系统以及网络组件都有着相关性。即使主要考虑一个应用程序时,应用程序的易管理性也必须把这些相关性考虑在内。管理模型应该捕捉这些相关性和关系。

  创建一个管理模型,要求您在软件生命周期的早期,考虑到易管理性的需求。通常,开发人员总是在事后才想起易管理性的问题。为了在构建 SOMA 方面获得真正的成功,您必须从一开始就考虑易管理性和管理模型。事实上,无论您使用何种技术来公开易管理性,一个优秀的管理模型总是会为您带来好处的。在开发管理模型的过程中,您应该询问一些问题。什么是托管 资源 ?什么是托管资源的 状态 ,如何去控制它?托管资源之间存在 关系 吗?您需要跟踪什么特定的业务或性能 量度 ?应该公开什么样的额外管理 属性 和 操作 ?托管资源关心的是什么事件和消息 通知 ?

定义模型
  理解这些问题的答案,能够帮助您定义一个可以跨 IT 基础架构利用的管理模型。您还应该从操作人员的角度考虑这个管理模型,因为操作人员必须在部署应用程序之后对其进行管理。

  记住,应用程序公开的管理模型和它的内部结构不是一回事。前者的目标是允许外部管理系统有效地监控、配置和控制应用程序,而后者的目标则是实现业务功能。

  例如,我们设想一台 Web 应用服务器,它允许部署多个 Web 应用程序或 Web 模块,每个 Web 模块由一个或多个 servlet 组成(参见 图 3 中高度简化的系统管理模型)。

java精华(5) - java world - Java World
图 3. 管理模型

  在高度简化的系统管理模型中,一台 Web 应用服务器允许部署多个 Web 应用程序或 Web 模块,而每个 Web 模块由一个或多个 servlet 组成。

  这个模型为每项可管理资源,都定义了状态、通知、量度、属性和操作。我们将利用这个过分简化的模型,围绕在 SOMA 中使用 JMX 实现易管理性,来应用另外的最佳实践。另外,我们还开发了 实现这个模型的完整源代码 。

  最佳实践 2 :设计互操作性。 拥有一个定义良好的管理模型,是走向 SOMA 的第一步。 Java 社区已经意识到了这种需要,并且已经通过 JSR 77 和 174 为 J2SE 和 J2EE 平台定义了管理模型。

  JSR 174 现在是 J2SE 5.0 规范的一部分,它提供了一种为 Java 虚拟机( Java Virtual Machine , JVM )公开管理模型的方式。它引入了平台 MXBean 的概念,这个概念是用于建模,代表特定的 JVM 监控指示器的,专门 JMX MBean 。这些 MXBean 可以用于监控内存使用、线程争用问题、类装载行为和垃圾收集频率。

  所有 MXBean 都被实现为 OpenMBean ,这是一个提供增强级别互操作性的 JMX Mbean 类型。 OpenMBean 限制了 Mbean 接口中某些数据类型的使用。这种限制最小化了客户端对于访问 Mbean 的需求,进一步确保了可以使用 XML 轻松访问和操作这些 MBean ,同时不用求助于特定的封送和解除封送逻辑。

  所有 MXBean 操作和属性必须遵循特定的数据类型集合,叫做开放类型( open type ) ,它包括原始类型 ( int, long 和 boolean )、枚举、 CompositeData 类型和 Map and List 类型。 清单 1 显示了一个遵从这些数据类型要求的 ServletMXBean 接口的例子。

  注意, J2SE 5 天生就不支持从用户定义的 MXBean 到 Open Mbean 的映射,这意味着您无法引入您自己的 MXBean ,并指望它的行为像平台 MXBean 一样。可以开发一个一般类集合,来支持把用户定义的 MXBean 作为 Open Mbean 注册到 MBeanServer ,而且我们希望这些类将通过 JCP 过程变为可用。

  即使您没有直接使用平台 MXBean ,您仍然可以把您的 JMX MXBean 设计为符合 Open Mbean 的模型。最终结果是一个互操作性更强,并满足 SOMA 需要的 JMX 接口。

补充 JMX
  最佳实践 3 :利用 J2EE 管理机制。 JSR 77 提出了一个为 J2EE 平台公开管理信息的管理模型。模型支持管理大量 J2EE 资源的能力,包括 EJB 、 Web 容器、 JMS 和 JDBC 连接。该模型可以用于收集、监控和控制有关应用服务器的运行时信息。这个通用模型支持使用管理工具,轻松地管理多个 J2EE 的厂商实现。

  JSR 77 定义了许多抽象,在处理性能统计信息、应用程序状态和关系方面,对 JMX 进行了补充。它定义了 Statistic 接口,用于对 J2EE 组件的性能数据建模。例如,模型定义了一个 EJBStats 接口,为所有的 EJB 组件指定了统计信息。这个接口公开了基本的 CountStatistics ,用于跟踪,创建和删除的对象的数目:

public interface EJBStats extends

Stats {

CountStatistic getCreateCount();

CountStatistic getRemoveCount();

  可以通过 StateManageable 接口来管理一个对象的状态。您可以查询资源的状态,并启动和停止特定的组件。另外, JSR 77 为模型中的代表关系定义了一个基本的惯例。可以定义包容关系,在这种关系中,特定的容器可以维护一个托管对象的数组。

  我们可以将多个此类设计原则,应用到我们的管理模型例子(参见 图 4)。我们说明了如何增强 MXBean ,来支持状态管理、事件、规格和包容的能力。参考前面 清单 1 中的源代码,您会发现 Servlet MXBean 接口是按照前面描述的模型,来定义 Servlet Managed 对象的易管理性接口的。

java精华(5) - java world - Java World
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/975768
推荐阅读
相关标签
  

闽ICP备14008679号