sprsong
2003-08-04 18:27
我是说:因为是两个jsp所以是两个线程

robbin
2003-08-04 19:49
静态方法效率高出很多。

上面的测试程序写的不对,测不出正确的结果。

静态方法执行的时候,class被JVM load 进内存就执行。而非静态方法的class被JVM load进内存之后,在new的时候,在heap中分配内存空间,而heap的操作是昂贵的。更何况new了许多对象实例,都需要GC进行回收,也增加JVM的负担。

anonymous
2003-08-04 20:02
有篇文章上曾经看到对static类的使用建议:

1:这个类不需要考虑线程安全

2:这个类不需要频繁创建

3:这个类在整个应用生命周期中都可能或需要使用到

4:这个类被调用的频率不高

.

.

.

等等

一般来说静态类被的使用就想全局变量什么的,整个系统都有可能使用,而且是随时会使用到,但又使用的并发性不需要太多考虑等等,

最经典也最常用的例子是系统的配置模块,从配置文件中加载系统配置参数,放在一个静态类中,当系统的其他应用需要这些参数时从这个静态类中取得,这样的效率就比每次new一个效率高了,而一般这钟情况下都会使用sigleton模式实现

当若仅仅为了不用new,节省一点点(基本都不能说节省)时间而把很多类

设计成静态类的话就有点滥用和做作了,该new就new,为重用可以用cache和pool机制等,J2EE系统的性能和效率的调优一个是架构的设计,一个是服务器、容器的性能调整,和用static或new没多大的关系,偶认为:)

世界上没有万能药,什么样的情况用什么样的设计需要看具体的问题,所以需要static就static,想new就new:)

mellon
2003-08-05 09:47
我就是希望得到一个正确的例子。

还请robbin能够show一下。

这里有一篇关于提高性能的文章:

http://www.candle.com/www1/vgn/images/portal/cit_1749/3465317ExtremePerformanceTuning.pdf

可惜没有static内容。

Servlet,JSP都是多线程的,我想知道在多个线程同时并发执行调用一个类的static方法时,以及调用一个类的同步了的static方法时分别是怎样一个机制,有何区别。最好能和instance方法有个好的对比。

mellon
2003-08-05 10:45
TestStatic.java

三个方法,一个instance方法,一个static方法,一个synchronized static 方法。执行每个方法需要10秒

TestServlet.java

在doGet()中根据request参数决定调用TestStatic的哪个方法。

CallServlet.java

从Thread类继承,多线程发送对Servlet的请求,三种参数各发送100个请求。

结论:static方法是并发执行的,instance方法也是并发执行的。

现在的问题是,static方法是否和static变量一样有一个所谓的“只有一份”的概念,如果有,那么static方法的一份和instance方法的多份是什么关系。

robbin
2003-08-05 11:58
我还是劝你不要白费力气做这样无用的测试了。因为你的测试用例非常不合理,并且我自己也没有足够的水平写出良好的测试用例来。

首先,不应该一个class包括3个不同的方法,而应该是3个不同的class,否则你测不出差别来。

其次,你在主程序中要首先分别调用该class一次之后才能进入循环测试,因为第一次调用有一个从硬盘把class文件load到JVM中的过程,该过程由于有IO,所以要比循环测试本身长很多,不把该过程占用时间排除,测不出准确结果。

还有,你用Thread.sleep非常值得斟酌,方法是否静态,差别在于是否需要在heap中分配内存给对象实例,你sleep了,等于把差别抹销了。准确的做法是要排除其它一切无关因素,也就是执行空方法。当然执行空方法也有一定问题,执行速度太快,可能CPU占用率都到循环语句上了。

另外,光测试方法执行的速度是非常片面的测试,测试结果也没有任何实际意义。你需要测试的是,几种不同的方法造成的JVM的CPU占用率有什么差别,其中最最重要的是测试是,几种不同的方法对JVM的内存的影响变化,监测JVM的内存heap堆的变化,以及不同的方法给JVM的GC造成的影响。

实际上我后面提到几项测试更加重要,一个程序就算执行的稍微快点,但是如果造成JVM运行负载很重,heap堆频繁的分配内存,GC频繁的回收内存,那么还不如一个执行速度稍慢,但是对JVM资源压力小很多的程序更加有效率。

