27 June 2012

这两天看java虚拟机的文章。又复习了下Java虚拟机下些工作原理,记录如下:

编译期的运算

对于java文件里面的直接运算比如: 4+3, 在编译成bytecode(class文件)时,java编译器会直接将其运算为: bipush 7这里bipush的意思是将后面的byte放入栈并存储为整数。

其他的有用的optcode: istore_x 将int 的值存入变量x,有几个快捷的optcode为:istore_1~4, 即默认了1~4的变量空间 iload_x 将int值从变量x中取出。快捷的bytecode同上 aload_x

基本类型(8个):

类的加载: 从java bytecode开始: 类加载的最终产物:

类加载器: Java虚拟机自带类加载器: a. 根类加载器 (Bootstrap) in C b. 系统加载器 (System) in Java c. 扩展加载器 (Extension) in Java 用户自定义类加载器()ClassLoader的子类

每个Class类都包含一个对ClassLoader的引用

不同类是由不同的类加载器加载,如java.lang.String是有根类加载器加载,若是根加载器,则Class.getClassLoader()返回null, 而我自定义的类则ClassLoader为:sun.misc.Launcher$AppClassLoader@35ce36;(应用类加载器(系统加载器))

invocationHandler类应用了ClassLoader,这个类可以生成proxy 类加载器并不需要某个类首次主动使用时才加载他。 jvm规范: 允许类加载器预料某个类会被使用而预先加载他,如果当加载.class时遇到错误,则必须在这个类首次主动使用时才报出:LinkageError错误。

类加载后进入链接阶阶段,即将各个类的关系链接起来。

类的验证:

  1. 类的文件结构检测。确保类结构遵从java类文件的固定格式
  2. 语义检查: 确保类本身符合java语法规范比如final类没有子类。
  3. 字节码验证: 确认字节码流可以被java虚拟机安全执行。
  4. 二进制兼容验证:确保类相互引用之间协调一致

类的准备:

在准备阶段: java为静态变量分配内存: 为int类型分配4个字节,并赋值为0,为long分配8个字节,并赋值为0。

类的解析:

类的解析阶段,jvm会把类的二进制数据终端符号引用替换为直接引用: worker.run();

如此方法, jvm会将此方法转换为一个针对run方法的指针。这个指针就叫做直接引用。

类的初始化:

在初始化阶段: 在jvm执行初始化语句,执行静态变量和静态代码块中的语句,为静态变量赋予初始值。没有赋值的为默认值。所有静态变量从上到下进行初始化。

类先进行加载和连接; 如果类有父类,则先初始化父类。但不适用于接口。即不会将父接口或实现的接口初始化。(除非调用了接口。) 加入类中有初始化语句,则按照顺序执行初始化语句。

有意思: 对这种类:

import java.util.Random;

class Test {
public static final int x = 6 /3;
public static final int y = new Random().nextInt(100);

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

public class TestFinal {

    /**
    * @param args
    */
    public static void main(String[] args) {
        System.out.println(Test.y);
	}

}

这里面x是编译时常量,调用x时不会初始化类。而y是运行时常量。会在调用时初始化Test类。注意x和y都是final类型。

对于下面这种类:

```java class Parent{ static int x = 3; static{ System.out.println(“Parent initial”); } }

class Child extends Parent{ static int y = 4; static{ System.out.println(“Child initial”); } }

public class TestParent { static{ System.out.println(“Test initial”); }

public static void main(String[] args) {
    System.out.println(Child.y);
} } ```

其结果,按照初始化顺序执行。

如果调用父类的static变量,则不会初始化子类。

ClassLoader.getSystemClassLoader()会获得系统的ClassLoader(系统加载器)。

系统加载器的方法: classLoader.getClass();可以将一个class从硬盘加载到内存中,实现加载的过程。但是不会执行连接和初始化?

但是注意: 在加载的同时,jvm会在堆区内建立一个与之对应的Class类对象(反射),这个类对象与你load进来的类一一对应。反射方法可以进行。

如果用反射机制,则会执行初始化:

参考TestLoader Class

jvm的加载过程采用父亲委托机制(Parent Delegation),除根加载器外,所有的加载器都继承于ClassLoader类,当加载类Sample时,加载器会首先尝试让父加载器加载,若父加载器可以加载,则由父亲加载

根加载器:负责加载java核心类(由jdk提供)如java.lang.String。根据sun.boot.class.path所指定的路径中加载类库。

应用加载器(System): 父加载器是扩展加载器,并且是所有用户自定义加载器的默认父加载器。

扩展加载器:父加载器是 Bootstrap, ext/lib/ext目录下的类有扩展加载器加载,

这里父加载器不是java里面的继承。这里父加载器是一种组合的关系。 除了根加载器意外,其他的加载器都有且只有一个父加载器。

在加载时,类加载器会首先查找看类是否已经被加载,如果有则返回加载类的Class对象引用。如果Sample类没有被加载

定义类加载器:如果某个类加载器能够成功加载某个类,这个类加载器就成为定义类加载器。定义类及其子加载器都成为初始类加载器。 子加载器中包装了父加载器。

当生成一个用户自定义的类加载器时, 如果没有指定父加载器,则默认系统加载器(应用加载器)为其父加载器。

父加载器委托机制保证了类加载的安全性,保证了用户自定义类加载器不会加载应由系统加载器加载的类。

命名空间: 命名空间由加载器及其父加载器所加载的所有的类的集合。同一个命名空间中不会出现(含包名)相同的两个类

运行时包: 同一个类加载器加载的属于相同包的类组成了运行时包,同一运行时包里面的类相互可见。用户自定义类:java.lang.Spy不能访问到系统类加载器里面的内容。

自定义的类加载器加载的类会被加载,系统的三种类加载器加载的类Class对象是不会被卸载的。这些Class对象是可触及的



blog comments powered by Disqus