Chinaunix首页 | 论坛 | 博客
  • 博客访问: 534874
  • 博文数量: 260
  • 博客积分: 10435
  • 博客等级: 上将
  • 技术积分: 1939
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-24 14:50
文章分类

全部博文(260)

文章存档

2011年(22)

2010年(209)

2009年(29)

我的朋友

分类: Java

2010-12-19 13:30:10

HotSpot是较新的Java虚拟机技术,用来代替JIT(Just in Time)技术,可以大大提高Java运行的性能。

  Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢。而该技术将常用的部分代码编译为本地(原生,native)代码,这样显著提高了性能。用于版和标准版的HotSpot有所不同。

  其他的Java虚拟机也有类似的技术。

  HotSpot JVM 参数可以分为标准参数(standard options)和非标准参数(non-standard options)。

  标准参数相对稳定,在JDK未来的版本里不会有太大的改动。

  非标准参数则有因升级JDK而改变的可能。

  标准参数

  -client

  使用Java HotSpot 客户端版VM。

  -server

  使用Java HotSpot 版VM。如果是64位的JDK,默认只有server版,所以以上两个参数对64位版本JDK无效。

  -agentlib: libname [=options]

  加载本地代理函数库, e.g.

  -agentlib:jdwp=help

  -agentpath :pathname [=options]

  使用给定的路径加载本地代理库。

  -classpath classpath

  -cp classpath

  不用说了。

  -Dproperty =value

  设置一个系统属性。

  -d32

  -d64

  要求程序在32位或64位下跑,未来这个参数可能有变。

  -enableassertions [:"..." | : ]

  -ea [:"..." | : ]

  开启断言。

  -disableassertions [:"..." | : ]

  -da [:"..." | : ]

  关闭断言。

  -enablesystemassertions

  - esa

  启动所有系统类的断言。

  -disablesystemassertions

  -dsa

  关闭所有系统类的断言。

  -jar

  这个也没什么说的。

  -javaagent :jarpath [=options]

  加载Java 程序语言代理

  -verbose:class

  输出每个加载的类详细信息。

  -verbose:gc

  输出GC的详细信息。

  -verbose:jni

  输出本地方法接口的调用信息。

  -version

  -help

  -?

  不用说了。

  -X

  显示可用的非标准参数




什么是JIT

JIT是just in time,即时编译技术。使用该技术,能够加速java程序的执行速度。下面,就对该技术做个简单的讲解。

首先,我们大家都知道,通常javac将程序源代码编译,转换成java字节码,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT技术。

在运行时JIT会把翻译过的机器码保存起来,已备下次使用,因此从理论上来说,采用该JIT技术可以,可以接近以前纯编译技术。下面我看看,JIT的工作过程。

JIT 编译过程

JIT编译启用时(默认是启用的),JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码,下图展示了该过程。

什么是JIT

JIT是just in time,即时编译技术。使用该技术,能够加速java程序的执行速度。下面,就对该技术做个简单的讲解。

首先,我们大家都知道,通常javac将程序源代码编译,转换成java字节码,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢。为了提高执行速度,引入了JIT技术。

在运行时JIT会把翻译过的机器码保存起来,已备下次使用,因此从理论上来说,采用该JIT技术可以,可以接近以前纯编译技术。下面我看看,JIT的工作过程。

JIT 编译过程

JIT编译启用时(默认是启用的),JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码,下图展示了该过程。


Graphic



Real-Time Specification for Java缩写就是RTSJ。
RTSJ是Java的适应实时计算要求而开发。关于对实时系统的介绍和特性说明,请参见其他文章,这里重点是Java针对实时系统开发所做的改进做详细的介绍。
RTSJ在6各方面对Java做了增强:
1.增加实时线程。实时线程提供了比普通线程更完善和细致的控制属性和操作内容,例如更大的优先级范围,控制内存分配等等;
2.增加了新的工具类和编程方式,比如内存工具类。利用这些工具类,可以完成不需要垃圾回收的Java程序;
3.增加了异步事件处理器类和相应的机制。把异步事件和jvm外界发生的事件直接关联起来;
4.增加了异步传输的控制机制,允许一个线程更改另一个线程中的控制流。也就是说,可以中断或者继续另一个线程的运行;
5.增加一种机制,能够控制对象分配到内存的位置;
6.增加一种机制,能够访问特定地址的内存。

