描述一下JVM加载class文件的原理机制?

sodarkdays 2008-06-29 04:42:56
描述一下JVM加载class文件的原理机制?
...全文
7119 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
xfsunxiaolong 2011-10-13
  • 打赏
  • 举报
回复
1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,
类装载器所做的工作实质是把类文件从硬盘读取到内存中

2.java中的类大致分为三种:
1.系统类
2.扩展类
3.由程序员自定义的类

3.类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 通过class.forname()等方法,显式加载需要的类

daijope 2011-10-13
  • 打赏
  • 举报
回复 1
JVM
类的加载、连接、初始化:
加载:查找并加载类的二进制数据

连接:
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化我默认值
解析:把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值

Java程序对类的主动使用:
— 创建类的实例
— 访问某个类或接口的静态变量,或者对该静态变量赋值
— 调用类的静态方法
— 反射(如Class.forName(xx))
— 初始化一个类的子类
— Java须立即启动时被标明启动类的类 Java Test
这时Test就是被标明启动类的类。
以上就是主动使用的情况。

所有的java虚拟机实现必须在每个类或接口首次
主动使用时才会初始化他们。

除了这六种其他的都是被动使用。

加载.class文件的几种方式:
— 从本地系统中直接加载
— 从网络下载.class文件
— 从zip,jar压缩包中加载
— 从专有数据库中提取.class文件
— 将Java源文件动态编译为.class文件

类加载的最终产品是位于堆中的class对象。


有两种类型的类加载器.

自带的加载器:
— 根类加载器(BootStrap) c++语言写的,我们无法获取
— 扩展类加载器(Extension)Java实现
— 系统类加载器(system)应用加载器 Java实现

用户定义的了加载器:
— Java.lang.ClassLoader的子类
— 用户可以定制类的加载方式
Public abstract class ClassLoader

类加载器并不需要等待某个类首次使用时才去加载它们,注意这里不是初始化。

LinkageError

类加载后,就将进入连接阶段。连接就是就是将已经读入到内存的二进制数据合并到虚拟机的环境之中。

类的验证的内容
— 类文件的结构检查
— 语义检查
— 字节码的验证
— 二进制兼容性的验证

程序分析:
<code>
package com.cn.dai;

class Singleton
{
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;

private Singleton()
{
counter1++;
counter2++;
}
public static Singleton getInstance()
{
return singleton;
}
}
public class MyTest
{
public static void main(String[] args)
{
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 = " + singleton.counter1);
System.out.println("counter2 = " + singleton.counter2);
}
}
</code>
Output: 1,0
对比:
<code>
package com.cn.dai;

class Singleton
{
public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();


private Singleton()
{
counter1++;
counter2++;
}
public static Singleton getInstance()
{
return singleton;
}
}
public class MyTest
{
public static void main(String[] args)
{
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 = " + singleton.counter1);
System.out.println("counter2 = " + singleton.counter2);
}
}
Output:1,1

这两段程序的不同之处只在于一句代码的位置不同,而输出的结果确截然不同,分析其原理。
对于第一段程序。一开始JVM加载类Singleton,并没有初始化,这个类的成员变量的取值分别人默认值,即 singleton = null;count1 = 0; count2 = 0;现在使用了Singleton的static成员,这是对这个类的一次主动使用,所以这个类会被初始化,程序从上至下一次执行,会先new 一个对象,当然有执行构造方法,这是count1,count2的值都会加1,变成1,下面两句public static int counter1; public static int counter2 = 0;第一句没有赋初值,所以counter1的值是1,第二句counter会被从新赋值为0,所以输出的结果是1,0.
第二段程序是类似的,不管是public static int counter2 = 0;还是构造方法中的counter1++;counter2++;都是在类初始化的时候才执行的。
编译常量:
<code>
package com.cn.dai;

import java.util.Random;

class FinalTest2
{
public static final int x = new Random().nextInt(100);

static
{
System.out.println("FinalTest2 static block");
}
}

public class Test3
{
public static void main(String[] args)
{
System.out.println(FinalTest2.x);
}
}

</code>
Output:FinalTest2 static block
2(一个0~99的随机数)

对比:

<code>
package com.cn.dai;

import java.util.Random;

class FinalTest2
{
public static final int x = 6/3;

static
{
System.out.println("FinalTest2 static block");
}
}

public class Test3
{
public static void main(String[] args)
{
System.out.println(FinalTest2.x);
}
}

</code>

