编译和运行

来源:互联网 发布:乐亭阿里巴巴农村淘宝 编辑:程序博客网 时间:2024/06/05 19:45

Java程序的编译
使用命令: javac *.java
编译时,会将写的.java文件(高级语言),生成相应的字节码文件.class文件(二进制代码)
Java程序的执行
使用命令:java *
流程: 加载到 – 连接 —- 初始化 …
虚拟机把Class文件加载到内存,然后进行校验,准备,解析和初始化,最终形成java类型,这就是虚拟机的类加载机制

编译期&运行期

编译期在类加载过程中的加载阶段。而运行期主要指类加载过程中的初始化阶段。

编译期常量
可以看到只有通过static final修饰的基本类型数据或者用字符串字面量直接声明的String的类属性,属于编译期常量。

 编译期可以将编译期常量代入到任何用到它的计算式中,也就是说可以在编译期执行计算式。同时需要注意的是编译期常量必须要在声明时进行初始化。

1-加载阶段(编译期)完成以下操作:

通过一个类的全限定名来获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转换化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

2-验证是为了确保Class文件中的字节流符合虚拟机的要求,并且不会危害虚拟机的安全。

3-准备阶段,准备阶段已经涉及到了类数据的初始化赋值。

-----在java虚拟机加载class文件并且验证完毕之后,就会正式给类变量分配内存并设置类变量的初始值。这些变量所使用的内存都将在方法区分配。注意这里说的是类变量,也就是static修饰符修饰的变量,在此时已经开始做内存分配,同时也设置了初始值。比如在 Public static int value = 123 这句话中,在执行准备阶段的时候,会给value分配内存并设置初始值0, 而不是我们想象中的123. 那么什么时候 才会将我们写的123 赋值给 value呢?------从类的加载到类的初始化过程中,还有一个过程称为“连接”。在连接过程中,有一个准备阶段,用于初始化类变量(static修饰的变量)系统默认值(例如整型默认值是0...)。特殊情况,public static final value = 123;该变量会根据常量池中的值初始化为123。

4-解析阶段在某些情况下,可以在初始化阶段之后再开始—为了支持java语言的运行时绑定。

 ----解析阶段是虚拟机将常量池中的符号引用转换为直接引用的过程。 ---- class文件中常量池(constant_pool)是一个类似表格的仓库,里面存储了我们编写的java类的类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。在java虚拟机将class文件加载到虚拟机内存之后,class类文件中的常量池信息以及其他的数据会被保存到java虚拟机内存的方法区。我们知道class文件的常量池存放的是java类的全名,接口的全名和字段名称描述符,方法的名称和描述符等信息,这些数据加载到jvm内存的方法区之后,被称做是符号引用。而把这些类的全限定名,方法描述符等转化为jvm可以直接获取的jvm内存地址,指针等的过程,就是解析。

5-初始化阶段属于类加载过程的最后一个阶段。主要用于显示的初始化类变量(static修饰的变量),执行静态代码块。

----- 这要从编译开始讲起。在编译的时候,编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。收集完成之后,会编译成java类的 static{} 方法,java虚拟机则会保证一个类的static{} 方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完成了类变量的初始化。值得说明的是,如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。-----如果有继承的话,父类中的类变量该如何初始化?这点由虚拟机来解决:虚拟机会保证在子类的static{}方法执行之前,父类的static{}方法已经执行完毕。由于父类的static{}方法先执行,也就意味着父类的静态变量要优先于子类的静态变量赋值操作。-----实例变量的初始化,其实是和静态变量的过程是类似的,但是时间和地点都不同(new 的时候才开始进行实例初始化)。一个类实例化过程主要执行实例域的默认初始化(赋予系统默认的值)和显示初始化,执行非静态代码块,最后执行构造函数在堆内存中生成一个实例对象。     1)当用new xx() 创建对象的时候,首先在堆上为xx对象分配足够的空间。     2)这块存储空间会被清零,这就是自动将xx对象中的所有基本类型的数据都设置成了默认值,而引用类型则被设置成了null(类似静态类的准备阶段的过程)     3)Java收集我们的实例变量赋值语句,合并后在构造函数中执行赋值语句。没有构造函数的,系统会默认给我们生成构造函数。     代码中若有一个final修饰的实例域,例如: final int z;。在实例化过程第一步的实例域初始化并不会执行默认的初始化。如果没有在声明中显示的赋值,那么必须在构造器执行结束之后,该实例域已经被赋值,否则编译不通过。

java中类的显示初始化会在首次被“主动使用”时执行初始化,为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。而我们这里所说的主动使用 包括:
1. 创建类的实例
2. 调用类的静态方法
3. 使用类的非常量静态字段,例如:static int i;
4. 调用Java API中的某些反射方法
5. 初始化某个类的子类
6. 含有main()方法的类启动时

初始化一个类包括两个步骤:
1、 如果类存在直接父类的话,且直接父类还没有被初始化,则先初始化其直接父类
2、 如果类存在一个初始化方法,就执行此方法
注:初始化接口并不需要初始化它的父接口
有趣的小例子
—–String str1 = “hello”;String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。这里的str1指的是方法区中的字符串常量池中的“hello”,编译时期就知道的;
—– String str2 = “he” + new String(“llo”);用+拼接字符串时会创建一个新对象再返回。
这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。 如果用str1.equal(str2),那么返回的是true;因为String类重写了equals()方法。

class文件中有一部分来存储编译期间生成的字面常量以及符号引用,这部分叫做class文件常量池,在运行期间对应着方法区的运行时常量池。JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池   工作原理     当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。   实现前提       字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。
原创粉丝点击