RTSJ对传统java的增强的过程中本着一个原则,就是不改变原有的java的任何语法。这样做的目的就是保持原有java的连续。也就是说,在普通 jvm上能够运行的程序,理论上,可以完全相同的在rt jvm上运行;反过来则不行。之所以强调是在理论上情形,原因是现有的rt jvm还没有实现完全的java类库,比如awt,swing,text等。而一些常用的lang,util,io等都有实现。这也是现有的rt jvm选择实现的优先级别。
对于rtsj的核心和基础,我认为有以下的几点:
1.领域内存 Scope Memory
2.实时线程 Realtime Thread
3.高精度时间 High Resolution Time

这当然不是说,其他的不重要。其他的rtsj能力就像awt对你的重要程度一样,如果你在作客户界面程序,那awt对你就很重要;如果你在做servlet,jsp等形式的j2ee程序,awt可能对你完全没有意义。所以这里只介绍核心和基础的几点内容。

领域内存 Scope Memory

Rtsj增加领域内存根本目的就是解决jvm的垃圾收集问题。我们知道,jvm的垃圾收集机制具有 不确定性。例如,我们不知道jvm什么时候将要进行垃圾收集,收集操作需要多长时间,收集线程本身占用多少内存,多少cpu时间,是否有其他的i/o资源 占用等等。而实时应用程序的一个很重要的指标就是,能够预计一个操作的准确时间,占用的资源。rtsj要能够在实时应用中得到应用,首先就要解决垃圾收集 这个头疼的问题。
Rtsj并没有放弃垃圾收集机制,如果真的这样做,也是不可接受的。Java能够存在和发展到现在的这个地步,头号功臣可能就是这种机制。它让 开发者从细节内存管理的泥潭中解放出来。如果去掉这种机制,rtsj和c++又有什么区别?rtsj的做法是,让有实时要求的代码使用领域内存机制,避开 垃圾收集,而没有此项要求的代码,和标准的java一样书写,享受垃圾收集给你带来的快乐。对我来说,可以自由的用碗来吃饭,而不用自己来刷碗,应该不算 一件坏事。
Rtsj的这种做法,是基于这样的两条结论:不产生垃圾的代码不会导致请求式的垃圾收集;不引用堆中对象的代码可以抢占垃圾收集器的线程。
领域内存的机制,其实就是内存管理的机制,开发者自己管理内存的机制。对照上段的文字,你可能有了疑问:需要我自己管理内存,这又和我使用c++ 有什么区别?这个问题的答案,不同的人理解不同,最终结论就会有所不同。我的理解是,rtsj提供了比c++简单的和高级别的内存管理方式,是开发者学习 几个工具类和一种编程风格,就能实现与c++等相近的效率和功能。你的理解是什么呢?这和Java公认的比c++慢很多倍,但却被人们普遍接受的情况,有 着异曲同工之处。
读者脸色有变,现在进入正题。
为了实现内存管理,rtsj提供了一组内存管理类,和一种编程方法,或者说的编程规范。


图 1 内存管理类