Output: 5(一个随机数)

这两段程序中的FinalTest2一个初始化了,一个没有,这是为什么呢?6/3是一个编译常量,在编译的时候就已经确定值了。这是不会导致类的初始化,前一段程序,public static final int x = new Random().nextInt(100);并不能在编译的时候就赋初值,类只有在加载的时候才能赋予初值。

这里我们可以发现final的变量的初始值不能是系统加载是赋予默认值就行了,它必须有我们给它一个值,如果我们给它的值,在编译的时候就可以确定下来,那么在类加载的时候给它赋的值就不是默认值了,而是这个编译时就可以确定的值。

如果在编译的时候不能确定,那么就必须在初始化的时候赋予一个初始值,如果没有这个初始的赋值操作,那么编译器就会报错的。

一个final的变量,不是说任何时期都会有一个初始值。在类加载的时候它可以有一个初始值,也可以没有(这时编译器会确定后面是否有赋初值的操作,如果没有就会报错)。



final 变量的初始化:

可以在函数块或者构造函数中去初始化,因为我们要生成一个对象那么函数块和构造方法是必须的。而且初始化只能而这选一,不然会出现The final field i may already have been assigned。
如果要在构造方法冲去初始化final变量,那么每一个构造方法都必须有初始化操作。

当然最直接的办法就是直接在变量申明后面赋值。

Static final 变量的初始化,只能是直接赋值,或者是在静态代码块中赋予初值。

对于Interface,我们知道interface中是可以有变量的,但是它里面的变量默认是public static final的,而interface中是不能有代码块或者静态代码块,所以这时候我们定义接口时,如果里面有成员变量,那么只能是直接赋值。
<code>
interface A {
int a = 9;//( 前面的修饰符only public, static & final are permitted,不写的话默认就是这些合起来)

}
</code>

其实通过编译器报的这句错误The interface A cannot define an initializer,我们可以发现代码块或者静态代码块的作用就是拿来初始化变量的,initializer。





只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。
<code>
package com.cn.dai;

class Parent3
{
static int a = 3;

static
{
System.out.println("Parent3 static block");
}

static void doSomething()
{
System.out.println("do something");
}
}

class Child3 extends Parent3
{
static
{
System.out.println("Child3 static block");
}
}

public class Test6
{
public static void main(String[] args)
{
System.out.println(Child3.a);

Child3.doSomething();
}
}
</code>
这段代码Child3是不会被初始化的。



类加载器******
Java自带的类加载:
— 根类加载器(BootStrap) c++语言写的,我们无法获取
— 扩展类加载器(Extension)Java实现
— 系统类加载器(system)应用加载器 Java实现
我们也可以自己定义一个自己定义的类加载。

Class类(基因类):它有getClassLoader()方法,可以得到一个类的加载器。
API文档的一段话:
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
从这段话可以看出,我们可以得到一个我们是不能生成一个Class对象的,因为它的构造方法是private的,但是我们可以得到一个Class ,有三种方法可以得到Class。Class.forName(“全名”);obj.class;obj.getClass();。

Class与ClassLoader是双向的关联关系。你知道我,我也知道你,Class与对象之间是单向的,对象知道Class,但是Class却不知道Object,Class是Object的一面镜子。

JVM的类加载机制是采用叫父委托机制来实现的。当一个类加载器要去加载一个类,那么他会委托他的父亲来加载,一直向上,知道父加载器不能加载为止。

下面来自定义一个类加载器:


<code>
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

public class MyClassLoader extends ClassLoader {

private String name;
private String path = "d:\\";
private final String fileType = ".class";

public MyClassLoader(String name) {
super();
this.name = name;
}

public MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}

public String toString() {
return this.name;
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

public Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}

private byte[] loadClassData(String name) {
InputStream is = null;
ByteArrayOutputStream baos = null;

byte[] data;
try {
name = name.replace(".", "\\");
is = new FileInputStream(new File(path + name + fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}

}
data = baos.toByteArray();
return data;
}

public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("D:\\zzz\\server\\");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("D:\\zzz\\client\\");

MyClassLoader loader3 = new MyClassLoader(loader2, "loader3");
loader3.setPath("D:\\zzz\\other\\"); MyClassLoader loader4 = new
MyClassLoader(null, "loader3"); loader3.setPath("D:\\zzz\\sys\\");



testLoader(loader2);
testLoader(loader3);

}