所以你看看吧,你的测试用例还有意义吗?除非对JVM的运行原理有非常深刻的掌握,有JVM调优的丰富经验,以及对测试环境的精心搭建(包括操作系统,内存测试工具,CPU测试工具,专业的JVM测试工具),和测试用例的精心编写,否则测试根本就是白搭。

说实话,我不认为个人能够完成这样专业的测试,除非是大公司投资投人投设备进行专业的测试,才能得到准确的结果。

robbin
2003-08-05 13:34
>>static方法是否和static变量一样有一个所谓的“只有一份”的概念,如果有,那么static方法的一份和instance方法的多份是什么关系。

方法没有一份和多份的概念。本质上来说,方法就是指令代码,被ClassLoader从硬盘或者网络上load到JVM以后就放在指令区,不论是不是静态方法,都不存在指令代码的多份拷贝。如果是静态方法,那么该指令段直接依次执行;如果是非静态方法,需要new一个对象实例,new对象过程是这样的:

这个过程需要在heap中分配一段内存空间,存放对象实例的每个对象属性的初始化值,以及对象的类型和对象每个属性的类型(类似对象序列化过程),然后在stack中存放4字节指针指向该heap中存放对象实例的地址。

这样非静态方法调用可以访问到该对象在heap中的内存空间,也就是说该方法可以访问该对象实例的属性值。当然每个对象实例的非静态方法都是对应自己对象实例的heap中的内存空间的,因此只能访问自己对象实例的属性值,而不能访问别的对象实例的属性值。而静态方法由于不在heap中分配对象属性内存,所以并不能够访问任何对象实例的属性值。

从执行速度来说,如果一个非静态方法已经new了对象实例,那么执行速度和静态方法不会有本质区别(纵然有些许区别,也可以忽略不计了),因为方法的执行不论静态还是非静态都是程序计数器读取指令代码,然后执行之,所差别者仅仅是非静态方法可以访问heap中对象实例属性而已。不过考虑到创建对象的开销(在heap中分配内存的开销很大)和加重对GC的负担,所以静态方法执行效率要高得多。

因此,如果你的方法不需要访问对象的非静态属性,也不需要访问对象的其它非静态方法的话,就可以写成静态方法;如果方法涉及到对对象属性的读写,和调用其它非静态方法,就必须也是非静态的方法。总得来说,静态方法从作用上来说,类似于C/C++ 函数的意思,被当做一个函数来调用,和具体的数据无关。而非静态方法总是和具体的数据绑定的很紧的。

至于synchronized和方法静态不静态并没有什么关系。在多线程环境下,你可以这样理解,如果非同步的方法,那么多个程序计数器可以同时读取该指令段执行该指令段,如果方法是同步的话,那么只有一个程序计数器能够读取该指令段,执行该指令段,其它程序计数器只有干等的份。

robbin
2003-08-05 15:29
我试着从JVM的内存管理原理的角度来谈一下静态方法和静态属性的问题,不对的地方请指正。

JVM的内存分为两部分:stack和heap:

stack(栈)是JVM的内存指令区。stack管理很简单,push一定长度字节的数据或者指令,stack指针压栈相应的字节位移;pop一定字节长度数据或者指令,stack指针弹栈。stack的速度很快,管理很简单,并且每次操作的数据或者指令字节长度是已知的。所以Java基本数据类型,Java指令代码,常量都保存在stack中。

heap(堆)是JVM的内存数据区。heap的管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例。在heap中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在stack中),在heap中分配一定的内存保存对象实例和对象的序列化比较类似。而对象实例在heap中分配好以后,需要在stack中保存一个4字节的heap内存地址,用来定位该对象实例在heap中的位置,便于找到该对象实例。

由于stack的内存管理是顺序分配的,而且定长,不存在内存回收问题;而heap则是随机分配内存,不定长度,存在内存分配和回收的问题;因此在JVM中另有一个GC进程,定期扫描heap,它根据stack中保存的4字节对象地址扫描heap,定位heap中这些对象,进行一些优化(例如合并空闲内存块什么的),并且假设heap中没有扫描到的区域都是空闲的,统统refresh(实际上是把stack中丢失了对象地址的无用对象清除了),这就是垃圾收集的过程。