ImmortalMemory的中文名字是不朽内存,他是静态的,或者说是与jvm共存亡的。只要jvm在运行,不朽内存就存在,并且可 用。他和static静态的对象行为一样。存放在不朽内存中的对象不会被垃圾收集。但是要当心,不朽内存不会被回收,所以在其中放入太多的东西,会导致系 统内存耗尽。所以,在有其他选择的情况下,不要使用不朽内存。
HeapMemory中文名字是堆内存。他和我们平时在java程序中使用new 这个关键字生成对象的行为是一样的。其中的对象数据在无用的时候会被垃圾回收。
ScopeMemory就是领域内存。他有各种不同的实现。这里只拿出两个典型的,并且已经被现有的几种rtjvm支持的领域内存实 现:LTMemory和VTMemory。这两者的区别,是在他们分配存储空间的时间要求上,LTMemory随着分配空间的增加,分配的时间以固定的线 性增长。在rtsj规范中,没有说明VTMemory相对LTMemory的任何优点,但规范对LTMemory的分配时间有严格要求,而对 VTMemory没有。所以可以简单的这样理解:LTMemory分配时间很短,但内存使用效率可能不高;而VTMemory分配时间可能长一点,但内存 使用效率可能会高。个人认为,现阶段不用考虑他们的具体区别,全部使用LTMemory也不会有任何问题,我以我的房东的房子做保证。
下面提供一段代码,示范一下,使用领域内存的情景。
import javax.realtime.*;
public class Foo {
class Bar implements Runnable{
// run()在运行sm.enter(this)时被调用
public void run(){
//newObject 被分配在LTMemory sm中
Object newObject = new Object();
}//方法退出后,newObject对象的空间立即释放
}
void createSomeObjects() {
//分配1024字节空间
ScopeMemory sm = new LTMemory(1024, 1024);
// 1.直接分配
Bar b = (Bar)sm.newInstance(Bar.class);
// 2.进入领域
sm.enter(b);
}//在这里,垃圾收集器准备回收sm
//
//这样的情形下,sm.enter()方法中分配的对象,
//在运行期间即使引用悬空,也不会被垃圾收集,
//而在此方法退出后,其中所有的分配空间被立即释放,
//也不通过垃圾收集机制。

Public static void main(String[] args){
  :Foo foo = new Foo();
  Foo. createSomeObjects();
}
}
这个例子比较简陋,但他是完整的,并且说明了rtsj编写程序的几个两个基本要求:内存管理类的使用和程序编写风格要求。内存管理类在前面的例子 程序的注释中有所介绍。而所谓程序编写风格,其实就是sm.enter(b)的使用的方式,包括Bar需要实现run方法。这样的风格称做 closure,中文为闭包。闭包的意思是,一个程序段相对的独立和封闭,这样的程序段可以很容易的控制内存这样的资源的分配控制。比如,我在进入这个程 序段时准备好内存空间,例子中的分配空间是1024字节,在退出这个程序段时将这些内存释放掉,因为在闭包的环境下,相当于告诉jvm,过了这段程序,这 些内存空间中的对象对我已经没有用了。
我们来假想一下sm.enter(b)中的情形:
enter( b ){
prepareResource(b);
b.run();
releaseResource(b);
}

对于内存管理,浅尝则止,只说到概念。这些内容可能还不足以开始真正的工作,还有很多内存管理方面内容没有说明。更详尽的内容描述可能在其它的更深入的资料中找到。

实时线程 Realtime Thread

在介绍RealtimeThread之前,我们先来看一个HelloWorld.
Import javax.realtime.*;
Public class Hello{
Public static void main(String[] args){
RealtimeThread rt = new RealtimeThread(){
Public void run(){
System.out.println("Hello RealTime World!");
}
};
//准入控制,分析可行性。如果发现此线程某些资源不能得到,则退出。
//注意:分析可行性是一项可选功能,有的rtsj jvm可能没有实现,
//但是这个特性确实很吸引人。
if(!rt.getScheduler().isFeasible()){
System.out.println("rt thread is not feasible.\nsorry!");
System.exit(1);
}else{
rt.start();
}
//
try{
rt.join();
}catch(InterruptedException e){}
System.exit(0);
}
}


图 2 实时线程类



从上图中可以看出,RealtimeThread扩展了Thread,也就是说,他具有了普通Thread所有的特性和功能。
说到线程,就不得不说到线程的优先级了。普通java的线程有1-10,10个优先级,而rtsj的线程有32个优先级。而且只要是RealtimeThread的优先级,肯定比普通Thread优先级高。换句话说,RealtimeThread优先级肯定大于10。
RealtimeThread相对于Thread,增加了更多的细致的控制能力,这些从RealtimeThread的构造函数中,就能看得出来。
public RealtimeThread(SchedulingParameters scheduling,
ReleaseParameters release,
MemoryParameters memory, MemoryArea area,
ProcessingGroupParameters group,
java.lang.Runnable logic)
1.SchedulingParameters 调度参数:此参数中可能包含优先级信息,此线程可能就按照这个优先级运行;
2.ReleaseParameters 释放参数:释放参数在线程使用waitForNextPeriod方法和要求使用准入控制的时候起作用;
3.MemoryParameters 内存参数:此参数可以约束线程对领域内存的使用。
4.MemoryArea 内存区域:可选择的有,堆,不朽内存,领域内存;
5.ProcessingGroupParameters 处理组:用来控制非周期性的活动;
6.Runnable 逻辑:也就是Runnable对象。

