上一节我们简单解释了:当我们在程序中构造一个对象时,不仅要在内存中存储对象的有效数据信息,还要在内存中存储对象所属的类信息,这样在程序的运行过程中,JVM才可以正确的解析对象内存块中的数据。
那么问题来了,对象的类信息存储在哪里呢?在程序运行时是如何表示的呢?
首先回答第一个问题。在程序运行期间,所有的类信息都是存储在内存的方法区中。我擦!!!啥玩意是方法区啊?诶……要想知道什么是方法区,你要去看周志明老师的《深入理解Java虚拟机》的39页了。不过这里我们不讨论那么深,只需要知道:JVM按照不同的用途,将内存划分成多个区域。对象的类型信息就保存在方法区这个内存区域中。这个内存区域是所有线程共享的,也就是说,只要有一个线程把类型信息存储到内存中,整个程序的所有线程就都可以获取到类型信息了。
接着来回答第二个问题。在程序运行过程中,类信息是由Class对象表示的。在Java程序中,每个类都对应一个Class对象。下面我们就简单描述一下Class对象的构建过程:
首先,帅气的你写了一个java源文件Xxx.java,并熟练的javac编译源文件产生了Xxx.class文件。接着这个.class文件就静静的躺在硬盘里等待着施展自己才华机会的到来。只有你的程序用到Xxx类的时候,Xxx.class文件才会被读取到内存中,否则这个Xxx.class文件就一直躺在硬盘里,焦急的等待着。
终于,你的程序要构造一个Xxx类的对象了,这时候JVM发现,Xxx类还没加载到内存呢,用毛线来构造Xxx对象啊!!!于是JVM就根据Xxx这个类的名字,在ClassPath的指引下,开始寻找Xxx.class文件。终于在硬盘的某一个角落,JVM找到了正在哭泣的Xxx.class文件,于是就将Xxx.class文件读取到了内存之中。Xxx类高兴极了,因为它终于可以被派上用场了。
JVM将Xxx.class文件读取到内存中之后,就根据Xxx.class的内容,在内存中的方法区中创建了一个Class对象,这个Class对象包含了Xxx类的全部信息,JVM知道了类的信息,就可以创建Xxx的对象了。
过了不久,程序中又要构造一个Xxx对象了。JVM去内存中的方法区一看,我擦!!!Xxx类的Class对象正笑嘻嘻的在那等着呢。Xxx类的Class对象对JVM说:你直接用我就可以了,不用再去硬盘上找Xxx.class文件了。于是JVM就用这个Class对象,又构造了一个Xxx的对象实例,而没有去硬盘上去找Xxx.class文件。
可以看出,Java程序在它开始运行之前并不是加载所有的类,只有当这个类要被使用时才会被加载,也就是动态加载。我们做一个小实验:
import java.util.*;
public class Test
{
public static void main(String[] args)
{
Scanner scan = new Scanner(System.in);
int num = scan.nextInt();
if(num == 1)
{
A a = new A();
}
else
{
B b = new B();
}
}
}
class A
{
static
{
System.out.println("AAAAAA");
}
}
class B
{
static
{
System.out.println("BBBBBB");
}
}
当类被加载时,会执行类中的静态代码块。根据输出内容,我们可以判断哪个类被加载了。程序比较简单,就是根据不同的输入内容,构造不同类的对象实例。当输入数字1时,可以看到只有A类被加载,而B类没有被加载。
另外,在加载一个类的时候,JVM的类加载器会首先检查该类的Class对象是否已经被加载到内存中,如果尚未加载,加载器才会去查找.class文件执行加载过程。看下面的例子:
import java.util.*;
public class Test
{
public static void main(String[] args)
{
A a1 = new A();
A a2 = new A();
A a3 = new A();
A a4 = new A();
}
}
class A
{
static
{
System.out.println("AAAAAA");
}
}
从输出可以看出,虽然构造了多个A类的对象,但A类就只被加载了一次。
好啦,现在我们知道了,Class对象就包含了一个类的全部信息,那么我们该如何使用Class对象呢?先睡觉,明天继续