在中日没有任何伙伴关系关系class的情况下,也不是静态变量或者method,怎么调取

博客分类:
个人总结:
先静态后动态
先定义后构造函数;
JAVA面试题解惑系列(一)——类的初始化顺序
关键字: java 面试题 初始化
作者:臧圩人(zangweiren)
网址:http://zangweiren.iteye.com
&&&转载请注明出处!&&&
大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。这实际上是在考查我们对于继承情况下类的初始化顺序的了解。
我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)&(变量、初始化块)&构造器。我们也可以通过下面的测试代码来验证这一点:
public class InitialOrderTest {&&
&&& // 静态变量&&
&&& public static String staticField = "静态变量";&&
&&& // 变量&&
&&& public String field = "变量";&&
&&& // 静态初始化块&&
&&& static {&&
&&&&&&& System.out.println(staticField);&&
&&&&&&& System.out.println("静态初始化块");&&
&&& // 初始化块&&
&&&&&&& System.out.println(field);&&
&&&&&&& System.out.println("初始化块");&&
&&& // 构造器&&
&&& public InitialOrderTest() {&&
&&&&&&& System.out.println("构造器");&&
&&& public static void main(String[] args) {&&
&&&&&&& new InitialOrderTest();&&
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
public String field = "变量";
// 静态初始化块
System.out.println(staticField);
System.out.println("静态初始化块");
// 初始化块
System.out.println(field);
System.out.println("初始化块");
public InitialOrderTest() {
System.out.println("构造器");
public static void main(String[] args) {
new InitialOrderTest();
运行以上代码,我们会得到如下的输出结果:
静态初始化块
这与上文中说的完全符合。那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:
class Parent {&&
&&& // 静态变量&&
&&& public static String p_StaticField = "父类--静态变量";&&
&&& // 变量&&
&&& public String p_Field = "父类--变量";&&
&&& // 静态初始化块&&
&&& static {&&
&&&&&&& System.out.println(p_StaticField);&&
&&&&&&& System.out.println("父类--静态初始化块");&&
&&& // 初始化块&&
&&&&&&& System.out.println(p_Field);&&
&&&&&&& System.out.println("父类--初始化块");&&
&&& // 构造器&&
&&& public Parent() {&&
&&&&&&& System.out.println("父类--构造器");&&
public class SubClass extends Parent {&&
&&& // 静态变量&&
&&& public static String s_StaticField = "子类--静态变量";&&
&&& // 变量&&
&&& public String s_Field = "子类--变量";&&
&&& // 静态初始化块&&
&&& static {&&
&&&&&&& System.out.println(s_StaticField);&&
&&&&&&& System.out.println("子类--静态初始化块");&&
&&& // 初始化块&&
&&&&&&& System.out.println(s_Field);&&
&&&&&&& System.out.println("子类--初始化块");&&
&&& // 构造器&&
&&& public SubClass() {&&
&&&&&&& System.out.println("子类--构造器");&&
&&& // 程序入口&&
&&& public static void main(String[] args) {&&
&&&&&&& new SubClass();&&
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
public String p_Field = "父类--变量";
// 静态初始化块
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
// 初始化块
System.out.println(p_Field);
System.out.println("父类--初始化块");
public Parent() {
System.out.println("父类--构造器");
public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
public String s_Field = "子类--变量";
// 静态初始化块
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
// 初始化块
System.out.println(s_Field);
System.out.println("子类--初始化块");
public SubClass() {
System.out.println("子类--构造器");
// 程序入口
public static void main(String[] args) {
new SubClass();
运行一下上面的代码,结果马上呈现在我们的眼前:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
现在,结果已经不言自明了。大家可能会注意到一点,那就是,并不是父类完全初始化完毕后才进行子类的初始化,实际上子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。
那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。我们以静态变量和静态初始化块为例来进行说明。
同样,我们还是写一个类来进行测试:
public class TestOrder {&&
&&& // 静态变量&&
&&& public static TestA a = new TestA();&&
&&& // 静态初始化块&&
&&& static {&&
&&&&&&& System.out.println("静态初始化块");&&
&&& // 静态变量&&
&&& public static TestB b = new TestB();&&
&&& public static void main(String[] args) {&&
&&&&&&& new TestOrder();&&
class TestA {&&
&&& public TestA() {&&
&&&&&&& System.out.println("Test--A");&&
class TestB {&&
&&& public TestB() {&&
&&&&&&& System.out.println("Test--B");&&
public class TestOrder {
// 静态变量
public static TestA a = new TestA();
// 静态初始化块
System.out.println("静态初始化块");
// 静态变量
public static TestB b = new TestB();
public static void main(String[] args) {
new TestOrder();
class TestA {
public TestA() {
System.out.println("Test--A");
class TestB {
public TestB() {
System.out.println("Test--B");
运行上面的代码,会得到如下的结果:
静态初始化块
大家可以随意改变变量a、变量b以及静态初始化块的前后位置,就会发现输出结果随着它们在类中出现的前后顺序而改变,这就说明静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。同样,变量和初始化块也遵循这个规律。
了解了继承情况下类的初始化顺序之后,如何判断最终输出结果就迎刃而解了。
java2times
浏览: 13964 次
来自: 广州
传值---传递基本数据类型参数
传引用---传递对象
好贴!赞一个
如果在方法中把对象(或数组)作为参数,方法调用时,参数传递的是 ...
public class Inc{
public stati ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'【向ZangXT,火龙果,李晗提问】为什么接口不能声明静态变量?为什么接口的成员必须public?
[问题点数:20分,结帖人bigbro001]
【向ZangXT,火龙果,李晗提问】为什么接口不能声明静态变量?为什么接口的成员必须public?
[问题点数:20分,结帖人bigbro001]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2009年6月 挨踢职涯大版内专家分月排行榜第二2009年5月 挨踢职涯大版内专家分月排行榜第二2009年3月 挨踢职涯大版内专家分月排行榜第二2008年12月 挨踢职涯大版内专家分月排行榜第二
2009年6月 Web 开发大版内专家分月排行榜第三
2009年3月 扩充话题大版内专家分月排行榜第三
匿名用户不能发表回复!|Java中类和对象的定义及关系!
一、类和对象的定义及关系
1、类是同一类别对象的抽象定义;可以理解为是某一类具体对象的模子,确定对象将会拥有的特性,包括该类对象具备的属性和方法。
2、对象是对类的一种具体化的客观存在,是对类的具体实例化。要创建一个对象,必须基于类,即先要创建可描述该对象的类。
3、创建一个类
public class Telphone {
//属性(成员变量):有什么
//方法 :干什么
void call(){
System.out.println("Telphone有打电话的功能!");
void sendMessage(){
System.out.println("Telphone有发短信的功能!");
4、创建一个对象
针对上面代码创建类的创建对象方法
Telphone myphone=new Telphone();
5、对象的比较:
有两种方法,分别为“==”运算符和equals()方法,但两者有本质的区别:
(1)“==”运算符比较的是两个对象引用的地址是否相同;
(2)equals()方法比较的是两个对象引用所指的内容是否相同
public class ClassCompare {
public static void main(String[] args) {
String c1=new String("abc");
String c2=new String("abc");
String c3=c1;
System.out.println("c2==c3的运算结果为: "+(c2==c3));
System.out.println("c2.equals(c3)的运算结果为: "+(c2.equals(c3)));
/*首先为c1分配一块内存区域存储字符串abc,并将c1指向设为该内存区域
*然后同样为c2分配另外一块内存区域存储字符串abc,并将c2指向设为该内存区域
*最后把c3的引用指向c1的指向
6、类的使用
同样针对上面创建的Telphone类:
public class InitialTelphone {
public static void main(String[] args) {
// TODO Auto-generated method stub
Telphone phone=new Telphone();
phone.sendMessage();
//这里各实例属性值在未初始化赋值的时候默认为0
phone.screen=5.0f;
phone.cpu=1.4f;
phone.mem=2.0f;10 phone.sendMessage();
//赋值后的显示
二、成员变量和局部变量
1、成员变量:在类中定义,用于描述对象的属性;成员变量可以被本类的方法使用,也可以被其他类的方法使用(依据该成员变量定义时的修饰词)。
2、局部变量:在类的方法中定义,在方法中临时保存数据。局部变量只能在当前方法中使用。
3、区别:局部变量的作用域仅限于定义的方法内部;成员变量的作用域在整个类内部都是可见的。Java中会给成员变量赋初始值,但不会给局部变量默认赋初值,所以局部变量在使用前必须要赋值,否则会报错。
4、同一个方法中不能用同名的局部变量;在不同的方法中,可以有同名局部变量。如果局部变量和成员变量同名,在定义该局部变量的方法内部局部变量有更高的优先级。
public class Telphone {
void call(){
int localVar=10;
System.out.println(var);
//可以调用类的成员变量var,默认值为0
System.out.println("Telphone有打电话的功能!");
void sendMessage(){
System.out.println(localVar);
//调用call()方法中的局部变量会报错
System.out.println("Telphone有发短信的功能!");
三、类的构造方法
1、构造方法是定义在Java类中的一个用来初始化对象的方法。构造方法与类同名,并且没有返回值。
2、如果类定义中没有设置无参数的构造方法,则系统会自动生成。
//设置无参数的构造方法
public class Telphone {
int sc=10; //定义成员变量
public Telphone(){
System.out.println("无参的构造方法执行了");
public class InitialTelphone {
public static void main(String[] args) {
Telphone phone=new Telphone();
//可以看到构造方法执行了
System.out.println(phone.sc);
//可以看到输出了成员变量sc的值为10
//当没有设置无参构造方法时,默认的无参构造方法会使对象拥有成员变量这个实例属性
//当定义了无参构造方法时,即便没有显式的定义成员变量的赋值,对象也会拥有成员变量对应的实例属性
3、有参数的构造方法通常只有一个目的:即给成员变量赋初值。
public class Telphone {
public Telphone(){
System.out.println("无参的构造方法执行了");
//有参构造方法与无参构造方法同名
public Telphone(float screen,float cpu,float mem){
System.out.println("有参的构造方法执行了");
public class InitialTelphone {
public static void main(String[] args) {
// TODO Auto-generated method stub
Telphone phone=new Telphone();
Telphone phone1=new Telphone(5.2f,1.4f,2.0f);
4、当类中定义了构造方法(不管是有参还是无参)时,系统都不会再默认生成无参的构造方法。
5、构造方法的重载:方法名相同,但参数不同的多个构造方法,调用时候会自动判断、根据不同的参数组合选择对应的构造方法。如上面代码中无参和有参的构造方法为同名,都可以使用。
6、构造方法中一般可以添加保证赋值合理的功能。
public class Telphone {
public Telphone(){
System.out.println("无参的构造方法执行了");
public Telphone(float screen,float cpu,float mem){
if(screen&3.5){
System.out.println("赋值不合理,已自动赋值screen为3.5");
screen=3.5f;
System.out.println("有参的构造方法执行了");
四、静态变量和静态方法
1、静态变量:当同一个类的两个或者多个对象需要在同一个内存区域共享一个数据时,可以通过定义类的静态变量来实现。由static修饰的成员变量即是静态变量。
静态变量可以通过类名.静态变量名,也可以通过实例对象名.静态变量名来访问,但因为是同一类所有实例对象的共享,所以一般采用类名.静态变量名的方式进行访问。
2、静态方法:对于方法的静态修饰,与上述静态变量规则一致。需要注意:
(1)静态方法可以直接调用同一个类中的静态成员(包括静态变量和静态方法),但不能直接调用非静态成员。如果要调用一个类中的非静态成员,则必须先实例化对象,通过实例对项目名.非静态成员来调用。
(2)与静态成员方法不一样的是在普通成员方法中可以直接访问同类的非静态和静态变量。
public class HelloWorld {
static String hobby="java";
System.out.println(name); //普通成员方法中可以使用非静态成员
public static void main(String[] args) {
System.out.println(HelloWolrd.name); //错误同上
//静态方法中可以直接调用静态成员
//也可以通过类名.静态成员名进行调用 HelloWorld myHelloWorld=new HelloWorld();
System.out.println(myHelloWorld.name);
System.out.println(myHelloWorld.hobby);
HelloWorld.show(); //错误!静态方法main中不能使用类中的非静态成员
myHelloWorld.show(); } }
3、通过 static 静态初始化块:静态初始化块只在类加载时执行,且只执行一次,并且静态初始化块只能给静态变量赋值,不能给普通成员变量赋值。
public class HelloWorld {
static int num3;
public HelloWorld(){
System.out.println("通过构造方法给num1赋初值!");
{//通过普通初始化块给num2赋初值
System.out.println("通过构造方法给num2赋初值!");
static{ //通过静态初始化块给静态变量num3赋初值
num3=3333;
System.out.println("通过构造方法给num3赋初值!");
public static void main(String[] args) {
HelloWorld hello=new HelloWorld(); //构造方法创建类的实例对象hello
* 本次使用构造方法时,程序第一次加载HelloWorld类,会先执行静态初始化块,对应给num3赋值3333,并输出"通过构造方法给num3赋初值!"
* 再执行普通初始化块,对应给num2赋值222并输出"通过构造方法给num2赋初值!"
* 最后才执行构造方法,对应给num1赋值11并输出"通过构造方法给num1赋初值!"
System.out.println("hello的num1:"+hello.num1); //普通成员通过实例对象名访问
System.out.println("hello的num2:"+hello.num2);
System.out.println("hello的num3:"+num3); //静态成员直接访问
HelloWorld hello2=new HelloWorld(); //构造方法创建类的实例对象hello2
/*因静态初始化块只在加载时执行一次,所以这次创建对象时不执行静态初始化块
* 这里不执行也是可以理解的,因为构造方法本身不包括静态初始化块,自然不会执行
* 前次创建hello对象时并不是因为构造方法而去执行静态初始化块,而是加载类的时候执行
五、访问修饰符和this关键字
1、访问修饰符包括 private\protected\public;其中private范围对应为本类;protected 范围对应本类、子类和同包;public范围为本类、子类、同包及其他包。
如果没有使用访问修饰符,则默认的访问范围是本类和同包。
2、this关键字用于引用对象,实质上是对一个对象的引用。有些情况下可以省略this 关键字,如下例代码:
private void setName(String name){
this.name= //这里可以省略this,写成name=
在当需要返回一个类的对象时则必须显式的使用this关键字:
/*创建一个返回类型为Book类的方法 *方法名为getBook */使用this引用对象将Book类的对象返回public Book getBook(){
public class HanoiTower {
public static int moveTimes=0;//设置全类下的静态变量,计步器
public static void moveDish(int num,char a,char b,char c){
if(num==1){
System.out.println("Move dish from "+a+" to "+c);
moveTimes++;
moveDish(num-1,a,c,b);
//先把num-1个盘子从a通过c放到b;这里不涉及具体的移动,而是调用方法,所以计步器不加
System.out.println("Move dish from "+a+" to "+c);
//然后再把a上剩余的最后一个移动到c,这里涉及到具体的移动步骤,所以计步器加1
moveTimes++;
moveDish(num-1,b,a,c);
//最后解决b上的num-1个盘子,通过a再移动到c
public static void main(String[] args) {
// TODO Auto-generated method stub
int numTimes=12;
moveDish(numTimes,'A','B','C');
System.out.println("总共移动了"+moveTimes+"次!");
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
目前互联网行业中最紧缺的开发职位
在北上广拼搏,没点技术怎么行
今日搜狐热点浪潮烟草技术人员针对广东烟草12月10日内存溢出事件进行了广泛的技术探讨,并得到了一些建设性的建议和结论。
其中重点提到了:
关于websphere在控制台中进行重启应用,而不是重启整个websphere,这时候静态类是不能回收的,造成些类不能销毁,占用着内存,而 且这些内存是不能重复使用的,可以说是内存泄露。静态的类不能销毁,那么静态类引用的对象也不能销毁,因此一些bean都不能被正常回收,其实这些小对象 占用内存是很少的,最主要的是这些类引用的缓存没有销毁,这些缓存才是占用内存的大头。如果系统用了一两天,然后有人在控制台上将这个应用重启,那么缓存 将不能销毁,照成大量内存浪费,因此现在我们分析的dump文件中缓存一半的内存是由这些没有销毁的无用的缓存占用的。其实BSP中有个 HttpServletContextListener,这个监听器能够在关闭应用的时候清空缓存,但是从dump文件中可以看出这个监听器可能没有在应 用关闭的时候调用。
对于静态类支持有的对象销毁问题需要进行研究,解决Websphere的这种bug。
package com.
public class StaticCls{
public static void main(String[] args){
OuterCls.InnerCls.test();
class OuterCls{
public static class InnerCls{
public static void test(){
System.out.println("InnerCls");
所以大家认为是WAS的Bug导致:在进行应用重新启动过程中,一些类的Class MetatData本身描述不能从内存中被释放,同时在LouShang烟草系统中一些系统类型的类Class进行了大量类静态变量的定义,并在这些类静 态变量中存放了大量的对象。长此以往,多次重新启动应用从而大量的内存被占用,最终导致内存泄漏。
针对Java静态类的补充说明:通常一个普通类不允许声明为静态的,只有一个内部类才可以。在一个内部类中如果想提供静态方法访问的前提下,我们才会把此内部类设置为静态类。这时不需实例一个外部类和内部类,而可以直接调用内部内的静态方法。样例如下:
package com.
public class StaticCls {
public static void main(String[] args) {
OuterCls.InnerCls.test();
class OuterCls {
public static class InnerCls {
public static void test() {
System.out.println("InnerCls");
相信在LouShang烟草系统中这种静态类的应用场景并不多见,所以上面提到的静态类的说法并不准确,应该改正为:类静态变量。
虽然把“静态类”改变为“类静态变量”,但是上面提到的Class MetatData类本身描述不能从内存中被释放的问题确实存在。在周恒总12月14日询问HeapAnalyzer问题邮件中有一张图片能非常直观的说 明问题,实际生产环境HeapDump的分析文件图示如下:
其中class org/loushang/bsp/organization/domain/support/Stru描述的是这个类Stru本身的属性:
a) 其中Size (304)是描述类Stru本身的大小
b) 其中No.Child (43)描述的是Stru类中所有变量引用到和方法中使用到的类的个数
c) 其中 TotalSize (348,544,600)描述的是此Stru类中所有引用到和方法中使用到的类的大小 + 所有引用到和方法中使用到的类实例化对象的大小,所有值比较大。但是仅仅通过上图中罗列的TotalSize (348,544,600)并不能直接说明内存使用异常根源来自于Stru
在给周恒总的电子邮件回复中,我提到:“在图中存在两个不同地址、不同大小的class org/loushang/bsp/organization/domain/support/Stru实属有些怪异。一般情况下(在正常类加载器运行过程中)在当前内存中只会存在一份Class类的描述。”
当时我仅仅是觉得比较怪异:为什么在内存中出现了两份Stru类的Class描述?但是没有引起足够重视:认为它是一个严重问题。
在后续周恒总的邮件中提到:
“经我们的技术人员测试,发现两个class的问题是一个Websphere的bug,重启动ear应用后静态变量及其引用的对象不会被释放。每重启一次就多一个class行。”
随后卫兵总邮件12月19日邮件中在此提到:
“通过控制台重启的静态变量不释放。Was是有意这么做的,还是这个结论不正确?”
至此,形成了一个命题:
在WAS服务器中,如果重启J2EE应用(不重启WAS服务器),某些类型的类不能从内存中被回收。多次重启应用可能会导致内存泄漏?
这是不是WAS的一个Bug?
疑问:应用重启,导致内存泄漏?
针对这个疑问,我们可以求助于Google获得一些线索,我们可以通过检索关键词:OutOfMemoryError redeploy,来获得关于重启应用导致内存泄漏的大量信息。
我们把其中几个比较经典的来分享一下:
1、 在JBOSS服务器中重复部署启动应用,会导致OutOfMemory URL:
描述:OutOfMemory error when repetatively deploying and undeploying with 10 minute interval 问题仍然没有解决
2、 为什么重复部署应用时会导致Tomcat内存不断使用增加? URL:
描述:Why does the memory usage increase when I redeploy a web application? 问题仍然没有解决
3、 SUN JDK+Tomcat 5.5.20运行服务的时候遇到问题,重启应用,服务器几天后就会挂掉,并报java.lang.OutOfMemoryError: PermGen space异常。 URL:
描述:推断可能是由于SUN JDK 的BUG引起加载到SUN JVM Perm区域的Class不能被回收导致内存泄漏,推荐使用IBM JDK 或 BEA JRokit虚拟机解决问题。(我的评论:其实不是JVM BUG导致的)
4、 认为是Spring导致OutOfMemory,展开大讨论 URL:
5、 有的认为是SUN JDK Bug引起的 URL:
描述:2003年的时候就有一个bug报告给sun,但是到现在,这个bug还没有close!有人在这个bug加了句评语:“A bug this critical is open since 2003? Absolutely shameful.”大家觉得SUN在这个BUG上确实有些丢脸,其实SUN并不认为这是JVM本身BUG引起的,一定是应用本身的BUG导致的。
真是众说纷纭,并没有一个确切的答案。
但是我们可以认定此问题比较普遍,看上去并不是由于WAS应用服务器、JDK/JRE、Tomcat、Spring、Hibernate等中间件Bug引起的,下面会来论述一下我们猜想:
1、 针对Class的MetaData类描述在SUN JDK中是由专门的内存空间PermGen space来存储的(PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同),而PermGen空间缺省比较小为4M。所以一旦出现应用重新启动并存在相同Class重复加载的情况,极易导致PermGen溢出,从而直接导致“java.lang.OutOfMemoryError: PermGen space”的出现。 换用IBM JDK或BEA JRokit JVM看似解决问题,其实并没有根本解决Class类重复加载的问题。只不过在IBM JDK或BEA JRokit JVM中并没有专门的PermGen空间来存放Class类描述,而是与JVM Heap共用空间,所以重复加载的Class并不能马上导致内存溢出。但是日积月累,问题仍然会显现出来,就像广东烟草问题一般。
2、 为什么同样的问题,在不同J2EE平台、不同J2EE框架、不同JDK都同样出现?看上去并不像是由这些中间件Bug导致,难道这些不同厂商、开发人员开发的代码存在同样的Bug?
真是事实胜于雄辩,我们还是用事实来说话吧:我们想办法开发一些场景来再现这个问题。
问题的再现
如何判断在重启应用后出现Class类重复加载的问题?
针对Class是否被重复加载的这个问题,市面上的所有JVM Profiling诊断工具都无法进行有效的跟踪和调试。目前唯一可行的方式:就是使用IBM JVM运行存在问题的应用,通过JVM接口或Unix环境中kill -3 &Java_PID&的方式让JVM产生当前JVM HeapDump文件,据此我们可以使用IBM HeapAnalyzer工具来分析是否存在Class类重复加载的问题。
为了简化产生Java HeapDump的过程,我们专门开发了用于产生HeapDump文件的JSP页面,以方便我们在Windows平台的测试和验证。
&!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&
&%@page language="java" contentType="text/ charset=GB18030" pageEncoding="GB18030"%&
&html&&head&
&title&dump&/title&
&meta http-equiv="Content-Type" content="text/ charset=GB18030"&&/head&
&h2& 产生HeapDump 和 JavaCore&/h2&
&%String heapdumpCmd = request.getParameter("heapdump");
if(heapdumpCmd!=null) com.ibm.jvm.Dump.HeapDump();
String javacoreCmd = request.getParameter("javacore");
if(javacoreCmd != null) com.ibm.jvm.Dump.JavaDump();
String gcCmd = request.getParameter("gc");
if(gcCmd != null) System.gc();%&
&form action="dump.jsp"&
&input type="submit" name="gc" value="GarbageCollection"&
&input type="submit" name="heapdump" value="CreateHeapDump"&
&input type="submit" name="javacore" value="CreateJavaCore"&&/form&
尝试编写样例再现Class重复加载的问题
根据浪潮烟草开发中心和浪潮技术研发中心的反馈,一直认为是由于类静态变量的采用导致Class无法被释放,从而出现Class重复加载的问题。为此我们模拟以下代码:
ClassLoadBugServlet.java
public class ClassLoadBugServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet{
private static byte[] testByteArray = newbyte[2024000];
public ClassLoadBugServlet(){
protected void doGet(HttpServletRequest request,HttpServletResponse response) throwsServletException,IOException{
perform(request,response);
protected void doPost(HttpServletRequest request,HttpServletResponse response) throwsServletException,IOException{
perform(request,response);
private void perform(HttpServletRequest request,HttpServletResponse response) throwsIOException{
StaticClasss c = newStaticClass();
response.getOutputStream().print(sc.test());
System.out.println(sc.test());
StaticClass.java
public class StaticClass{
privates tatic byte[] testByteArray = newbyte[2024000];
public String test(){
return"TestClassLoader";
public class StaticClass {
private static byte[] testByteArray = new byte[2024000];
public String test(){ return "Test Class Loader"; }
使用以上代码,我们部署到WAS进行对应的测试,重复运行、重新启动应用数十次,使用上面的dump.jsp产生我们所需要的JVM HeapDump,然后使用IBM HeapAnalyzer进行分析,并没有出现我们上面提到的Class重复加载的问题。
实验一度陷入困境。
Class重复加载问题得以再现
根据浪潮软件浪潮烟草v3和LouShang v3系统核心框架构建在Spring平台之上,并在Internet网站上有网页反映Spring平台存在Class重复加载的问题
为此我们把上面的样例进行了改造,使用Spring框架来加载StaticClass,进一步验证是否存在Class重复加载的问题。
ClassLoaderTestServlet.java
public class ClassLoaderTestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet{
public ClassLoaderTestServlet(){super();}
protected void doGet(HttpServletRequest request,HttpServletResponse response) throwsServletException,IOException{
perform(request,response);
protected void doPost(HttpServletRequest request,HttpServletResponse response) throwsServletException,IOException{
perform(request,response);
private void perform(HttpServletRequest request,HttpServletResponse response) throwsIOException{
StaticClasss c = (StaticClass)getClassPathApplicationContext().getBean("staticClass");
response.getOutputStream().print(sc.test());
System.out.println(sc.test());
private ApplicationContext getWebApplicationContext(){
WebApplicationContextwac=
wac=(WebApplicationContext)getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
private ApplicationContext getClassPathApplicationContext(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/serviceContext.xml");
class ClassLoaderTestServlet extends javax.servlet.http.HttpServlet
implements javax.servlet.Servlet {
public ClassLoaderTestServlet() { super(); }
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{ perform(request, response); }
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{ perform(request, response); }
private void perform(HttpServletRequest request,
HttpServletResponse response) throws IOException {
StaticClass sc = (StaticClass)
getClassPathApplicationContext().getBean("staticClass");
response.getOutputStream().print(sc.test());
System.out.println(sc.test());
private ApplicationContext getWebApplicationContext()
{ WebApplicationContext wac =
(WebApplicationContext)getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
private ApplicationContext getClassPathApplicationContext()
{ ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("spring/serviceContext.xml");
spring/serviceContext.xml
version="1.0"encoding="GBK"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
default-autowire="byName"default-lazy-init="true"
id="staticClass"class="com.test.StaticClass"
version="1.0" encoding="GBK"?&
&beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
default-autowire="byName" default-lazy-init="true"&
&bean id="staticClass" class="com.test.StaticClass"/&
同样方法我们使用以上代码,部署到WAS进行对应的测试,重复运行、重新启动应用数十次,使用上面的dump.jsp产生我们所需要的JVM HeapDump,然后使用IBM HeapAnalyzer进行分析,最终出现了我们上面提到的Class重复加载的问题。
难道是Spring的Bug导致了Class类重复加载?不可想象?
为了进一步定位在Spring中存在Class重复加载的问题,我们有必要阐述一下JVM内存垃圾回收和Class类加载的基本原理。
JVM GC 垃圾回收机制概述
JVM GC即Java虚拟机垃圾收集机制是指JVM用于释放那些不再使用的对象所占用的内存。Java语言并不要求JVM有GC,也没有规定GC如何工作。不过常用的JVM都有GC,而且大多数GC都使用类似的算法管理内存和执行收集操作。
垃圾收集的目的在于清除不再使用的对象,现在大多数JVM通过采用对象引用遍历的方式(确定对象是否被活动对象引用)来确定是否收集该(垃圾)对 象。对象引用遍历从根线程对象开始,沿着整个对象图上的每条对象引用链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根线程对象 引用到达,则将它作为垃圾收集。
Class类加载的基本机理
像IBM WAS等J2EE应用服务器允许编写的多个J2EE应用EAR/WAR部署到同一台J2EE应用服务器上。如果其中某一个J2EE应用发生改变了,我们只 要针对此EAR/WAR进行更新,重新部署、启动此EAR/WAR应用,并不需要重新启动部署所在的应用服务器,从而不影响部署在同一应用服务器上其他应 用的运行。
这种功能的实现主要是由于在WAS等J2EE服务器中,针对不同应用EAR/WAR提供了不同的ClassLoader类加载器,使用各自的 ClassLoader来加载自身的Class类,故而各个不同EAR/WAR应用之间不会互相影响。简单的来说ClassLoader本身也是一个标准 的Java Class(由J2EE容器提供),只不过其特殊在仅仅用于加载在/WEB-INF/classes或JAR文件中的Class类。正常情况下,当你停止 此应用时,此应用EAR的ClassLoader将会被J2EE应用服务器所丢弃成为垃圾,故而所有由此EAR ClassLoader类加载器所加载的类将会被丢弃成为垃圾,最终会为JVM GC所回收。
而在各个J2EE应用服务器中都存在不同层次的ClassLoader,现我们以WAS 应用服务器为例(其他服务器的ClassLoader请参考《 》):
Websphere类加载机制
Java应用程序运行时,在Class执行和被访问之前,它必须通过类加载器加载使之有效,类加载器是JVM代码的一部分,负责在JVM虚拟机中查 找和加载所有的Java 类和本地的lib库。类加载器的不同配置影响到应用程序部署到应用程序服务器上运行时的行为。JVM和WebSphere应用程序服务器提供了多种不同的 类加载器配置, 形成一个具有父子关系的分层结构。
WebSphere中类加载器的层次结构图示
如上图所示,WebSphere中类加载器被组织成一个自上而下的层次结构,最上层是系统的运行环境JVM,最下层是具体的应用程序,上下层之间形成父子关系。
a) JVM Class loader:位于整个层次结构的最上层,它是整个类加载器层次结构的根,因此它没有父类加载器。这个类加载器负责加载JVM类, JVM 扩展类,以及定义在classpath 环境变量上的所有的Java类。
b) WebSphere Extensions Class loader:WebSphere 扩展类加载器, 它将加载WebSphere的一些runtime 类,资源适配器类等。
c) WebSphere lib/app Class loader:WebSphere服务器类加载器,它将加载WebSphere安装目录下$(WAS_HOME)/lib/app路径上的类。 在WAS v4版本中,WAS使用这个路径在所有的应用程序之间共享jar包。从WAS v5开始, 共享库功能提供了一种更好的方式,因此,这个类加载器主要用于一些原有的系统的兼容。
d) WebSphere "server" Class loader:WebSphere应用服务器类加载器。 它定义在这个服务器上的所有的应用程序之间共享的类。WAS v5中有了共享库的概念之后,可以为应用服务器定义多个与共享库相关联的类加载器,他们按照定义的先后顺序形成父子关系。
e) Application Module Class Loader:应用程序类加载器,位于层次结构的最后一层,用于加载J2EE应用程序。根据应用程序的类加载策略的不同,还可以为Web模块定义自己的类加载器。
关于WebSphere的类加载器的层次结构,以下的几点说明可能更有助于进一步的理解类的查找和加载过程:
a) 每个类加载器负责在自身定义的类路径上进行查找和加载类。
b) 一个子类加载器能够委托它的父类加载器查找和加载类,一个加载类的请求会从子类加载器发送到父类加载器,但是从来不会从父类加载器发送到子类加载器。
c) 一旦一个类被成功加载,JVM 会缓存这个类直至其生命周期结束,并把它和相应的类加载器关联在一起,这意味着不同的类加载器(平级或上下级之间)可以加载相同名字的类。
d) 如果一个加载的类依赖于另一个或一些类,那么这些被依赖的类必须存在于这个类的类加载器查找路径上,或者父类加载器查找路径上。
如果一个类加载器以及它所有的父类加载器都无法找到所需的类,系统就会抛出ClassNotFoundExecption异常或者NoClassDefFoundError的错误。
JVM GC垃圾回收和ClassLoader类加载器之间的微妙关系
如果您的应用中存在以下类似代码:
private void x1(){
Listc=newArrayList();
private void x1() {
for (;;) {
List c = new ArrayList();
这样代码运行时会不断重复地申请ArrayList新对象内存空间,但是此代码并不会导致内存泄漏OutOfMemory的现象。因为不断申请的 ArrayList新对象会被立即丢弃成为垃圾对象,最终在JVM GC过程中回收,并释放出所占用的Heap内存空间,从而我们可以不断地申请到新对象所需的内存空间。
现在我们以Servlet为例,演示下面代码正常运行情况下在内存中的使用情况
public class Servlet1 extends HttpServlet{
private static final String STATICNAME="Simple";
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
public class Servlet1 extends HttpServlet {
private static final String STATICNAME = "Simple";
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
当此Serlvet1类被加载到内存中运行时,以下对象objects和类Class将会在内存中存在,互相关联关系图示如下(核心的几个类和对象):
图中黄色方框标识的是由应用ClassLoader类加载器(AppClassLoader)加载的类和对应的类实例化对象,其他以绿色方框进行标 识。其中图示化(简化)的J2EE容器对象Container引用指向了为此J2EE应用所创建的应用类加载器AppClassLoader对象,与此同 时引用还指向了由应用类加载器AppClassLoader所加载创建的Serlvet1实例对象。当有外界HTTP请求此Servlet1时,容器 Container将会调用Servlet1实例对象的doGet()方法来提供服务。
其中几点应该引起注意:
a) 其中STATICNAME是被Class Servlet1类本身所有,并不属于Servlet1类实例对象。
b) 其中Servlet1实例对象含有引用指向Servlet1.class类本身。
c) 每一个Class类含有一个引用指向加载此Class类的类加载器AppClassLoader对象。
d) 同时每一个类加载器AppClassLoader对象含有一个引用指向由其加载的类Class
从上面的图示中我们可以清晰的得知,如果AppClassLoader以外的类加载器所加载的对象引用了任何一个由AppClassLoader加载的对象,那么由AppClassLoader加载的任何Class(包括AppClassLoader本身)将不能被垃圾回收。此结论非常重要,这是出现上面我们描述Class内存泄漏现象最根本的原因,后面我们会阐述此现象是如何被触发的。
正常情况下,如果上面部署的应用被卸载或被停止,那么Container对象将会与应用相关的任何类和对象(如Servlet1实例对象、 AppClassLoader类加载器实例)断开引用关联关系,从而这些与被停止应用相关的所有类和类实例将会被JVM进行抛弃成为垃圾并进行内存回收。
正如上图所示,Servlet1应用相关的类、对象、类加载器对象等等所有的一切都和根线程对象没有任何的关联,从而最终会被JVM进行垃圾回收。
现在我们来演示一个非正常情况下的样例,正是此“非正常”导致应用的所有Class类不能从内存中正确销毁。此处我们在原来的样例Servlet1中引入一个特殊的Class类和其实例:Level,改写样例代码如下:
package com.
import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LeakServlet extends HttpServlet{
private static final String STATICNAME = "Thisleaks!";
private static final Level CUSTOMLEVEL = new Level("test",550){};//anonclass!
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
Logger.getLogger("test").log(CUSTOMLEVEL,"doGetcalled");
package com.
import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LeakServlet extends HttpServlet {
private static final String STATICNAME = "This leaks!";
private static final Level CUSTOMLEVEL = new Level("test", 550) {}; // anon class!
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Logger.getLogger("test").log(CUSTOMLEVEL, "doGet called");
注意其中CUSTOMLEVEL是一个匿名类型的类实例对象,因为Level类的构造方法属性的protected,不能被直接构造,所以我们必须创建新的类new Level("test", 550) {},从而形成LeakServlet的内置类,最终编译会生成LeakServlet$1.class
当此LeakServlet.class类被加载到内存中运行时,以下对象objects和类Class将会在内存中存在,互相关联图示如下(核心的几个类和对象):
在这张图示中出现了你意想不到的景象: 系统Level类实例使用了一个类静态变量 ArrayList来保存所有创建的所有类型Level的实例对象,我们可以通过JDK Level类源代码来进行验证:
public class Level implements java.io.Serializable{
private static java.util.ArrayList known = new java.util.ArrayList();
protected Level(String name,int value){
this(name,value,null);
protected Level(String name,int value,String resourceBundleName){
if(name==null){
throw new NullPointerException();
this.name=
this.value=
this.resourceBundleName=resourceBundleN
synchronized(Level.class){known.add(this);}
public class Level implements java.io.Serializable {
private static java.util.ArrayList known = new java.util.ArrayList();
protected Level(String name, int value) {
this(name, value, null);
protected Level(String name, int value, String resourceBundleName) {
if (name == null) {
throw new NullPointerException();
this.name =
this.value =
this.resourceBundleName = resourceBundleN
synchronized (Level.class) { known.add(this); }
当此应用被卸载或停止时,那么JVM GC能做那些事情呢?
严重的事情发生了,在所有类和实例对象中仅仅是LeakServlet实例对象才能被JVM GC回收,其他的任何由AppClassLoader加载的类都无法被JVM GC从内存中销毁删除。
因为Level类属于WebSphere应用服务器JRE核心系统类,存在于JRE核心类库core.jar文件中,是由JVM ClassLoader最上层类加载器来进行加载的,也就是Level.class类并不隶属于应用的类加载器AppClassLoader。
当应用被卸载或停止时,Level.class是由系统JVM加载的,所以Level.class是不会被回收的,那么它引用的 CUSTOMLEVEL实例变量和其对应的类LeakServlet$1将不会被回收,从而导致AppClassLoader对象无法被JVM回收,从而 最终导致此应用中的所有Class类(Class MetaData属性描述)无法被JVM当作垃圾来进行内存回收。
当应用重新启动时,将由新创建的AppClassLoader来重新加载所有的Class类,那么此时内存中就存在了同一Class类的多分拷贝。
这样就形成了臭名昭著的ClassLoader类加载内存泄漏问题。
严重警告:如果存在任何来自应用以外的引用,引用了由J2EE应用本身类加载器加载的类和类实例对象,那么ClassLoader类加载内存泄漏问题就出现了。
ClassLoader类加载内存泄漏问题的定位
正如上所述Classloader类加载内存泄漏是由于不断重复地启动、停止应用导致Class多次加载,并由于系统一级对象引用的关系,导致以前创建的类无法被回收,从而导致内存泄漏。
定位ClassLoader 类加载内存泄漏的根源还是非常困难的,因为任何现有的JVM Profiling工具都不能通过ClassLoader的视角来分析当前内存中所存在的Class类。 目前我们只能通过产生JVM Heapdump的方式来鉴定是否存在ClassLoader 类加载内存泄漏,再通过以上ClassLoader 类加载内存泄漏产生的机理来排查可能出现问题的地方,最终解决问题。
好像在JDK6工具集中提供了相应的工具来定位问题。请参考
为了简化大家排查和定位应用中可能存在ClassLoader 类加载内存泄漏的过程,为此我们罗列了一些能导致Classloader类加载内存泄漏的代码和组件:(我们此处就不铺开篇幅阐述下面组件导致内存泄漏的根源,就当作大家的作业吧!哈哈)
a) 应用或使用的组件中使用了java.util.logging.Level那你得注意了。
b) 如果使用了诸如DBCP等基于DriverManager API基础上开发的数据库连接池组件,如果底层设计考虑不周,极易引发Classloader类加载内存泄漏。
c) 如果你使用到了commons-logging组件(其实很多OpenSource组件都依赖于commons-logging),那十有八九 会出现Classloader类加载内存泄漏。因为在象WebSphere、Tomcat等服务器核心引擎中同样使用到了commons-logging 组件,并在应用启动之前commons-logging的很多类已经被系统级ClassLoader所加载。缺省状态下,一个类的加载是从JVM类加载器 开始的,这样系统commons-logging的优先级一般高于应用EAR中所包含的commons-logging,所以Classloader类加 载内存泄漏就有可能出现了。问题简单分析如下:
1) 我们一般在应用中使用commons-logging的API来获得Log:protected final Log logger = LogFactory.getLog(getClass())。
2) 为此我们分析commons-logging类库中LogFactory类,请注意其中factories是类静态变量,而getFactory()方法是静态方法,都是属于类属性。 通过下面代码我们可以清晰的得知:如果LogFactory在应用EAR上一级的 类加载路径中被加载,那么在应用类加载器加载、创建的LogFactory实例(不管 org.apache.commons.logging.impl.LogFactoryImpl还是 org.apache.commons.logging.impl.Log4jFactory),将会被上一级类加载器中的LogFactory类所强制 性地引用并存储在静态变量factories的类属性中。 故而即使强行停止此EAR应用,但是由于系统类加载器加载的LogFactory中的factories强制引用了此应用创建的LogFactory实例对象不能被进行垃圾回收,从导致所有的Class无法被销毁,最终形成Classloader类加载内存泄漏。
LogFactory.java
public abstract class LogFactory{
protected static Hashtable factories=
publics tatic Log getLog(Class clazz) throws LogConfigurationException{
returngetFactory().getInstance(clazz);
public abstract Log getInstance(Class class1) throws LogConfigurationE
public static LogFactory getFactory() throws LogConfigurationException{
ClassLoader contextClassLoader=getContextClassLoader();
LogFactory factory=getCachedFactory(contextClassLoader);
if(factory!=null)
//下面大量的代码被删除,主要用于:创建由应用加载的新LogFactory对象factory
if(factory!=null){
cacheFactory(contextClassLoader,factory);
private static void cache Factory(ClassLoader classLoader,LogFactory factory){
if(factory!=null)
if(classLoader==null)
nullClassLoaderFactory=
factories.put(classLoader,factory);
public abstract class LogFactory {
protected static Hashtable factories =
public static Log getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
public abstract Log getInstance(Class class1) throws LogConfigurationE
public static LogFactory getFactory() throws LogConfigurationException {
ClassLoader contextClassLoader = getContextClassLoader();
LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null)
//下面大量的代码被删除,主要用于:创建由应用加载的新LogFactory 对象 factory
if (factory != null) {
cacheFactory(contextClassLoader, factory);
private static void cacheFactory(ClassLoader classLoader, LogFactory factory) {
if (factory != null)
if (classLoader == null)
nullClassLoaderFactory =
factories.put(classLoader, factory);
d) 把log4j类库放置到系统类路径下(比如:JVM、WebSphere Extensions Class loader、WebSphere lib/app Class loader、WebSphere "server" Class loader类路径),并且使用log4j的“Context Repository Selector”模式来获得各个应用的logging配置。 如果此时应用EAR/WAR中包含log4j类库将会出现Class Cast Exceptions异常不能正常运行;如果应用EAR/WAR中不包含log4j类库,虽然应用能够正常运行但是会导致Classloader类加载内 存泄漏。关于log4j的“Context Repository Selector”模式请参考
e) 如果你开发的组件中使用了java.beans.Introspector来进行Class/Method MetaData的缓存,可能会引发Classloader类加载内存泄漏。 每次解析Java Bean 的属性、方法是比较耗CPU资源的,所以在很多的框架级组件如Spring中普遍使用java.beans.Introspector来Cache缓存JavaBean的定义,Introspector类中会使用private static Map beanInfoCache = Collections.synchronizedMap(new WeakHashMap())类静态变量的方式来进行保存JavaBean的定义。 而Introspector是由系统JVM ClassLoader进行加载的,所以应用中定义的JavaBean Class将会被系统类加载器加载的Introspector强制引用,从而导致在应用被停止的状态下,所有与此应用相关的类无法被回收。 Introspector具体代码如下:
java.beans.Introspector.java
package java.
public class Introspector{
private static Map declaredMethodCache=Collections.synchronizedMap(newWeakHashMap());
private static Map beanInfoCache=Collections.synchronizedMap(newWeakHashMap());
public static BeanInfo getBeanInfo(Class&?&beanClass)throws IntrospectionException{
if(!ReflectUtil.isPackageAccessible(beanClass)){
return (new Introspector(beanClass,null,USE_ALL_BEANINFO)).getBeanInfo();
BeanInfo bi=(BeanInfo)beanInfoCache.get(beanClass);
if(bi==null){
bi=(new Introspector(beanClass,null,USE_ALL_BEANINFO)).getBeanInfo();
beanInfoCache.put(beanClass,bi);
package java.
public class Introspector {
private static Map declaredMethodCache = Collections.synchronizedMap(new WeakHashMap());
private static Map beanInfoCache = Collections.synchronizedMap(new WeakHashMap());
public static BeanInfo getBeanInfo(Class&?& beanClass) throws IntrospectionException
if (!ReflectUtil.isPackageAccessible(beanClass)) {
return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
BeanInfo bi = (BeanInfo)beanInfoCache.get(beanClass);
if (bi == null) {
bi = (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
beanInfoCache.put(beanClass, bi);
我们同样可以在Spring org.springframework.beans.CachedIntrospectionResults类的注释中,清晰的得知Spring中可能会存在Introspection Classloader类加载内存泄漏:“Internal class that caches JavaBeans {@link java.beans.PropertyDescriptor} information for a Java class. Not intended for direct use by application code. Necessary for own caching of descriptors within the application's ClassLoader, rather than rely on the JDK's system-wide BeanInfo cache (in order to avoid leaks on ClassLoader shutdown).” 在CachedIntrospectionResults中同样使用了类静态变量classCache来缓存类的定义,如果Spring的类库存在于应用类加载器上一级的JVM系统或应用服务器类路径上,则有可能导致Classloader类加载内存泄漏。 CachedIntrospectionResults具体代码如下:
org.springframework.beans.CachedIntrospectionResults.java(注:针对Spring2.0.7以后的版本)
package org.springframework.
final class CachedIntrospectionResults{
private static final L
private static final Map classCache=Collections.synchronizedMap(newWeakHashMap());
private final BeanInfo beanI
private final Map propertyDescriptorC
logger=LogFactory.getLog(org.springframework.beans.CachedIntrospectionResults.class);
public static CachedIntrospectionResultsforClass(ClassbeanClass) throws BeansException{
CachedIntrospectionResults results=
Object value=classCache.get(beanClass);
if(results==null){
results=new CachedIntrospectionResults(beanClass);
boolean cacheSafe=isCacheSafe(beanClass);
if(logger.isDebugEnabled())
logger.debug("Class["+beanClass.getName()+"]is"+(cacheSafe?"":"not")+"cache-safe");
if(cacheSafe)
classCache.put(beanClass,results);
classCache.put(beanClass,new WeakReference(results));
} else if(logger.isDebugEnabled())
logger.debug("Usingcachedintrospectionresultsforclass["+beanClass.getName()+"]");
private CachedIntrospectionResults(Class clazz) throws BeansException{
beanInfo=Introspector.getBeanInfo(clazz);
Class classToFlush=
package org.springframework.
final class CachedIntrospectionResults
private static final L
private static final Map classCache = Collections.synchronizedMap(new WeakHashMap());
private final BeanInfo beanI
private final Map propertyDescriptorC
{logger = LogFactory.getLog(org.springframework.beans.CachedIntrospectionResults.class); }
public static CachedIntrospectionResults forClass(Class beanClass)
throws BeansException
CachedIntrospectionResults results =
Object value = classCache.get(beanClass);
if(results == null)
results = new CachedIntrospectionResults(beanClass);
boolean cacheSafe = isCacheSafe(beanClass);
if(logger.isDebugEnabled())
logger.debug("Class [" + beanClass.getName() + "] is " + (cacheSafe ? "" : "not ") + "cache-safe");
if(cacheSafe)
classCache.put(beanClass, results);
classCache.put(beanClass, new WeakReference(results));
if(logger.isDebugEnabled())
logger.debug("Using cached introspection results for class [" + beanClass.getName() + "]");
private CachedIntrospectionResults(Class clazz)throws BeansException
beanInfo = Introspector.getBeanInfo(clazz);
Class classToFlush =
f) 在commons-beanutils 1.7版本(包括1.7版本)的组件中存在Classloader类加载内存泄漏,只有最新的1.8.0Beta修正了此潜在的问题, 问题描述: * [BEANUTILS-59] - Memory leak on webapp undeploy in WrapDynaClass * [BEANUTILS-156] - Memory leak on webapp undeploy in MappedPropertyDescriptor 详细描述请参考:
g) 在应用中使用了commons-beanutils 的MethodUtils来对类的方法Method进行操作,那同样存在Classloader类加载内存泄漏的可能。 如果commons-beanutils类库放置在应用上一级的类加载路径中,并且有其他应用(或系统代码)在此应用之前使用同样方式MethodUtils来对Class的Method进行操作(在其他类加载器上加载MethodUitls),那么Classloader类加载内存泄漏必然出现。我们可以参考MethodUtils对应代码,可以非常直观地定位问题:
package org.apache.commons.
public class MethodUtils{
privatestaticWeakHashMapcache=newWeakHashMap();
public static Method getAccessibleMethod(Class clazz,String methodName,Class parameterTypes[])
md=new MethodDescriptor(clazz,methodName,parameterTypes,true);
method=(Method)cache.get(md);
if(method!=null)
method=getAccessibleMethod(clazz.getMethod(methodName,parameterTypes));
cache.put(md,method);
}catch(NoSuchMethodException e){
package org.apache.commons.
public class MethodUtils
private static WeakHashMap cache = new WeakHashMap();
public static Method getAccessibleMethod(Class clazz, String methodName, Class parameterTypes[])
md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
method = (Method)cache.get(md);
if(method != null)
method = getAccessibleMethod(clazz.getMethod(methodName, parameterTypes));
cache.put(md, method);
}catch(NoSuchMethodException e){
h) 如果应用中使用到Java 1.5语法定义的 enum 类,而此定义的类放置在应用上一级的类加载路径中。首先在我们开发的 应用类加载器中加载并初始化了应用中定义的enum类,随后其他应用EAR/WAR(或系统代码)也使用到此定义的enum类,在并把此类enum属性引 用放置到(针对其他应用的)类静态变量或Servlet类变量,那么我们开发应用的Classloader类加载器将不会被回收,最终内存泄漏必然出现。 举例如下:
OperationEnum.java
package com.test.
public enum OperationEnum{
QUOTE(1),ISSUE(2),RENEW(4),CANCEL(12),
ENDORSE(16),CHANGE(64),REINSTATE(192);
private int operation=0;
private OperationEnum(intop){
this.operation=
public boolean isNewOperation(){
return (this.operation==2)||
(this.operation==4)||
(this.operation==192);
package com.test.
public enum OperationEnum {
QUOTE(1), ISSUE(2), RENEW(4), CANCEL(12),
ENDORSE(16), CHANGE(64), REINSTATE(192);
private int operation = 0;
private OperationEnum(int op) {
this.operation =
public boolean isNewOperation() {
return (this.operation==2) ||
(this.operation==4) ||
(this.operation==192);
其他EAR应用中使用到上面定义enum类的样例代码
public class LeakCauseServletInOtherApp extends HttpServlet{
private final OperationEnum operation=OperationEnum.CHANGE;//
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
doSomething(request,response);
public class LeakCauseServletInOtherApp extends HttpServlet {
private final OperationEnum operation = OperationEnum.CHANGE; //
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doSomething(request, response);
i) 导致Classloader类加载内存泄漏的另外一个重要因素就是:如果在框架中或应用使用ThreadLocal线程数据空间来存储实例对象,你必须知道在WAS等应用服务器中线程实例都是属于池态的,是由应用服务器WebContainer等容器来维护这些线程实例。 即使应用被停止了,这些池态的线程实例仍然属于存活运行状态,如果应用Web Servlet线程运行过程中在ThreadLocal上存储的实例对象没有被正确删除,可能导致线程类加载内存泄漏问题。 在老版本的DOM4J、Mozilla Rhino、CGLIB都存在这种类型的线程内存泄漏,请使用这些组件的最新版本来避免此类泄漏问题的发生。
1) Hibernate 3.2.2版本中存在ThreadLocal 线程变量内存泄漏问题,在3.2.3版本中得到修订。详细内容请参考“Big memory leak in the use of CGLIB”
2) CGLIB 2.1存在ThreadLocal 线程变量内存泄漏问题,在最新的版本2.1_3中问题应该得到修订。详细内容请参考
3) dom4j 1.6之前的版本存在ThreadLocal 线程变量内存泄漏问题,在1.6以后的版本中此问题得到解决。 问题描述: Bug修订描述:“Added a SingletonStrategyclass for managing singletons. This allows to use different strategies for singletons, like: one instance per VM, one instance per thread, ... This change removed the usage of ThreadLocals.”
ClassLoader类加载内存泄漏问题的解决方案
ClassLoader类加载内存泄漏问题解决的基本原则:
1、 不要把应用使用的类库放置到JRE或WebSphere服务器的类加载器路径中,尽量把使用的类库保持在EAR 或WAR/WEB-INF/Lib路径中。
2、 尽量在WebSphere服务器中设置类加载顺序为“Child-First ClassLoaders”/“Parent-Last ClassLoaders”。
3、 针对DOM4J、Mozilla Rhino、CGLIB请确认使用最新的版本,并确认类库保存在应用EAR级别之下。
4、 尽量避免使用Java 1.5语法定义的 enum 类,如果使用了enum类,必须确认开发的类库保持在应用EAR类加载器这一级别之下,而千万不能放置到WebSphere或JVM类库路径中。
5、 使用最新版本的commons-logging,并确认类库保存在应用EAR级别之下。
6、 使用最新版本的commons-beanutils,并确认类库保存在应用EAR级别之下,千万不能放置到WebSphere或JVM类库路径中。
7、 使用最新版本的log4j,并确认类库保存在应用EAR级别之下,千万不能放置到WebSphere或JVM类库路径中。
8、 不要在生产环境中使用DriverManager。
9、 不要在生产环境中使用commons-dbcp作为数据源实现,推荐使用应用服务器提供的数据源。
10、 不要在应用中使用java.util.logging.Level。
由于无法避免commons-logging类库存在于WebSphere应用服务器类路径和大量J2EE OpenSource组件使用java.beans.Introspector来Cache缓存JavaBean定义的事实,针对这种情况我们如何来应对和处理呢?
就像我们上面为了再现Class重复加载问题而编写的ClassLoaderTestServlet样例中,使用最简单的方式调用有Spring管 理的类实例对象StaticClass sc,就发生了臭名昭著的Class类加载内存泄漏问题。我们如何来避免此问题的发生?
针对java.beans.Introspector内存泄漏问题
其实在Spring框架2.0.7以后的版本中已经对此有了对应的解决方案,提供了一个专门处理 Java.beans.Introspector内存泄漏问题的辅助类: org.springframework.web.util.IntrospectorCleanupListener,并附有专门的文档进行说明说明。 文档原话如下:
Listener that flushes the JDK's JavaBeans Introspector cache on web app shutdown. Register this listener in your web.xml to guarantee proper release of the web application class loader and its loaded classes.
If the JavaBeans Introspector has been used to analyze application classes, the system-level Introspector cache will hold a hard reference to those classes. Consequently, those classes and the web application class loader will not be garbage-collected on web app shutdown! This listener performs proper cleanup, to allow for garbage collection to take effect.
Unfortunately, the only way to clean up the Introspector is to flush the entire cache, as there is no way to specifically determine the application's classes referenced there. This will remove cached introspection results for all other applications in the server too.
Note that this listener is not necessary when using Spring's beans infrastructure within the application, as Spring's own introspection results cache will immediately flush an analyzed class from the JavaBeans Introspector cache and only hold a cache within the application's own ClassLoader. Although Spring itself does not create JDK Introspector leaks, note that this listener should nevertheless be used in scenarios where the Spring framework classes themselves reside in a 'common' ClassLoader (such as the system ClassLoader). In such a scenario, this listener will properly clean up Spring's introspection cache.
Application classes hardly ever need to use the JavaBeans Introspector directly, so are normally not the cause of Introspector resource leaks. Rather, many libraries and frameworks do not clean up the Introspector: e.g. Struts and Quartz.
Note that a single such Introspector leak will cause the entire web app class loader to not get garbage collected! This has the consequence that you will see all the application's static class resources (like singletons) around after web app shutdown, which is not the fault of those classes!
This listener should be registered as the first one in web.xml, before any application listeners such as Spring's ContextLoaderListener. This allows the listener to take full effect at the right time of the lifecycle.
在上面的文档中,我们可以清晰的得知,如果把Spring类库放置到JVM系统或应用服务器一级别的类库路径中,我们必须在web.xml中配置 org.springframework.web.util.IntrospectorCleanupListener,才能防止Spring中可能存在 的Introspector内存泄漏。
web.xml样例配置如下:
web.xml配置文件 (此Spring Listener只有在Spring2.0以后的版本才存在)
version="1.0"encoding="UTF-8"
id="WebApp_ID"version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
ClassLoader
contextConfigLocation
classpath*:spring/*.xml
org.springframework.web.util.IntrospectorCleanupListener
org.springframework.web.context.ContextLoaderListener
&?xml version="1.0" encoding="UTF-8"?&
&web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"&
&display-name&ClassLoader&/display-name&
&context-param&
&param-name&contextConfigLocation&/param-name&
&param-value&classpath*:spring/*.xml&/param-value&
&/context-param&
&!-- Spring 刷新Introspector防止内存泄露,推荐把此Listener放置在第一个,至少是Spring相关 Listener的第一个 --&
&listener&
&listener-class&org.springframework.web.util.IntrospectorCleanupListener&/listener-class&
&/listener&
&listener&
&listener-class&org.springframework.web.context.ContextLoaderListener&/listener-class&
&/listener&
org.springframework.web.util.IntrospectorCleanupListener样例代码如下:
IntrospectorCleanupListener.java (为Spring 2.0.7以后版本的代码)
package org.springframework.web.
public class IntrospectorCleanupListener implements ServletContextListener{
public void contextInitialized(ServletContextEvent event){
CachedIntrospection Results.acceptClassLoader(Thread.currentThread().getContextClassLoader());
public void contextDestroyed(ServletContextEvent event){
CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader());
Introspector.flushCaches();
package org.springframework.web.
public class IntrospectorCleanupListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader());
public void contextDestroyed(ServletContextEvent event) {
CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader());
Introspector.flushCaches();
针对WebSphere应用服务器类路径中存在commons-logging类库,应用中commons-logging的使用导致ClassLoader类加载内存泄漏问题
其实针对上面编写ClassLoaderTestServlet样例的EAR应用,我们在测试过程中并没有把Spring类库放置到 WebSphere应用服务器或JVM系统类库路径中,Spring类库仅仅存在于应用的WEB-INF/lib目录中(即:应用的类加载范围内),那为 什么还出现类加载内存泄漏?应该不是由Introspector内存泄漏问题引起的!
通过分析Spring源代码得知,Spring Bean定义加载诸如ClassPathXmlApplicationContext等类的父类AbstractApplicationContext中 使用了commons-logging组件来进行框架日志记录,所以ClassLoaderTestServlet样例测试中的内存泄漏是由 commons-logging导致的。
那么,我们如何避免commons-logging内存泄漏?
其实我们可以仿照上面Spring 框架中针对Introspector泄漏问题的解决方案,编写一个ServletContextListener来监听Servlet容器的生命周期,一 旦发现WebContainer被终止,我们可以主动释放存储在LogFactory类静态变量factories中所有由此应用产生的类实例对象,最终 解决commons-logging内存泄漏。
用于清除LogFactory类静态变量factories中实例对象的代码如下:
ApplicationLifecycleListener.java
packagecom.
import javax.servlet.ServletContextE
import javax.servlet.ServletContextL
import org.apache.commons.logging.LogF
public class ApplicationLifecycleListener implements ServletContextListener{
publicvoidcontextDestroyed(final ServletContextEvent sce){
LogFactory.release(Thread.currentThread().getContextClassLoader());
public void contextInitialized(final ServletContextEvent sce){
package com.
import javax.servlet.ServletContextE
import javax.servlet.ServletContextL
import org.apache.commons.logging.LogF
public class ApplicationLifecycleListener implements ServletContextListener {
public void contextDestroyed(final ServletContextEvent sce) {
LogFactory.release(Thread.currentThread().getContextClassLoader());
public void contextInitialized(final ServletContextEvent sce) {
当然我们必须把此ServletContextListener 注册到web.xml 中,web.xml样例配置如下:
web.xml配置文件
version="1.0"encoding="UTF-8"
id="WebApp_ID"version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2eehttp://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
ClassLoader
contextConfigLocation
classpath*:spring/*.xml
org.springframework.web.util.IntrospectorCleanupListener
org.springframework.web.context.ContextLoaderListener
com.test.ApplicationLifecycleListener
&?xml version="1.0" encoding="UTF-8"?&
&web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"&
&display-name&ClassLoader&/display-name&
&context-param&
&param-name&contextConfigLocation&/param-name&
&param-value&classpath*:spring/*.xml&/param-value&
&/context-param&
&!-- Spring 刷新Introspector防止内存泄露,推荐把此Listener放置在第一个,至少是Spring相关 Listener的第一个 --&
&listener&
&listener-class&org.springframework.web.util.IntrospectorCleanupListener&/listener-class&
&/listener&
&listener&
&listener-class&org.springframework.web.context.ContextLoaderListener&/listener-class&
&/listener&
&listener&
&listener-class&com.test.ApplicationLifecycleListener&/listener-class&
&/listener&
通过实验,我们最终解决了Spring标准应用中臭名昭著的ClassLoader类加载器内存泄漏问题。!
参考资料:
Tomcat和Websphere类加载机制
JVM的垃圾回收机制详解和调优
With updating ClassLoader several times, jdk1.4.1_05 server VM will be down with an error java.lang.OutOfMemoryError
java.lang.OutOfMemoryError: PermGen space及其解决方法
OutOfMemory error when repetatively deploying and undeploying with 10 minute interval in JBoss Application Server
How to fix the dreaded "java.lang.OutOfMemoryError: PermGen space" exception (classloader leaks)
Commons-logging Logging/UndeployMemoryLeak
Supporting the log4j RepositorySelector in Servlet Containers
commons-logging Classloader and Memory Management
http://commons.apache.org/logging/guide.html#Classloader and Memory Management
Big memory leak in the use of CGLIB
浏览: 22039 次
来自: 北京
事实上许多BI项目都会用到联合主键的。
aa 写道我实在是想不到有什么项目/场景需要用 ...
我实在是想不到有什么项目/场景需要用到联合主键的
这些lib在websphere 6.0.2.5的lib目录下确 ...
bootstrap.jar ecutils.jar emf.j ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 es6 class 静态变量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信