这六个参数这样介绍,真是糊里糊涂。现在用一个例子,看看能不能帮助你。

//Note:此代码引自<实时Java平台编程>,page 117
import javax.realtime.*;
public class FullConstr{
public static void main(String[] args){
//1.将优先级信息封装在此参数中
SchedulingParameters scheduling =
new PriorityParameters( PriorityScheduler.MIN_PRIORITY + 20 );
//在线程执行完一个周期之后,也就是waitForNextPeriod时
//会使用此参数中的信息。这里全部用null构造,就是不起作用
ReleaseParameters release =
new AperiodicParameters( null,null,null,null );
//指定内存使用的类型
MemoryParameters memory =
new MemoryParameters( MemoryParameters.NO_MAX,//Heap
0);//Immortal memory
//内存区域。这里使用了堆内存,和我们使用new没有区别
MemoryArea area = HeapMemory.instance();
//这里不讨论此参数。虽然不能靠这个参数帮助你,
//但我保证,特不会妨碍你。
ProcessingGroupParameters group = null;
//这个参数熟悉吧,就是我们要运行的代码。
//这里使用MyThread,假设已经准备好了要运行的代码。
Runnable logic = new MyThread();
RealtimeThread rt = new RealtimeThread(scheduling,
release,
memory,
area,
group,
logic);
rt.start();
try{
//等待此线程,直到结束。
rt.join();
}catch(Exception e){};
}
}

实时线程中,还有一个重要的问题是,资源的抢占和优先级的关系。现在的rt jvm实现中,实现的都是优先级继承机制。Rtsj推荐的另一种抢占机制是优先级限高机制。这里不作详细介绍。这些内容会在本文的后续专题文章中出现。
在rtsj中,很多问题的解决方法都是衍生于实时线程的,例如事件触发机制。这里所说的事件,扩展了java awt & swing中的事件思想,rtsj把jvm外部的事件也映射到jvm中,例如i/o的网络操作。为了实现高效的事件操作,体现实时特性,就需要借助于实时 线程,线程池这样的技术。异步事件,异步传输的控制机制就是结合实时线程,加上各种资源的封装分配的结果。这样说来,实时线程是rtsj很基础的内容。
线程介绍了这么多,是不是觉得有点复杂?对,不但复杂,而且能完成更多的工作。如果你能够把这两段程序运行一下,并试着根据api修改一下,我认为,本文的介绍目的就达到了。
浅尝则止。

高精度时间 High Resolution Time

这里所说的高精度时间,是相对于普通java而言。普通java的时间精度是毫秒,而rtsj的高精度时间可表示的精度为纳秒。
高精度时间用一个64位表示的毫秒值和一个32位表示的纳秒值组成,时间长度就是:
10e64 (毫秒) + 10e32 * 1000 (纳秒) = 时间总和 (毫秒)
这个时间总和相当于大约2.92亿年。
这样,从时间精度上和时间表示长度上,都对以前的java时间做了很大的提升。
不过,现在的软件系统能够响应1个毫秒级也决非易事,更何况是纳秒了。这可能是rtsj规范的制定者对千年虫问题还是心有余悸的缘故吧。


图 3 高精度时间类