public static void testLoader(ClassLoader loader) {
Class<?> clazz = null;
Object obj = null;
try {
clazz = loader.loadClass("Sample");
obj = clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

}

</code>
<code>
public class Dog {
public Dog() {
System.out.println("Dog is loaded by:" + this.getClass().getClassLoader());
}
}
</code>

<code>
public class Sample {
public int v1 = 1;
public Sample() {
System.out.println("Sample is loaded by:" + this.getClass().getClassLoader());
new Dog();
}
}

</code>

在编写一个自定义的类时应该注意:
1. 每一个自定义的类加载器都应该继承至ClassLoader这个类,这个类有两个构造方法,第一个ClassLoader(),这样表示父加载器为系统类加载器。ClassLoader()指定父加载器。
2. 我们要重写public Class<?> findClass(String name)这个方法,name是传进来的集体的类的全名,例如:com.cn.dai.Test或者默认包Test。首先我们要得到.class 文件的byte数组,然后用这个方法this.defineClass(name, data, 0, data.length);就可以得到该二进制文件对应的Class。
最后我们自己定义的类加载器加载的Class可以卸载,如果是其他的三种类加载器加载的Class会在程序运行期间一直存在。不会被卸载掉。

这里我们可以想到我们在类中申明的static变量会一经初始化,就会在程序运行的整个过程中不会被卸载掉,会一直存在。

回想单例模式,一经生成一个对象,那么我们每次得到的就会使同一个对象也就可以理解了。



Author: daijope
2011/8/14 20:27
leisore 2011-10-13
  • 打赏
  • 举报
回复
suiwaxiao 2011-10-13
  • 打赏
  • 举报
回复
装在、连接、初始化
树成 2010-09-03
  • 打赏
  • 举报
回复
虚拟机加载class就分为三步,装在、连接、初始化。
ksoong 2010-09-03
  • 打赏
  • 举报
回复
昨天没事干,刚看了一下jvm classloader
ksoong 2010-09-03
  • 打赏
  • 举报
回复
salever 2010-09-03
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 java2000_net 的回复:]

ClassLoader 其实就是读取文件,按照CLASSPATH配置的顺序。
然后解析后转化为类。
[/Quote]
先顶一个,言简意赅

PS:我把你的签名读成“老婆竹”了,想着你好V5啊。。。
- -!
java2000_net_net 2010-09-03
  • 打赏
  • 举报
回复
此回复为自动发出,仅用于显示而已,并无任何其他特殊作用
楼主【sodarkdays】截止到2008-06-29 04:42:57的历史汇总数据(不包括此帖):
发帖数:1 发帖分:100
结贴数:0 结贴分:0
未结数:1 未结分:100
结贴率:0.00 % 结分率:0.00 %
如何结贴请参考这里:http://topic.csdn.net/u/20080501/09/ef7ba1b3-6466-49f6-9d92-36fe6d471dd1.html
zxsqi521 2010-09-03
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 closewbq 的回复:]

我建议你看深入java虚拟机,里面绝对有你要的东西!也能帮助你对java有个更高的认识!
[/Quote]

同意
closewbq 2010-09-03
  • 打赏
  • 举报
回复
我建议你看深入java虚拟机,里面绝对有你要的东西!也能帮助你对java有个更高的认识!
bama2008 2010-09-03
  • 打赏
  • 举报
回复
这个问题 推介你看看 孙卫琴的 <JAVA面向对象编程>

里面有你想要的答案
lutinghuan 2010-09-03
  • 打赏
  • 举报
回复
顶! .
dididadabit 2010-09-02
  • 打赏
  • 举报
回复
此贴不顶,更待何时啊!!!
jerry_lan272 2010-08-12
  • 打赏
  • 举报
回复
顶!!!!
ledkaa 2010-08-12
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 java2000_net 的回复:]

ClassLoader 其实就是读取文件,按照CLASSPATH配置的顺序。
然后解析后转化为类。
[/Quote]

这位老兄,你女儿的小辫子很可爱
gycproperties 2010-08-11
  • 打赏
  • 举报
回复
顶。。
longmei998 2010-03-24
  • 打赏
  • 举报
回复
我也了解下
SambaGao 2010-03-24
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 java2000_net 的回复:]
ClassLoader 其实就是读取文件,按照CLASSPATH配置的顺序。
然后解析后转化为类。
[/Quote]

很好
npuhetao 2008-07-01
  • 打赏
  • 举报
回复
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
加载更多回复(2)

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