JVM 深入浅出

 关于JVM 内存,本博还有 Java GC 和 JVM 调优  和  Java Memory Leak详解 两篇文章。

 

每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分。

 

程序计数器(Program Counter Register)

在一个Java方法运行时,Program counter Register总是存放着下一条要执行的语句的节码指令的地址。每个线程都有自己的PC寄存器,各条线程之间互不影响,内存在任意时刻,一条JVM线程只会执行一个方法的代码。BTW, 如果该方法是native,那PC寄存器的值是undefined此区域是唯一一不会抛出OutOfMemoryError区域。

 

Java虚拟机栈(Java Virtual Machine Stack)

每启动一个线程,JVM都会为它分配一个Java栈,即线程私有,用于存放方法中的局部变量,操作数以及异常数据等。当线程调用某个方法时,JVM会根据方法区中该方法的字节码组建一个栈帧(Stack Frame)并将该栈帧压入Java栈中,方法执行完毕时,JVM会弹出该栈帧并释放掉。

Stack大小可动态扩展,尝试扩展时无法申请到足够的内存时抛出OutOfMemoryError;如果线程请求的栈深度大于虚拟机所允许的深度将抛出StackOverflowError

栈帧栈帧有三部分构成:局部变量区、操作数栈和帧数据区。在编译器编译Java代码时,就已经在字节码中为每个方法都设置好了局部变量区和操作数栈的数据和大小。并在JVM首次加载方法所属的Class文件时,就将这些数据放进了方法区。因此在线程调用方法时,只需要根据方法区中的局部变量区和操作数栈的大小来分配一个新的栈帧的内存大小,并堆入Java栈。

局部变量区:用来存放方法中的所有局部变量值,包括传递的参数。这些数据会被组织成以一个字长(32bit64bit)为单位的数组结构(以索引0开始)中。其中类型为int,float,reference(引用类型,记录对象在堆中地址)returnAddress(一种JVM内部使用的基本类型)的值占用1个字长,而byte,charshot会扩大成1个字长存储,longdouble则使用2个字长。

操作数栈:用来在执行指令的时候存储和使用中间结果数据。

帧数据区:常量池的解析,正常方法返回以及异常派发机制的信息数据都存储在其中。


 本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈作用相似,后者为虚拟机执行Java方法服务,而前者为虚拟机用到的Native方法服务

虚拟机规范对于本地方法栈中方法使用的语言,使用方式和数据结构没有强制规定,甚至有的虚拟机(比如HotSpot)直接把二者合二为一。


 Java堆(JavaHeap)

虚拟机管理的内存中最大的一块,同时也是被所有线程所共享的,它在虚拟机启动时创建,这货存在的意义就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。这里面的对象被自动管理,也就是俗称的GCGarbage Collector所管理。一般来说,堆的划分有年轻代(又分为一个伊甸区和两个存活区),年老代和持久代,其中持久代在位置上属于方法区。

Java堆的容量可动态扩展-Xms-Xmx,内存不需要物理上连续 ,可抛出OutOfMemoryError。

堆中对象存储的是该对象以及对象所有超类的实例数据(实例数据是指属于对象的非静态数据) 比如下面的类型:

class X{

    private int data;

    private static int stcdata=0;

    public X(int d){

         this.data=d;

    }

}

X x1=new X(100);

X x2=new X(200);

这样在堆中开辟了两个对象x1x2的内存空间。其中x1中的一个实例数据data=100,而x2data=200。但是这两个对象中都没有stcdata这样的数据,这个静态数据存储在的方法区中。

此外,堆中对象还必须有指向方法区中的类信息数据(见方法区)为什么需要这个信息呢?因为当程序在运行时需要对象转型,那么JVM必须检查当前对象所属类型及父类的信息。以判断转型是否是合法的,而这一点也是instanceof操作符实现的基础。

其中一个对象的引用可能在整个运行时数据区中的很多地方存在,比如Java栈,堆,方法区等。

堆中对象还应该关联一个对象的锁数据信息以及线程的等待集合。这些都是实现Java线程同步机制的基础(即实现线程的同步所需要的信息在堆中,因为栈是独属于一个线程的,而堆是可以被所有线程共享的)。但实际上很多具体实现中并不在对象自身内部保存一个指向锁数据的指针。而只有当第一次需要加锁的时候才分配对应锁数据。另外,每个对象都会从Object中继承三个Object方法(waitnotifynotifyAll),当某个线程在一个对象上调用了等待方法时。JVM就会阻塞这个线程,并把这个线程放在该对象的等待集合中。直到另外一个线程在该对象上调用了notify/notifyAllJVM才会在等待集合中唤醒一个或全部的等待线程。

  因此,也就出现了,用改变公共数据区对象的锁的方法是通过公共数据区对象本身来调用,和线程对象是没有关系的。

 

事实上,每一个同步锁lock下面都挂了几个线程队列,包括就绪(Ready)队列,等待(Waiting)队列等。当线程A因为得不到同步锁lock,从而进入的是lock.ReadyQueue(就绪队列),一旦同步锁不被占用,JVM将自动运行就绪队列中的线程而不需要任何notify()的操作。但是当线程Await()了,那么将进入lock.WaitingQuene(等待队列),同时如果占据的同步锁也会放弃。而此时如果同步锁不唤醒等待队列中的进程(lock.notify()),这些进程将永远不会得到运行的机会。就绪队列和等待队列有很大的不同,这一点要牢记。Obj.wait()obj.notify()方法必须放在Objsynchronized块中使用。

 

方法区(Method Area) 

跟堆一样是被各个线程共享的内存区域,用于存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

方法区在虚拟机启动的时候创建,大小可扩展,像堆一样在内存空间可以不连续,可抛出OutofMemorryError。