HighResolutionTime是这些类中的抽象基类,提供了最基本的时间方法的实现。
AbsoluteTime表示绝对时间,例如2003年5月8日23:06:10 .177 就是一个绝对的时间,如果有需要,这个时间可以精确到纳秒级。可能在这个时间需要引爆一个原子弹,也可能这个时间我正写的头昏脑胀需要大喊一声,不管怎样,这个时间可是纳秒不差。
RelativeTime是一个相对时间,一个时间段,一个期限,一个deadline time。这个功能类的强项就是时间的前推后算。对于实时程序而言,很多的操作需要设置超时时间。比如,一辆汽车正在驶向悬崖边缘,要求必须在15个纳秒 内启动转向停车操作,如果在这个规定期限内此操作没有完成,那就让驾驶员跳车吧,否则人财两空。这个15纳秒就用RelativeTime来表示,也只有 这个工具类可以表示这样精度的时间段。但在我看来,汽车比我贵重多了,所以,如果是我,我就把转向停车操作的时限调到2.92亿年,誓与汽车共存亡。
RationalTime表示的是,在一个时间段内,某个操作发生的频率。从表现来说,他的行为类似于Java的Timer类,不同的时,RationalTime表示的时间精度更高。
关于高精度时间,这里没有给出例子。原因是我觉得他们和普通Java中提供的工具类太想象了,不同的就是时间精度。而Real-Time本身强调的很多问题就是时间的问题,所以在这里,才把时间部分的工具类单独拿出来作介绍。

告一段落

本人能力有限,如果你觉得没有把这些东西说明白,也可以理解。不过这些东西也是不太容易理解的。光说不练肯定不行。所以现在你还是没有搞清楚,也不用多浪费脑细胞。亲手试试,也许会有豁然开朗的感觉。
在前面也说过,本文重在概念性方面。具体每一个技术细节问题,后续会有相应的专题文章介绍。你也可以参考相关的书籍和资料。

参考


Huihoo Power!

企业计算研究中心


开放企业基金会


Real-Time Java Expert Group


Reference Implementation,RTSJ JVM




一. jvm 运行
The Java Virtual Machine (JVM) loads the class files and either interprets the bytecode or just-in-time compiles it to machine code and then possibly optimizes it using dynamic compilation.

以上内容引用自:

英文文档写的很好。这是我查了很久找到的最全面的jvm装载类执行的描述。

java虚拟机装载类文件后,通过解释执行字节码 或 即时编译成机器码 更有可能使用 自动优化编译。

以上内容有如下解释:
1.解释执行是最早期的执行方式,速度慢。

2.jit是传统的优化方式去加快代码执行

有文档描述IBM 使用 jit 进行运行时优化:http://www.ibm.com/developerworks/cn/linux/es-JITDiag.html

3.java hotspot vm 的自适应优化Adaptive optimization

sun 公司实现的 虚拟机命名 hotspot vm,sun 采用自动优化编译技术。



1.1 关于解释执行

Java代码
  1. public void getString(){   
  2.       String a ="a";  
  3. }   
  4.   
  5. //生成的java bytecode如下   
  6. 0x12 ldc   
  7.   0x15 iload   
  8.   0x4C astore_1   
  9.   0xB1 return   

 
以上通过javac 生成的class中的 bytecode,java现有的bytecode 有202个。

详见:
java是解释型语言,即编译成中间语言bytecode,jvm运行时将bytecode解释成机器码执行。

1.2 使用 jit 的 jvm 如 ibm vm
每条语句都解释执行,能不能无需解释,第一次运行的时候编译?

于是有一些JVM实现者通过jit优化运行时速度

just in time 的定义:

 