我们首先要搞清楚的是什么是数据,什么是指令?然后要搞清楚对象的方法和对象的属性分别保存在哪里?

为了便于描述,我简单的统称:

1)方法本身是指令的操作码部分,保存在stack中;

2)方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在stack中(实际上是简单类型保存在stack中,对象类型在stack中保存地址,在heap中保存值);

上述的指令操作码和指令操作数构成了完整的Java指令。

3)对象实例包括其属性值作为数据,保存在数据区heap中。

非静态的对象属性作为对象实例的一部分保存在heap中,而对象实例必须通过stack中保存的地址指针才能访问到。因此能否访问到对象实例以及它的非静态属性值完全取决于能否获得对象实例在stack中的地址指针。

先分析一下非静态方法和静态方法的区别:

非静态方法有一个和静态方法很重大的不同:非静态方法有一个隐含的传入参数,该参数是JVM给它的,和我们怎么写代码无关,这个隐含的参数就是对象实例在stack中的地址指针。因此非静态方法(在stack中的指令代码)总是可以找到自己的专用数据(在heap中的对象属性值)。当然非静态方法也必须获得该隐含参数,因此非静态方法在调用前,必须先new一个对象实例,获得stack中的地址指针,否则JVM将无法将隐含参数传给非静态方法。

而静态方法无此隐含参数,因此也不需要new对象,只要class文件被ClassLoader load进入JVM的stack,该静态方法即可被调用。当然此时静态方法是存取不到heap中的对象属性的。

总结一下该过程:当一个class文件被ClassLoader load进入JVM后,方法指令保存在stack中,此时heap区没有数据。然后程序技术器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问heap数据区的;如果是非静态方法,由于隐含参数没有值,会报错。因此在非静态方法执行前,要先new对象,在heap中分配数据,并把stack中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到heap数据区了。

再说一下静态属性和动态属性:

前面提到对象实例以及动态属性都是保存在heap中的,而heap必须通过stack中的地址指针才能够被指令(类的方法)访问到。因此可以推断出:静态属性是保存在stack中的(基本类型保存在stack中,对象类型地址保存在stack,值保存在heap中),而不同于动态属性保存在heap中。正因为都是在stack中,而stack中指令和数据都是定长的,因此很容易算出偏移量,也因此不管什么指令(类的方法),都可以访问到类的静态属性。也正因为静态属性被保存在stack中,所以具有了全局属性。

总结一下:静态属性保存在stack指令内存区,动态属性保存在heap数据内存区。

sprsong
2003-08-05 16:36
一个问题:

假如有这样两个非同步静态函数

public static int sum(int a,int b){

  int c = a + b;

return c;

}

public static Integer sum(Integer A,Integer B){

Integer C = new Integer(A.intValue + B.intValue);

return C;

}

----------------

那么当两个线程用不同的参数同时访问其中某一个方法时,c或者C会被两个线程传入的参数搞乱吗?是返回同样的值还是各返回各自的值。

Junt
2003-08-05 16:53
to sprsong :

我觉得你的问题就是

"多线程同时访问一个非同步方法"

这样会不会造成问题

我的答案是:会

另外推荐一本关于JVM的很好的书给各位

Inside the Java Virtual Machine

by Bill Venners

http://www.artima.com/insidejvm/ed2/index.html

相信可以帮助大家更好的理解robbin的解释

robbin
2003-08-05 16:55
不会,各自返回各自的值。

Junt
2003-08-05 17:03
对不起,我前面的答案是错的

两个方法里都只有局部变量

多线程调用时也不会造成冲突

应该是各自返回各自的值

sprsong
2003-08-05 17:47
非常感谢,我明白了!

Junt
2003-08-05 17:54
谈一下我的理解,请指正:

在JVM中,每个线程都有一个自己的Stack

所有线程共享heap(存放object)和method area(存放class data)

线程进行方法调用时的本地变量和得到的结果都存放在

自己的Stack,所以在上边的问题中,线程不会冲突

sprsong
2003-08-05 18:42
我的理解。

我理解中Stack应该是一个,即一个静态方法在内存中只有一份。

两个线程调用一个静态方法时会到同一个地址去,但是执行当中如果要生成临时变量时,两个线程会分别生成自己的临时变量。因此不会搅浑。

猜你喜欢