JVM使用类装载器定位Class文件,并将其输入到内存中时。会提取Class文件的类型信息,并将这些信息存储到方法区中。同时放入方法取中的还有该类型中的类静态变量。下面我们具体看看需要存储哪些信息?
  • 类全名,类接口名,类修饰符(pubic,final, abstract等)。
  • 类常量池常量池是类所用常量的一个有序集合,包括直接常量(基本类型,String)和对其他类型、字段、方法的符号引用。之所以是符号引用而不是像c语言那样,编译时直接指定其他类型,是因为java是动态绑定的,只有在运行时根据某些规则才能确定具体依赖的类型实例,这正是java实现多态的基础。  For each type it loads, a Java Virtual Machine must store a constant pool.A constant pool is an ordered set of constants used by the type, including literals (string, integer, and floating point constants) and symbolic references to types, fields, and methods. Entries in the constant pool are referenced by index, much like the elements of an array. Because it holds symbolic references to all types, fields, and methods used by a type, the constant pool plays a central role in the dynamic linking of Java programs. 为啥常量池中含有对方法,字段及其他类型的符号引用呢?因为class文件并不包含其内部组件最终内存布局的信息,因此类,字段和方法并不能被class文件中的字节码直接引用。Java虚拟机从常量池获得符号引用,然后在运行时解析引用项的实际地址。
  • 类中字段信息:字段名,类型,即修饰符(public, private, protected, static, final, volatile,transient的某个子集)。还有,这些字段在类或者接口中的声明顺序。
  • 方法信息:方法名,返回类型,修饰符,参数类型,异常表,方法的字节码,操作数栈及局部变量的大小
  • 类静态变量。
  • 如果该类是被自定义ClassLoader装载的,还要保存对该ClassLoader类的引用。
  • 指向java.lang.Class类的引用。对于每一个被装载的类型,虚拟机都会相应的为它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型信息关联起来。这就可以在程序运行时查看某个加载进内存的类的当前状态信息。也就是反射机制实现的根本。
  • 方法表。为了能快速定位到类型中的某个方法。JVM对每个装载的类型都会建立一个方法表,用于存储该类型对象可以调用的方法的直接引用,这些方法就包括从超类中继承来的。而这张表与Java动态绑定机制(参见《java动态绑定机制实现多态:http://hxraid.iteye.com/blog/428891》)的实现是密切相关的。

方法区是多线程共享的。也就是当虚拟机实例开始运行程序时,边运行边加载进Class文件。不同的Class文件都会提取出不同类型信息存放在方法区中。同样,方法区中不再需要运行的类型信息会被垃圾回收线程丢弃掉。
 

 这里有一个小例子,来说明堆,栈和方法区之间的关系的

public class Test2 {

    public static void main(String[] args) {

        publicTest2 t2 = new Test2();

        //JVMTest2类信息加载到方法区,new Test2()实例保存在堆区,Test2引用保存在栈区,在栈中执行时才创建对象

    }

}

运行时常量池(Runtime Constant Pool)

它是方法区的一部分Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(ConstantPool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

 
 
  1. String s0=”kvill”;  
  2. String s1=”kvill”;  
  3. String s2=”kv” + “ill”;  
  4. System.out.println( s0==s1 );  
  5. System.out.println( s0==s2 );  

结果为:

 
 
  1. true   
  2. true  

首先,我们要知道Java会确保一个字符串常量只有一个拷贝。

因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”kvill”的一个引用。

所以我们得出s0==s1==s2;

用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

看例2:

 
 
  1. String s0=”kvill”;  
  2. String s1=new String(”kvill”);  
  3. String s2=”kv” + new String(“ill”);  
  4. System.out.println( s0==s1 );  
  5. System.out.println( s0==s2 );  
  6. System.out.println( s1==s2 );  

结果为:

 
 
  1. false  
  2. false  
  3. false  

例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。

4. String.intern():

再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了

例3:

 
 
  1. String s0= “kvill”;  
  2. String s1=new String(”kvill”);  
  3. String s2=new String(“kvill”);  
  4. System.out.println( s0==s1 );  
  5. System.out.println( “**********” );  
  6. s1.intern();  
  7. s2=s2.intern(); //把常量池中“kvill”的引用赋给s2  
  8. System.out.println( s0==s1);  
  9. System.out.println( s0==s1.intern() );  
  10. System.out.println( s0==s2 );  

结果为:

 
 
  1. false  
  2. **********  
  3. false //虽然执行了s1.intern(),但它的返回值没有赋给s1  
  4. true //说明s1.intern()返回的是常量池中”kvill”的引用  
  5. true  

最后我再破除一个错误的理解:

有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

看例4:

 
 
  1. String s1=new String("kvill");  
  2. String s2=s1.intern();  
  3. System.out.println( s1==s1.intern() );  
  4. System.out.println( s1+" "+s2 );  
  5. System.out.println( s2==s1.intern() );  

结果:

 
 
  1. false 
  2. kvill kvill  
  3. true  

在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。

s1==s1.intern()为false说明原来的“kvill”仍然存在;

s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。

5. 关于equals()和==:

这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。

6. 关于String是不可变的

这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”;
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。


Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。为了实现实例间的互相调用,方法区和heap必须是可共享的。 既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常


关于String.intern()方法:public Stringintern() 返回字符串对象的规范化表示形式。

一个初始时为空的字符串池,它由类 String,而不是对象,私有地维护。

当调用 intern方法时,如果池已经包含一个等于此 String对象的字符串(该对象由equals(Object)方法确定),则返回池中的字符串。否则,将此 String对象添加到池中,并且返回此 String对象的引用。它遵循对于任何两个字符串 s t,当且仅当 s.equals(t) true时,s.intern() == t.intern()才为 true所有字面值字符串和字符串赋值常量表达式都是内部的。


可以看下面一个范例:

1String str1 ="a";
 
2String str2 ="b";
 
3String str3 ="ab";
 
4String str4 =str1+str2;
 
5String str5 =newString("ab");
 
6
 
7System.out.println(str5.equals(str3));
 
8System.out.println(str5 ==str3);
 
9System.out.println(str5.intern() ==str3);
10System.out.println(str5.intern() ==str4);

得到的结果:

true

false

true

false

 

为什么会得到这样的一个结果呢?我们一步一步的分析。首先str1,str2和str3都在常量池中了。

第一、str5.equals(str3)这个结果为true,不用太多的解释,因为字符串的值的内容相同

第二、str5 == str3对比的是引用的地址是否相同,由于str5采用new String方式定义的,所以地址引用一定不相等。所以结果为false

第三、当str5调用intern的时候,会检查字符串池中是否含有该字符串。由于str3已经进入字符串池中,所以会得到相同的引用。

第四,当str4 = str1 + str2后,str4的值也为ab,但是为什么这个结果会是false呢?先看下面代码:

1String a =newString("ab");
 
2String b =newString("ab");
 
3String c ="ab";
 
4String d ="a"+"b";
 
5String e ="b";
 
6String f ="a"+e;
 
7
 
8System.out.println(b.intern() ==a);
 
9System.out.println(b.intern() ==c);
10System.out.println(b.intern() ==d);
11System.out.println(b.intern() ==f);
12System.out.println(b.intern() ==a.intern());

运行结果:

false

true

true

false

true

由运行结果可以看出来,b.intern() != ab.intern() = c可知(1)采用new创建的字符串对象不进入字符串池并且通过b.intern() = db.intern() != f可知,(2)字符串相加的时候,都是静态字符串的结果会添加到字符串池,(3)如果其中含有变量(如f中的e)则不会进入字符串池中,所以上上题中为啥str5.intern()!=str4了。但是字符串一旦进入字符串池中,就会先查找池中有无此对象。如果有此对象,则让对象引用指向此对象。如果无此对象,则先创建此对象,再让对象引用指向此对象。

    当研究到这个地方的时候,突然想起来经常遇到的一个比较经典的Java问题,就是对比equal==的区别,当时记得老师只是说==判断的是地址,但是并没说清楚什么时候会有地址相等的情况。现在看来,在定义变量的时候赋值,如果赋值的是静态的字符串,就会执行进入字符串池的操作,如果池中含有该字符串,则返回引用。

执行下面的代码:

1String a ="abc";
 
2String b ="abc";
 
3String c ="a"+"b"+"c";
 
4String d ="a"+"bc";
 
5String e ="ab"+"c";
 
6
 
7System.out.println(a ==b);
 
8System.out.println(a ==c);
 
9System.out.println(a ==d);
10System.out.println(a ==e);
11System.out.println(c ==d);
12System.out.println(c ==e);

运行的结果:

true

true

true

true

true

true

运行的结果刚好验证了我刚才的猜想。

 

 直接内存(Direct Memory)

直接内存(Direct Memory并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

JDK1.4加的NIO中,ByteBuffer有个方法是allocateDirect(intcapacity),这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在JavaNative中来回复制数据

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAMSWAP区或者分页文件)的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

 

 

http://blog.csdn.net/smcwwh/article/details/6729622

 

执行引擎

运行Java的每一个线程都是一个独立的虚拟机执行引擎的实例。从线程生命周期的开始到结束,他要么在执行字节码,要么在执行本地方法。一个线程可能通过解释或者使用芯片级指令直接执行字节码,或者间接通过JIT执行编译过的本地代码。

 

指令集:实际上,Class文件中方法的字节码流就是有JVM的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。

 

Java虚拟机指令集关注的中心是操作数栈和局部变量集合。我们可以看看下面一组指令在执行引擎中执行的过程:

Jvm助记指令代码

1        iload_0    // 把存储在局部变量区中索引为0的整数压入操作数栈。 

2        iload_1    // 把存储在局部变量区中索引为1的整数压入操作数栈。 

3        iadd         // 从操作数栈中弹出两个整数相加,在将结果压入操作数栈。 

4        istore_2   // 从操作数栈中弹出结果 

很显然,上面的指令反复用到了Java栈中的某一个方法栈帧。实际上执行引擎运行Java字节码指令很多时候都是在不停的操作Java栈,也有的时候需要在堆中开辟对象以及运行系统的本地指令等。但是Java栈的操作要比堆中的操作要快的多,因此反复开辟对象是非常耗时的。这也是为什么Java程序优化的时候,尽量减少new对象。

 

 

下面将会是很有趣的过程,我们用一段代码来生动的展现JVM是如何运行这段程序的。

 

通过编译器将下面的代码编译成edu/hr/jvm/Test.class edu/hr/jvm/bean/Act.class。然后开始启动JVM

[java]view plaincopy

 

5        //源代码Test.java   

6        package edu.hr.jvm;   

7            

8        import edu.hr.jvm.bean;   

9        publicclass Test{   

10             public staticvoidmain(String[] args){   

11                     Act act=newAct();   

12                     act.doMathForever();   

13             }   

14      }   

15          

16      //源代码Act.java   

17      package edu.hr.jvm.bean;   

18          

19      publicclass Act{   

20             publicvoiddoMathForever(){   

21                    int i=0;   

22                    for(;;){   

23                           i+=1;   

24                           i*=2;    

25                    }   

26             }   

27      }   

(1) 首先OS会创建一个JVM实例(进行必要的初始化工作,比如初始启动类装载器,初始运行时内存数据区等)

(2) 然后通过ClassLoader加载Test.class,并提取该类的字节码存放在方法区其中在常量池中有一个符号引用"Act" (注意:这个引用目前还没有真正的类信息的内存地址)。  【并非将所有的类都加载进方法区,在用的时候才进行加载哦,在未被加载尽量的类即被引用类,被加载进来之前,在常量池中会有它的一个符号引用,即它引用的类全类名,未指向真正的地址。具体可以参看本文中关于方法区-常量池的介绍

(3) 接着JVM开始从Test类的main字节码处开始解释执行。在运行之前,会在Java栈中组建一个main方法的栈帧

(4) 现在可以开始执行main方法的第一条指令——JVM需要为常量池的第一项的类(符号引用Act)分配内存空间,但是Act类此时还没有加载进JVM。

 


(5) JVM加载进Act.class,并提取Act类信息放入方法区中,然后以一个直接指向方法区Act类信息的直接引用替换开始在常量池中的符号引用"Act",这个过程就是常量池解析。

(6) 此时JVM可以根据方法区中的Act类信息,在堆中开辟一个Act类对象act

(7) 接着开始执行main方法中的第二条指令调用doMathForever。这个可以通过堆中act对象所指的方法表中查找,然后定位到方法区中的Act类信息中的doMathForever方法字节码。在运行之前,仍然要组建一个doMathForever栈帧压入Java栈。(注意:JVM会根据方法区中doMathForever的字节码来创建栈帧的局部变量区和操作数栈的大小)

(8) 接下来JVM开始解释运行Act.doMathForever字节码的内容了。下面我们详细的描述一下这个JVM的运行过程:

我们首先看一下doMathForever方法的字节码在方法区中的指令,其中bytecode是指令的二进制编码mnemonic指令助记符pc程序计数器 (指向当前运行指令的下一条)offset为指令存放在方法区中的地址偏移

然后在上面的图Java栈中已经显示出了doMathForever方法的栈帧,其中比较重要的两个部分是局部变量区操作数栈。而此时在运行指令之前,局部变量区中只有一个整型i的存储位置(1个字长)。而操作数栈中还没有被创建了2个字长的大小(存储大小是帧栈创建的时候由方法区中的数据确定的)

 

                             局部变量区

             index        hex value        value

  (变量i)     0                                               

 

 optop: 0                操作数栈         

              offset        hex value        value

 optop->    0

                  1

 

 

 下面运行每一条指令后,看一下局部变量区和操作数栈的变化:

    指令[iconst_0]  int类型变量的数据0压入操作数栈。

                             局部变量区                                                                  操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0                                                                                 0           00000000          0

                                                                                  optop->   1

    指令[istore_0]   弹出操作数栈顶的数据0,将结果存储在局部变量区中index=0的空间中。

                             局部变量区                                                                  操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0           00000000          0                          optop->  0

                                                                                                   1

    指令[iinc 0 1] 把常量值1加到局部变量区中index=0的空间上。

                             局部变量区                                                                 操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0           00000001          1                         optop->  0

                                                                                                    1

    指令[iload_0] 把局部变量区index=0中的数据堆入操作数栈。

                             局部变量区                                                                 操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0           00000001          1                                         0         00000001          1

                                                                                    optop->  1

    指令[iconst_2]int类型变量的数据2压入操作数栈。

                              局部变量区                                                                 操作数栈    

             index        hex value        value                                 offset        hex value        value

   (变量i)    0           00000001          1                                         0         00000001          1

                                                                                                   1         00000002           2

                                                                                    optop->

    指令[imul] 弹出操作数栈中的两个数据12,相乘之后的结果2堆入操作数栈

                              局部变量区                                                                  操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0           00000001          1                          optop->  0         00000002           2

                                                                                                   1     

     指令[istore_0] 弹出操作数栈顶的数据2,将结果存储在局部变量区中index=0的空间中。

                              局部变量区                                                                  操作数栈    

             index        hex value        value                                offset        hex value        value

   (变量i)    0           00000002          2                          optop->  0         

                                                                                                   1    

     指令[goto 2]  跳转到指令iinc 0 1处循环执行下去.....

 

当然,这个例子不停的执行下去只会出现算术溢出,也就是一个字长(2bytes)的整型变量i无法表示不停计算的结果了。但是JVM不会抛出任何异常,

 

 

Java Classloader

 

jvm classLoader architecture :

a, Bootstrap ClassLoader/启动类加载器
主要负责jdk_home/lib目录下的核心 api 或-Xbootclasspath 选项指定的jar包装入工作.

b, Extension ClassLoader/扩展类加载器
主要负责jdk_home/lib/ext目录下的jar包或-Djava.ext.dirs 指定目录下的jar包装入工作

c, System ClassLoader/系统类加载器
主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.

b, User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件,体现java动态实时类装入特性.

类加载器的特性:

1, 每个ClassLoader都维护了一份自己的名称空间,同一个名称空间里不能出现两个同名的类。
2, 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 ” 双亲委派的加载链 ” 结构,如下classLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

Java代码 

1        //检查类是否已被装载过 

2        Class c = findLoadedClass(name); 

3        if (c ==null ) { 

4             //指定类未被装载过 

5             try

6                 if (parent !=null ) { 

7                     //如果父类加载器不为空,则委派给父类加载 

8                     c = parent.loadClass(name,false ); 

9                 } else

10                 //如果父类加载器为空,则委派给启动类加载加载 

11                 c =findBootstrapClass0(name); 

12             } 

13         } catch (ClassNotFoundException e) { 

14             //启动类加载器或父类加载器抛出异常后,当前类加载器将其 

15             //捕获,并通过findClass方法,由自身加载 

16             c =findClass(name); 

17         } 

18    

 

用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的.
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰.

即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.

 

Java代码 

19    publicstatic Class forName(StringclassName) 

20         throws ClassNotFoundException { 

21         return forName0(className,true ,ClassLoader.getCallerClassLoader()); 

22    

23       

24    /** Called after security checks have been made. */ 

25    privatestaticnative Class forName0(String name,boolean initialize, 

26    ClassLoader loader) 

27         throws ClassNotFoundException; 

 

上图中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器

 

线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader).

 

Java代码 

28    // Now create the class loader to use to launch theapplication 

29    try

30        loader =AppClassLoader.getAppClassLoader(extcl); 

31    } catch (IOException e) { 

32        thrownew InternalError( 

33    "Could not create application class loader" ); 

34    

35       

36    // Also set the context class loader for the primordialthread. 

37    Thread.currentThread().setContextClassLoader(loader); 

 

以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现,而不依赖于双亲委派.

大部分java app服务器(jboss,tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架,也使用了线程上下文类加载器, 比如seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.

使java类加载体系显得更灵活.

 

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择.

 

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意,保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException).

 

自定义的类加载器实现
defineClass(String name, byte[] b, int off, int len,ProtectionDomainprotectionDomain)
是java.lang.Classloader提供给开发人员, 用来自定义加载class的接口.

使用该接口, 可以动态的加载class文件.

 

例如,
在jdk中,URLClassLoader是配合findClass方法来使用defineClass,可以从网络或硬盘上加载class.

而使用类加载接口, 并加上自己的实现逻辑, 还可以定制出更多的高级特性.

 

比如,

一个简单的hot swap 类加载器实现:

 

 

Java代码 

38    import java.io.File; 

39    importjava.io.FileInputStream; 

40    importjava.lang.reflect.Method; 

41    import java.net.URL; 

42    importjava.net.URLClassLoader; 

43       

44    /**

45    *可以重新载入同名类的类加载器实现

46    *

47      

48    *放弃了双亲委派的加载链模式.

49    *需要外部维护重载后的类的成员变量状态.

50    *

51    * @author ken.wu

52    * @mail ken.wug@gmail.com

53    * 2007-9-28下午01:37:43

54    */ 

55    publicclass HotSwapClassLoaderextends URLClassLoader { 

56       

57        public HotSwapClassLoader(URL[] urls) { 

58            super (urls); 

59        } 

60       

61        public HotSwapClassLoader(URL[] urls, ClassLoader parent) { 

62            super (urls, parent); 

63        } 

64       

65        public Class load(String name) 

66              throws ClassNotFoundException { 

67            return load(name,false ); 

68        } 

69       

70        public Class load(String name,boolean resolve) 

71              throws ClassNotFoundException { 

72            if (null !=super .findLoadedClass(name)) 

73                return reload(name, resolve); 

74       

75            Class clazz =super .findClass(name); 

76       

77            if (resolve) 

78                super .resolveClass(clazz); 

79       

80            return clazz; 

81        } 

82       

83        public Class reload(String name,boolean resolve) 

84              throws ClassNotFoundException { 

85            returnnew HotSwapClassLoader(super .getURLs(), super .getParent()).load( 

86                name,resolve); 

87        } 

88    

89       

90    publicclass A { 

91        private B b; 

92       

93        publicvoid setB(B b) { 

94             this .b = b; 

95        }  

96       

97        public B getB() { 

98             return b; 

99        } 

100 

101     

102  publicclass B {} 

 

 

这个类的作用是可以重新载入同名的类, 但是, 为了实现hotswap, 老的对象状态需要通过其他方式拷贝到重载过的类生成的全新实例中来。(A类中的b实例)

而新实例所依赖的B类如果与老对象不是同一个类加载器加载的, 将会抛出类型转换异常(ClassCastException).为了解决这种问题, HotSwapClassLoader自定义了load方法.即当前类是由自身classLoader加载的, 而内部依赖的类

 

还是老对象的classLoader加载的.

 

Java代码 

103  publicclass TestHotSwap { 

104  publicstaticvoid main(String args[]) { 

105      A a = new A(); 

106      B b = new B(); 

107      a.setB(b); 

108     

109      System.out.printf("A classLoader is %sn" ,a.getClass().getClassLoader()); 

110      System.out.printf("B classLoader is %sn" ,b.getClass().getClassLoader()); 

111      System.out.printf("A.b classLoader is %sn",  a.getB().getClass().getClassLoader()); 

112     

113      HotSwapClassLoader c1 =new HotSwapClassLoader(new URL[]{new URL("file:\e:\test\")} ,a.getClass().getClassLoader()); 

114      Class clazz = c1.load(" test.hotswap.A "); 

115      Object aInstance =clazz.newInstance(); 

116     

117      Method method1 = clazz.getMethod(" setB ", B.class); 

118      method1.invoke(aInstance, b); 

119     

120      Method method2 = clazz.getMethod(" getB ",null); 

121      Object bInstance =method2.invoke(aInstance,null); 

122     

123      System.out.printf(" reloaded A.bclassLoader is %s n", bInstance.getClass().getClassLoader()); 

124 

125 

 

 

输出

A classLoader issun.misc.Launcher$AppClassLoader@19821f
B classLoader is sun.misc.Launcher$AppClassLoader@19821f
A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f
reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f

 

 

在大部分情况下,jvm的这个默认类加载体系已经能满足大部分情况的使用了。那为什么还需要ContextClassLoader呢?这其实是因为加载Class的默认规则在某些情况下不能满足要求。比如jdk中的jdbc API 和具体数据库厂商的实现类SPI的类加载问题。jdbc API的类是由BootStrap加载的,那么如果在jdbc API需要用到spi的实现类时,根据默认规则2,则实现类也会由BootStrap加载,但是spi实现类却没法由BootStrap加载,只能由Ext或者App加载,如何解决这个问题?牛人们就想出了ContextClassLoader的方法。

具体的实现过程如下:在类Thread定义一个属性classLoader,用来供用户保存一个classLoader(默认是App),并公开setter和getter方法,使得此线程的任何语句都可以得到此classLoader,然后用它来加载类,以此来打破默认规则2。说白了,ContextClassLoader就是Thread的一个属性而已,没什么复杂的。只不过这个属性和底层的加载体系联系紧密,使得很多人以为有什么高深的原理在里面。明白了这一点,ContextClassLoader也就没什么神秘的了。

那么我们看看ContextClassLoader是如何解决上面的问题的呢?以jdbc为例,当DriverManager需要加载SPI中的实现类是,可以获取ContextClassLoader(默认是App),然后用此classLoader来加载spi中的类。很简单的过程。当然不使用ContextClassLoader,自己找个地方把classLoader保存起来,在其他地方能得到此classLoader就可以。Tomcat就是这么做的。比如StandardContext是由Common加载的,而StandardContext要用到项目下的类时怎么办,显然不能用Common来加载,而只能用WebAppClassLoader来加载,怎么办?当然可以采用ContextClassLoader的方式来解决,但是tomcat不是这样解决的,而是为每个App创建一个Loader,里面保存着此WebApp的ClassLoader。需要加载WebApp下的类时,就取出ClassLoader来使用,原理和ContextClassLoader是一样的。至于tomcat为什么要这么做呢?因为tomcat中有关类加载的问题,是由一个main线程来处理的,而并没有为每个WebApp单独创建一个线程,故没办法用ContextClassLoader的方式来解决,而是用自己的属性来解决。

Tomact 6.0 Class Loader How-to:

http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

既然规则2已经被打破了,那么规则1:双亲委托机制能被打破吗?规则1是因为安全问题而设置的,如果我自己能控制类加载的安全问题,是否可以违反规则1呢?答案是肯定的。同样的Tomcat也打破了此规则。Tomcat中WebAppClassLoader加载项目的类时,可以决定是否先在项目本地的路径中查找class文件。而不是自动使用父加载器Common在tomcat下的common目录查找。这是怎么实现的呢?

要回答这个问题,就要先明白双亲委托机制是如何实现的。只有明白了如何实现,才有办法改变它。双亲委托机制是在类ClassLoader的loadClass里面实现的。那么继承ClassLoader的WebAppClassLoader,只要覆盖loadClass方法,实现自己的加载策略,即可避开双亲委托机制。

 

Thread.currentThread().getContextClassLoader()
我总是想不出在什么情况下会用这种方式获得一个ClassLoader,因为好像默认情况下,它返回的是和加载应用的ClassLoader是同一个,比如说在一个类Test中写
ClassLoader cl =Thread.currentThread().getContextClassLoader();
为何不直接用Test.class.getClassLoader()

获得当前上下文的类加载器是啥意思?有啥好处?

 

Java code?

1

2

3

4

5

6

7

8

9

10

11

publicclassTest {

 

    publicstaticvoidmain(String[] args) {

        

        // 此时三个ClassLoader是同一个对象

        System.out.println(Thread.currentThread().getContextClassLoader());//当前线程的类加载器

        System.out.println(Test.class.getClassLoader());//当前类的类加载器

        System.out.println(ClassLoader.getSystemClassLoader());//系统初始的类加载器

        

    }

}

 


如果楼主了解过openfire应该对ClassLoader有比较深的理解。
打个简单的比方,你一个WEB程序,发布到Tomcat里面运行。
首先是执行Tomcatorg.apache.catalina.startup.Bootstrap类,这时候的类加载器是ClassLoader.getSystemClassLoader()
而我们后面的WEB程序,里面的jarresources都是由Tomcat内部来加载的,所以你在代码中动态加载jar、资源文件的时候,首先应该是使用

Thread.currentThread().getContextClassLoader()。如果你使用Test.class.getClassLoader(),可能会导致和当前线程所运行的类加载器不一致(因为Java天生的多线程)。
Test.class.getClassLoader()一般用在getResource,因为你想要获取某个资源文件的时候,这个资源文件的位置是相对固定的。

 

http://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream

 

What's the difference between:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

and

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

and

InputStream is = this.getClass().getResourceAsStream(fileName)

When are each one moreappropriate to use than the others?

The file that I want to read is in the classpath as my classthat reads the file. My class and the file are in the same jar and packaged upin an EAR file, and deployed in WebSphere 6.1.

ANSWER: There are subtle differences asto how the fileName you arepassing is interpreted. Basically, you have 2 different methods: ClassLoader.getResourceAsStream() andClass.getResourceAsStream(). Thesetwo methods will locate the resource differently.

In Class.getResourceAsStream(path), the path is interpreted as a path local to the package ofthe class you are calling it from. For example calling, String.getResourceAsStream("myfile.txt")will lookfor a file in your classpath at the following location: "java/lang/myfile.txt". If yourpath starts with a /, then itwill be considered an absolute path, and will start searching from the root ofthe classpath. So calling String.getResourceAsStream("/myfile.txt") will lookat the following location in your in your class path ./myfile.txt.

ClassLoader.getResourceAsStream(path) will consider all paths to beabsolute paths. So calling String.getClassLoader().getResourceAsString("myfile.txt") and String.getClassLoader().getResourceAsString("/myfile.txt") will bothlook for a file in your classpath at the following location: ./myfile.txt.

Everytime I mention a locationin this post, it could be a location in your filesystem itself, or inside thecorresponding jar file, depending on the Class and/or ClassLoader you areloading the resource from.

In your case, you are loadingthe class from an Application Server, so your should useThread.currentThread().getContextClassLoader().getResourceAsStream(fileName)instead ofthis.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream() will alsowork.

Read this article for moredetailed information about that particular problem.

http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html

What is the best strategy forloading property and configuration files in Java?

When you think about how to load an external resourcein Java, several options immediately come to mind: files, classpath resources,and URLs. Although all of them eventually get the job done, experience showsthat classpath resourcesand URLs are by far themost flexible and user-friendly options.

In general, a configuration file can have an arbitrarilycomplex structure (e.g., an XML schema definition file). But for simplicity, Iassume below that we're dealing with a flat list of name-value pairs (thefamiliar.properties format). There's no reason,however, why you can't apply the ideas shown below in other situations, as longas the resource in question is constructed from an InputStream.

Evil java.io.File

Using good old files (via FileInputStream, FileReader, andRandomAccessFile) is simple enough andcertainly the obvious route to consider for anyone without a Java background.But it is the worst option in terms of ease of Java application deployment.Using absolute filenames in your code is not the way to write portable and diskposition-independent code. Using relative filenames seems like a betteralternative, but remember that they are resolvedrelative to the JVM's current directory. Thisdirectory setting depends on the details of the JVM's launch process, which canbe obfuscated by startup shell scripts, etc. Determining the setting places anunfair amount of configuration burden on the eventual user (and in some cases,an unjustified amount of trust in the user's abilities). And in other contexts(such an Enterprise JavaBeans (EJB)/Web application server), neither you northe user has much control over the JVM's current directory in the first place.

An ideal Java module is something you add to the classpath,and it's ready to go. Think EJB jars, Web applications packaged in .war files, and other similarly convenient deploymentstrategies. java.io.File is theleast platform-independent area of Java. Unless you absolutely must use them,just say no to files.

Classpath resources

Having dispensed with the above diatribe, let's talk about abetter option: loading resources through classloaders. This is much betterbecauseclassloadersessentially act as a layer of abstraction between a resource name and itsactual location on disk (or elsewhere).

Let's say you need to load a classpath resource thatcorresponds to a some/pkg/resource.properties file. Iuseclasspath resource to meansomething that's packaged in one of the application jars or added to theclasspath before the application launches. You can add to the classpath via the-classpath JVMoption each time the application starts or by placing the file in the <jre home>\classes directory once and for all.The key point is thatdeploying aclasspath resource is similar to deploying a compiled Java class, andtherein lies the convenience.

You can get at some/pkg/resource.propertiesprogrammatically from your Java code in several ways. First, try:

  ClassLoader.getResourceAsStream("some/pkg/resource.properties");

  Class.getResourceAsStream("/some/pkg/resource.properties");

  ResourceBundle.getBundle ("some.pkg.resource");

 



Additionally, if the code is in a class within a some.pkg Java package, then the following works as well:

  Class.getResourceAsStream("resource.properties");

 



Note the subtle differences in parameter formatting for thesemethods. All getResourceAsStream() methodsuse slashes to separate package name segments, and the resource name includesthe file extension. Compare that withresource bundles where the resource name looks more likea Java identifier, with dots separating package name segments (the .properties extension is implied here). Ofcourse, that is because a resource bundle does not have to be backed by a .properties file: it can be a class, for aexample.

To slightly complicate the picture, java.lang.Class'sgetResourceAsStream() instancemethod canperformpackage-relative resource searches (which can be handy as well, see"Got Resources?").To distinguish between relative and absolute resource names, Class.getResourceAsStream() usesleading slashes for absolute names. In general, there's no need to use this methodif you are not planning to use package-relative resource naming in code.

It is easy to get mixed up in these small behavioraldifferences forClassLoader.getResourceAsStream(), Class.getResourceAsStream(), andResourceBundle.getBundle(). Thefollowing table summarizes the salient points to help you remember:

Behavioral differences

Method

Parameter format

Lookup failure behavior

Usage example

ClassLoader.
getResourceAsStream()

"/"-separated names; no leading "/" (all names are absolute)

Silent (returns null)

this.getClass().getClassLoader()
.getResourceAsStream
("some/pkg/resource.properties")

Class.
getResourceAsStream()

"/"-separated names; leading "/" indicates absolute names; all other names are relative to the class's package

Silent (returns null)

this.getClass()
.getResourceAsStream
("resource.properties")

ResourceBundle.
getBundle()

"."-separated names; all names are absolute; .properties suffix is implied

Throws unchecked
java.util.MissingResourceException

ResourceBundle.getBundle
("some.pkg.resource")



From data streams to java.util.Properties

You might have noticed that some previously mentioned methodsare half measures only: they return InputStreams andnothing resembling a list of name-value pairs. Fortunately, loading data intosuch a list (which can be an instance ofjava.util.Properties) is easy enough. Because you will find yourselfdoing this over and over again, it makes sense to create a couple of helpermethods for this purpose.

The small behavioral difference among Java's built-in methodsfor classpath resource loading can also be a nuisance, especially if someresource names were hardcoded but you now want to switch to another loadmethod. It makes sense to abstract away little things like whether slashes ordots are used as name separators, etc. Without further ado, here's myPropertyLoader API that you might find useful(available with this article'sdownload):

public abstract classPropertyLoader

{

    /**

     * Looks up a resource named 'name' in theclasspath. The resource must map

     * to a file with .properties extention.The name is assumed to be absolute

     * and can use either "/" or"." for package segment separation with an

     * optional leading "/" andoptional ".properties" suffix. Thus, the

     * following names refer to the sameresource:

     * <pre>

     * some.pkg.Resource

     * some.pkg.Resource.properties

     * some/pkg/Resource

     * some/pkg/Resource.properties

     * /some/pkg/Resource

     * /some/pkg/Resource.properties

     * </pre>

     *

     * @param name classpath resource name [maynot be null]

     * @param loader classloader through whichto load the resource [null

     * is equivalent to the application loader]

     *

     * @return resource converted tojava.util.Properties [may be null if the

     * resource was not found andTHROW_ON_LOAD_FAILURE is false]

     * @throws IllegalArgumentException if theresource was not found and

     * THROW_ON_LOAD_FAILURE is true

     */

    public static Properties loadProperties(String name, ClassLoader loader)

    {

        if (name == null)

            throw new IllegalArgumentException("null input: name");

       

        if (name.startsWith ("/"))

            name = name.substring (1);

           

        if (name.endsWith (SUFFIX))

            name = name.substring (0,name.length () - SUFFIX.length ());

       

        Properties result = null;

       

        InputStream in = null;

        try

        {

            if (loader == null) loader =ClassLoader.getSystemClassLoader ();

           

            if (LOAD_AS_RESOURCE_BUNDLE)

            {   

                name = name.replace ('/', '.');

                // Throws MissingResourceExceptionon lookup failures:

                final ResourceBundle rb =ResourceBundle.getBundle (name,

                    Locale.getDefault (),loader);

               

                result = new Properties ();

                for (Enumeration keys =rb.getKeys (); keys.hasMoreElements ();)

                {

                    final String key = (String)keys.nextElement ();

                    final String value =rb.getString (key);

                   

                    result.put (key, value);

                }

            }

            else

            {

                name = name.replace ('.', '/');

               

                if (! name.endsWith (SUFFIX))

                    name = name.concat(SUFFIX);

                               

                // Returns null on lookupfailures:

                in = loader.getResourceAsStream(name);

                if (in != null)

                {

                    result = new Properties ();

                    result.load (in); // Canthrow IOException

                }

            }

        }

        catch (Exception e)

        {

            result = null;

        }

        finally

        {

            if (in != null) try { in.close ();} catch (Throwable ignore) {}

        }

       

        if (THROW_ON_LOAD_FAILURE &&(result == null))

        {

            throw new IllegalArgumentException("could not load [" + name + "]"+

                " as " +(LOAD_AS_RESOURCE_BUNDLE

                ? "a resource bundle"

                : "a classloaderresource"));

        }

       

        return result;

    }

   

    /**

     * A convenience overload of {@link#loadProperties(String, ClassLoader)}

     * that uses the current thread's contextclassloader.

     */

    public static Properties loadProperties(final String name)

    {

        return loadProperties (name,

            Thread.currentThread().getContextClassLoader ());

    }

       

    private static final booleanTHROW_ON_LOAD_FAILURE = true;

    private static final boolean LOAD_AS_RESOURCE_BUNDLE= false;

    private static final String SUFFIX =".properties";

} // End of class

 



The Javadoc comment for the loadProperties() method shows that the method's inputrequirements are quite relaxed: it accepts a resource name formatted accordingto any of the native method's schemes (except for package-relative namespossible with Class.getResourceAsStream()) andnormalizes it internally to do the right thing.

The shorter loadProperties()convenience method decides which classloader to use for loading the resource.The solution shown is reasonable but not perfect; you might consider usingtechniques described in "Find a Way Out of theClassLoader Maze" instead.

Note that two conditional compilation constants controlloadProperties() behavior, and you can tunethem to suit your tastes:

       THROW_ON_LOAD_FAILURE selects whether loadProperties() throws an exception or merely returns null when it can't find the resource

       LOAD_AS_RESOURCE_BUNDLE selects whether the resource is searched as a resource bundle oras a generic classpath resource



Setting LOAD_AS_RESOURCE_BUNDLE to true isn't advantageous unless you want to benefitfrom localization support built intojava.util.ResourceBundle. Also,Java internally caches resource bundles, so you can avoid repeated disk filereads for the same resource name.

More things to come

I intentionally omitted an interesting classpath resourceloading method, ClassLoader.getResources(). Despiteits infrequent use,ClassLoader.getResources() allowsfor some very intriguing options in designing highly customizable and easilyconfigurable applications.

I didn't discuss ClassLoader.getResources() in thisarticle because it's worthy of a dedicated article. As it happens, this methodgoes hand in hand with the remaining way to acquire resources: java.net.URLs. You can use these as evenmore general-purpose resource descriptors than classpath resource name strings.Look for more details in the nextJavaQ&A installment.

About the author

Vladimir Roubtsov hasprogrammed in a variety of languages for more than 13 years, including Javasince 1995. Currently, he develops enterprise software as a senior engineer forTrilogy in Austin, Texas.Read more about Core Java inJavaWorld's Core Java section.

 

 

 

我们知道,GC主要处理的是对象的回收操作,那么什么时候会触发一个对象的回收的呢?

1、    对象没有引用

2、    作用域发生未捕获异常

3、    程序在作用域正常执行完毕

4、    程序执行了System.exit()

5、    程序发生意外终止(被杀进程等)

其实,我们最容易想到的就是当对象没有引用的时候会将这个对象标记为可回收对象,那么现在就有一个问题,是不是这个对象被赋值为null以后就一定被标记为可回收对象了呢?我们来看一个例子:

 

package com.yhj.jvm.gc.objEscape.finalizeEscape;

 

import com.yhj.jvm.gc.objEscape.pojo.FinalizedEscapeTestCase;

 

 

/**

 * @Described:逃逸分析测试

 * @author YHJ create at 2011-12-24 下午05:08:09

 * @FileNmae com.yhj.jvm.gc.finalizeEscape.FinalizedEscape.java

 */

public class FinalizedEscape {

    public static void main(String[] args) throwsInterruptedException {

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

       FinalizedEscapeTestCase.caseForEscape=null;

       System.gc();

       Thread.sleep(100);

        System.out.println(FinalizedEscapeTestCase.caseForEscape);

    }

}

package com.yhj.jvm.gc.objEscape.pojo;

/**

 * @Described:逃逸分析测试用例

 * @author YHJ create at 2011-12-24 下午05:07:05

 * @FileNmae com.yhj.jvm.gc.pojo.TestCaseForEscape.java

 */

public class FinalizedEscapeTestCase {

 

    public static FinalizedEscapeTestCase caseForEscape = null;

    @Override

    protected void finalize() throws Throwable {

       super.finalize();

       System.out.println("哈哈,我已逃逸!");

       caseForEscape = this;

    }

}

 

程序的运行结果回事什么样子的呢?

我们来看这段代码

 

1  System.out.println(FinalizedEscapeTestCase.caseForEscape);

2  FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase();

3  System.out.println(FinalizedEscapeTestCase.caseForEscape);

4  FinalizedEscapeTestCase.caseForEscape=null;

5  System.gc();

6  Thread.sleep(100);

7    System.out.println(FinalizedEscapeTestCase.caseForEscape);

 

1、    当程序执行第一行是,因为这个对象没有值,结果肯定是null

2、    程序第二行给该对象赋值为新开辟的一个对象

3、    第三行打印的时候,肯定是第二行对象的hash代码

4、    第四行将该对象重新置为null

5、    第五行触发GC

6、    为了保证GC能够顺利执行完毕,第六行等待100毫秒

7、    第七行打印对应的值,回事null么?一定会是null么?

我们来看一下对应的运行结果


 本例中打印了

GC的日志,让我们看的更清晰一点,我们很清晰的看出,最后一句打印的不是null,并且子啊之前,还出现了逃逸的字样。说明这个对象逃逸了,在垃圾回收之前逃逸了,我们再来看这个pojo的写法,就会发现,我们重写了方法finalize,而这个方法就相当于C++中的析构方法,在GC回收之前,会先调用一次这个方法,而这个方法又将this指针指向他自己,因此得以成功逃逸!可见,并不是这个对象被赋值为null之后就一定被标记为可回收,有可能会发生逃逸!


 

 

推荐阅读:http://www.javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html

JavaSE7 javadoc  http://docs.oracle.com/javase/7/docs/api/

Java语言和虚拟机规范 http://docs.oracle.com/javase/specs/index.html

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值