JIT(just-in-time compilation,即时编译)指计算机领域里,即时编译也被称为动态翻译(dynamic translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。

just in time编译执行与解释执行的区别:

jit将整个getString方法编译,生成机器码,

解释执行执行getString时先解释ldc,执行对应机器码,再解释iload,执行对应机器码。于是,解释执行每次需要解释4次。

某confluence上jit compiler介绍



1.3 java hotspot vm

java hotspot vm 的自动优化技术在传统jit基础上有更多的优化方式:

请查阅hotspot 的overview:

 


文档介绍了使用jit是一种优化执行
同时指出了,However, there are several issues with JIT compilation 使用jit会有一些难以解决的问题。

再次,Secondly, even if a JIT had time to perform full optimization,就算完全使用jit优化了,速度依旧没有c或c++速度快。

* Java语言是动态的安全,即确保不违反程序的语言的语义,或直接进入非结构化内存。这意味着动态型测试必须经常进行

* Java语言分配的所有对象的堆,而相比之下, C++中,许多是栈分配。这意味着,C++对象分配率要高得多。此外,由于Java语言是垃圾收集,它非常不同类型的内存分配间接费用。

*  Java语言中,大多数是虚拟方法调用(可能多态性) ,更经常使用比在C + + 。这不仅意味着该方法调用的性能更占主导地位,而且静态编译器优化是更加难以执行的方法调用。许多传统的优化是最有效的要求,以及减少之间的距离要求在 Java语言,就可以大大降低其使用效能的最佳化,因为它们有小部分的代码工作。

* 基于Java技术的程序可以动态改变,动态加载的类。这使得困难得多。编译器必须不仅能够检测到这些优化成为无效,动态加载,但也可以撤消或重做这些优化 程序的执行过程中,即使他们积极参与的方法对堆栈。这必须要在不损害或影响的基于 Java技术的程序执行语义以任何方式。


最后指出:

The Java HotSpot VM architecture addresses the Java language performance issues described above by using adaptive optimization technology.

在Java HotSpot虚拟机架构的Java语言解决性能问题上文所述利用自适应优化技术



1.4 java hotspot vm 特性

在hotspot文档中有详细介绍:

  •  



    Hot spot Detection 热点检测

    往往程序大部分时间只是在执行一小段代码,于是无需将全部进行编译。

    hot spot vm解释运行代码,并分析是否是hot spot关键部分,如果是则进行global natvie code 优化。

    hot spot vm关注最关键的代码,避免汇编不常执行代码,而不必增加总汇编时间。

    hot spot vm持续动态监测的程序运行。

    程序运行一次后,可以对相关信息进行收集,执行更更智能优化。

     

     

    方法内嵌method inlining

    虚拟方法调用是java编程语言的一个重要优化瓶颈。

    it not only compiles the hot spot into native code, but also performs extensive method inlining on that code.

    vm不仅仅编译成本地代码,而且把方法内嵌到调用的代码里。

     

    内嵌有重要的作用。它极大地降低了动态频率的方法调用,从而节省所需的时间来执行这些方法调用。更重要的是,内嵌的代码可以执行优化工作。这就造成了一种局面,大大增加了传统的编译器效率的优化,克服的主要障碍增加了Java编程语言的性能。

    内嵌是协同其他代码优化,因为这使它们更有效。正如在Java HotSpot编译器的成熟,在未来,内置模块的代码将有更先进的优化技术。

     

     

    动态优化

    java语言特性,使得热点检测,方法内嵌变的相当复杂,如动态方法调用,重新装载类文件。

    所以vm所优化的代码必须跟着代码变化而动态变化。

    java hotspot client compiler 和 java hotspot server compiler就是执行动态优化的。

     


    java hotspot client compiler

    客户端分三个阶段编译代码,

     

    第一步:a platform-independent front end  constructs a high-level intermediate representation (HIR)from the bytecodes

    第二步:In the second phase, the platform-specific back end generates a low-level intermediate representation (LIR) from the HIR.

    第三步:The final phase performs register allocation on the LIR using a customized version of the linear scan algorithm, does peephole optimization on the LIR and generates machine code from it.


    java hotspot server compiler

    server compiler对应是优化服务器应用的。

    有大量的优化技术,包括including dead code elimination, loop invariant hoisting, common subexpression elimination, constant propagation, global value numbering, and global code motion

     


    JDK包括2个JVM 
       Java HotSpot Client VM(-client),为在客户端环境中减少启动时间而优化;
       Java HotSpot Server VM(-server),为在服务器环境中最大化程序执行速度而设计.

    比较:Server VM启动比Client VM慢,运行比Client VM快.

    如何将VM设置成 Server VM:
       找到 [jre安装目录]/lib/i386/jvm.cfg 文件
       里面第一行写的是 -client 默认就是client版本,把第二行的-server KNOWN 放到
       第一行,如下面所示
       代码
      -server KNOWN
      -client KNOWN
      -hotspot ALIASED_TO -client
      -classic WARN
      -native ERROR
      -green ERROR

    本地测试了一下:

    c:> java -client -Xmx1443M -version

    使用客户端:内存可以使用到 1443M

    使用服务端:内存可以使用到1444M

    感觉差别不是很大,不过确实是大了一些!

    阅读(1216) | 评论(0) | 转发(0) |
    0

    上一篇:java TOMCAT

    下一篇:java nio

    给主人留下些什么吧!~~