project->Properties->java compiler->修改选项即可。
1.类加载器深入剖析
Java虚拟机与程序的生命周期 :。
当我们执行一个java程序的时候 , 会启动一个JVM进程 , 当程序执行完之后 , JVM进程就消亡了 ; 。
在如下情况下JVM将结束声明周期 :。
System.exit(int)方法 , 当执行这个方法的时候 , 虚拟机会退出 ; 这个方法传入一个整形参数 , 这个参数是状态吗 : 如果这个整形是 0 的话 , 就是正常退出 , 如果不是0的话 , 就是异常退出 ;。
程序正常结束 ;
程序执行过程中 , 遇到了异常或错误 , 而异常终止 : 如果我们在程序中出现了异常 , 而不去处理 , 会将异常一直抛给main函数 , main函数会将异常抛给JVM , JVM如果处理不了异常 , JVM就会异常退出 ;。
由于操作系统出现错误导致JVM进程终止 : JVM所依赖的平台出现错误 , 导致JVM终止 ;。
2.类的加载,连接和初始化
加载 : 查找并加载类的二进制数据 , 将class字节码文件加载到内存中 ;。
连接 :
验证
: 确保被加载的类的正确性 , 使用javac 编译工具生成的字节码文件能通过验证 , 如果不是由javac编译生成的字节码文件 , 如果自己生成的字节码文件不符合JVM虚拟机对字节码文件的要求的话 , 可能会出现验证通不过的情况 ; 比如说随便拿一个文件 , 将后缀名直接修改为.class , 这样的字节码文件肯定不合法 ; 。
准备
: 为类的静态变量分配内存 , 并将其初始化为默认值 ; 。
解析
: 把类中的符号引用转为直接引用 ;。
初始化 : 为类的静态变量赋予正确的初始值(正确的值指的是用户赋的值) ;。
-好像这个与连接阶段的准备有些重复 , 在连接的准备阶段只是赋予初始变量 , 如果用户给这个变量赋了初始值 , 那么这个变量在连接的准备阶段仍然会赋予初始值 ; 。
-在这个阶段 , 才会真正的将初始值赋给静态变量 ; 。
Java程序对类的使用方式有 主动使用 和 被动使用 ;。
所有的JVM实现 , 必须在每个类或者接口 , 被java程序 “首次主动使用” 时才初始化他们 ; 。
主动使用 :
创建类的实例 ;
访问某个类或接口的静态变量 , 或者对该静态变量赋值 ;。
调用类的静态方法 ;
反射 : Class.forName(“类名”) ;。
初始化一个类的子类 , 看做对父类的主动使用 ;。
java虚拟机启动的时候 , 被标明启动类的类 , 即包含main方法的类 , 程序的入口 ;。
除了上面6种主动使用之外 , 其它的情况均为被动使用 , 其它情况都不会执行第三步初始化 ;。
3.类的加载
(1)概念
类的加载 : 指的是将类的.class文件中的二进制数据读入到内存中 , 将其放在运行时数据区的方法区内 , 然后再堆区创建一个java.lang.Class对象 , 用来封装类在方法区内的数据结构 ;。
反射 : 反射就是跟句堆区的字节码文件 , 获取方法去的数据结构 ;。
解析 : Class对象是由JVM自己创建的 , 所有的对象都是经过Class对象创建 , 这个Class对象是反射的入口, 通过Class对象 , 可以关联到目标class字节码文件的内部结构 ;。
所有的类对应的Class对象都是唯一的一个 , 这个类是由JVM进行创建的 , 并且只有JVM才会创建Class对象 ; 。
类加载的最终产品是位于堆区中的Class对象 , Class对象封装了类在方法区内的数据结构 , 并且向Java程序员提供了访问方法区内的数据结构的接口(反射用的接口) ;。
(2)加载.class文件的方式。
从本地系统中直接加载 : 编译好的.class字节码文件直接从硬盘中加载 ;。
通过网络下载.class文件 : 将class字节码文件放在网络空间中 , 使用URLClassLoader来加载在网络上的.class字节码文件 , 使用默认的父亲委托机制加载字节码文件 ;。
从zip , jar 等压缩文件中加载字节码文件 : 在开发的时候 , 导入jar包 , 就是这种方式 ;。
从专有的数据库中提取字节码文件 ;。
将java源文件动态编译为字节码文件 ;。
(3)类加载器
l Java虚拟机自带的类加载器 :。
-根类加载器 ( Bootstrap ) : 是C++写的 , 程序员无法再java代码中获取这个类 , 如果使用getClassLoader()方法获取到的是一个null值 ;。
package jvm;
Java代码
public class ClassLoaderTest {。
public static void main(String[] args) throws Exception {。
//java.lang包下的类使用的是跟类加载器进行加载的。
Class clazz = Class.forName("java.lang.String");。
System.out.println(clazz.getClassLoader());。
//自定义的类使用的是应用类加载器(系统加载器)。
Class clazz2 = Class.forName("jvm.C");。
System.out.println(clazz2.getClassLoader());。
}
}
class C{}
执行结果 :
null
Java代码
sun.misc.Launcher$AppClassLoader@1372a1a。
-扩展类加载器 ( Extension ) : Java编写 ;。
-系统类加载器(应用加载器) ( System ) : Java编写 ; 。
用户自定义的类加载器 :
-自定义的类加载器都是java.lang.ClassLoader子类 ;。
-用户可以定制类的加载方式
String类是由根类加载器进行加载的 , 我们可以调用Class对象的。
关于代理中创建对象的类加载器 : 创建代理对象的时候 , 动态创建一个类 , 然后使用指定的类加载器将这个类加载到内存中 , 然后用加载到内存中的类生成代理对象 ;。
创建代理对象的方法 : newProxyInstance(ClassLoader loader , Class [] Interfaces , InvocationHandler h )。
loader 是定义的代理类的类加载器 , 中间的接口数组是代理类的要实现的接口列表 , h 是指派方法调用的调用处理程序 ;。
类加载器并不需要在某个类被 “首次主动使用” 时再加载它 :。
-预加载机制 : JVM规范允许类加载器在预料某个类将要被使用的时就预先加载它 ;。
-报错时机 : 如果在预加载的过程中遇到了字节码文件缺失或者存在错误的情况 , 类加载器会在程序首次主动使用(上面提到的六种情况)该类的时候报错(LinkageError错误) ;。
-不报错时机 : 如果这个错误的字节码文件所对应的类一直没有被使用 , 那么类加载器就不会报告错误 ,即便有错误也不会报错 ;。
LinkageError : 这个错误是Error的子类 , 程序不能处理这些错误 , 这些错误都是由虚拟机来处理 , 这个错误表示出错的是子类 , 在一定程序上依赖于另一个类 , 在编译了前面一个类的时候 , 与后面所依赖的类出现了不兼容的情况 ;。
例如 : 我们使用了jdk 1.6 在编译一个程序 , 但是运行环境是jre1.5的 , 就会出现LinkageError错误 ;。
4.类的连接
(1)定义
类被加载之后 , 就进入链接阶段 ; 链接 : 将已读入内存的二进制数据合并到虚拟机的运行时环境中去 ;。
链接顾名思义就是讲类与类之间进行关联 , 例如我们在类A中调用了类B , 在链接过程中 , 就将A与B进行链接 ,将面向对象语言转化为面向过程语言 ;。
(2)类的验证
类文件的结构检查 : 确保类文件遵从java类文件的固定格式 , 开始类的描述 , 声明 , 方法调用格式等 ;。
语义检查 : 确保类本身符合java语言的语法规定 , 比如final类型的类没有子类 , final类型的方法没有被覆盖 ,在eclipse中这种错误编译的时候不能通过 , 但是通过其他的方法可以生成错误的字节码文件 , 这里就是检测恶意生成的字节码文件 ;。
字节码验证 : 确保字节码流可以被JVM安全的执行 , 字节码流代表java方法(包括静态方法和实例方法) , 它是由被称作操作码的单字节指令组成的序列 , 每一个操作码后面跟着一个或多个操作数 , 字节码验证步骤会检查每个操作码是否合法 , 即是否有着合法的操作数 ;。
下面是指令码组成的序列 , 类似于微指令 :。
Jvm编译指令代码代码
// Compiled from ByteToCharCp1122.java (version 1.5 : 49.0, super bit) 。
public class sun.io.ByteToCharCp1122 extends sun.io.ByteToCharSingleByte {。
// Field descriptor #17 Lsun/nio/cs/ext/IBM1122;。
private static final sun.nio.cs.ext.IBM1122 nioCoder;。
// Method descriptor #18 ()Ljava/lang/String;。
// Stack: 1, Locals: 1。
public java.lang.String getCharacterEncoding();。
0 ldc <String "Cp1122"> [1]。
2 areturn
Line numbers:
[pc: 0, line: 25]。
// Method descriptor #2 ()V。
// Stack: 2, Locals: 1。
public ByteToCharCp1122();。
0 aload_0 [this]。
1 invokespecial sun.io.ByteToCharSingleByte() [25]。
4 aload_0 [this]。
5 getstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]。
8 invokevirtual sun.nio.cs.ext.IBM1122.getDecoderSingleByteMappings() : java.lang.String [27]。
11 putfield sun.io.ByteToCharSingleByte.byteToCharTable : java.lang.String [24]。
14 return
Line numbers:
[pc: 0, line: 28]。
[pc: 4, line: 29]。
[pc: 14, line: 30]。
// Method descriptor #2 ()V。
// Stack: 2, Locals: 0。
static {};
0 new sun.nio.cs.ext.IBM1122 [15]。
3 dup
4 invokespecial sun.nio.cs.ext.IBM1122() [26]。
7 putstatic sun.io.ByteToCharCp1122.nioCoder : sun.nio.cs.ext.IBM1122 [23]。
10 return
Line numbers:
[pc: 0, line: 22] 。
l 二进制兼容性的验证 : 确保相互引用的类之间协调一致的 ; 例如在A类的a()方法中调用B类的b()方法 , JVM在验证A类的时候 , 会验证B类的b()方法 , 加入b()方法不存在 , 或者版本不兼容(A,B两类使用不同的JDK版本编译) , 会抛出NoSuchMethodError错误 ;。
(3)准备阶段
在准备阶段 , JVM为类的静态变量分配内存空间 , 并设置默认的初始值 . 例如下面的Sample类 , 在准备阶段 ,为int类型的静态变量分配4个字节 , 并赋予初始值 0 ; 为long 类型的静态变量 b , 分配8个字节 , 并赋予初始值 0 ;。
PS : 在java中基本类型变量占用的空间是一定的 , java运行在JVM上的 , 在C中 , 就要根据平台变化而变化了 ;。
public class Sample {。
Java代码
private static int a = 1 ; 。
private static long b ; 。
static {
b = 2 ;
(4)类的解析
在解析阶段 , JVM 会把类的二进制数据中的符号引用替换为直接引用 , 例如在A类中的a()方法引用B类中的b()方法 ;。
在A类的二进制数据中包含了一个对B类的b()方法的符号引用 , 这个符号引用由b()方法的全名和相关的描述符组成 , 在Java解析阶段 , 就会把这个符号引用替换为指针 , 这个指针就是C语言中的指针了 , 该指针指向B类的b()方法在方法区中的内存位置 , 这个指针就是直接引用 ;。
5.类的初始化
在初始化阶段 , Java虚拟机执行类的初始化操作 , 为类的静态变量赋予初始值 , 在程序中 , 静态变量初始化有两种途径 :。
直接在声明处进行初始化 , 例如下面的Sample中的 变量a ;。
在静态代码块中进行初始化 , 例如下面的Sample中的变量b ;。
Java代码
public class Sample {。
private static int a = 1 ;。
private static long b ;。
static {
b = 2 ;
}
}
6.面试题介绍
Java代码
public class PrepareOrInit {。
public static void main(String[] args) {。
Singleton singleton = Singleton.getInstance();。
System.out.println(singleton.count1);。
System.out.println(singleton.count2);。
}
}
class Singleton{。
private static Singleton singleton = new Singleton() ;。
public static int count1 ;。
public static int count2 = 0 ;。
private Singleton(){。
count1 ++ ;
count2 ++ ;
public static Singleton getInstance(){。
return singleton ;。
}
执行结果 : 1 0
分析 : 这段代码与类的链接中的准备阶段 和 初始化阶段 有关系 , 准备阶段是给静态的字段赋予默认值 , 初始化阶段给静态变量赋予正确的值 , 即用户的值 ;。
在主函数中 , 调用了类的静态方法 , 相当于主动使用 , 这里调用了类的静态方法 ;。
之后进行连接的准备操作 , 给类中的静态变量赋予初值 , singleton值为null , count1 与 count2 值为0 ;。
执行初始化操作 , 给类中的静态变量赋予正确的值 , 给singleton变量赋予正确的值 , 调用构造方法 , 此时count1与 count2执行自增操作 , 两者都变成1 , 然后执行count1的赋予正确值操作 , 这里用户没有赋值操作 , count2 用户进行了 赋值为0的操作 , 0将原来的1覆盖掉了 , 因此结果为 1 , 0 ;。
Java代码
public class PrepareOrInit {。
public static void main(String[] args) {。
Singleton singleton = Singleton.getInstance();。
System.out.println(singleton.count1);。
System.out.println(singleton.count2);。
}
}
class Singleton{。
public static int count1 ;。
public static int count2 = 0 ;。
private static Singleton singleton = new Singleton() ;。
private Singleton(){。
count1 ++ ;
count2 ++ ;
public static Singleton getInstance(){。
return singleton ;。
}
执行结果 : 1 1
在准备阶段count1 和 count2 都赋值为0 , 然后在初始化阶段 , 全部赋值为1 ;。
java -version和javac - version一下,
看下两个版本是否不一致
// 引用自:http://topic.csdn.net/t/20050708/12/4131400.html#。
一, 类路径 (class path) 。
当你满怀着希望安装好了 java, 然后兴冲冲地写了个 hello world,然后编译, 。
运行, 就等着那两个美好的单词出现在眼前, 可是不幸的是, 只看到了 Can't find 。
class HelloWorld 或者 Exception in thread "main" java.lang.NoSuchMethodError 。
: maain. 。
为什么呢? 编译好的 class 明明在呀. 。
我们一起来看一看 java 程序的运行过程. 我们已经知道 java 是通过 java 。
虚拟机来解释运行的, 也就是通过 java 命令, javac 编译生成的 .class 。
文件就是虚拟机要执行的代码, 称之为字节码(bytecode), 虚拟机通过 classloader 。
来装载这些字节码, 也就是通常意义上的类. 这里就有一个问题, classloader 从 。
哪里知道 java 本身的类库及用户自己的类在什么地方呢? 或者有着缺省值(当前路径) 。
.
或者要有一个用户指定的变量来表明, 这个变量就是类路径(classpath), 或者在运行 。
的时候传参数给虚拟机. 这也就是指明 classpath 的三个方法. 编译的过程和运行 。
的过程大同小异, 只是一个是找出来编译, 另一个是找出来装载. 。
实际上 java 虚拟机是由 java luncher 初始化的, 也就是 java (或 java.exe) 。
这个程序来做的. 虚拟机按以下顺序搜索并装载所有需要的类: 。
1, 引导类: 组成 java 平台的类, 包含 rt.jar 和 i18n.jar 中的类. 。
2, 扩展类: 使用 java 扩展机制的类, 都是位于扩展目录($JAVA_HOME/jre/lib/e 。
xt)
中的 .jar 档案包. 。
3, 用户类: 开发者定义的类或者没有使用 java 扩展机制的第三方产品. 你必须在 。
命令行中使用 -classpath 选项或者使用 CLASSPATH 环境变量来确定这些类的位置. 我 。
们在上面所说的用户自己的类就是特指这些类. 。
这样, 一般来说, 用户只需指定用户类的位置, 引导类和扩展类是"自动"寻找的. 。
那么到底该怎么做呢? 用户类路径就是一些包含类文件的目录, .jar, .zip 文件的 。
列表, 至于类具体怎么找, 因为牵扯到 package 的问题, 下面将会说到, 暂时可认为 。
只要包含了这个类就算找到了这个类. 根据平台的不同分隔符略有不同, 类 unix 的系 。
统基本上都是 ":", windows 多是 ";". 其可能的来源是: 。
* ".", 即当前目录, 这个是缺省值. 。
* CLASSPATH 环境变量, 一旦设置, 将缺省值覆盖. 。
* 命令行参数 -cp 或者 -classpath, 一旦指定, 将上两者覆盖. 。
* 由 -jar 参数指定的 .jar 档案包, 就把所有其他的值覆盖, 所有的类都来自这 。
个指
定的档案包中. 由于生成可执行的 .jar 文件, 还需要其他一些知识, 比如 package, 。
还有
特定的配置文件, 本文的最后会提到. 可先看看 jdk 自带的一些例子. 。
我们举个 HelloWorld 的例子来说明. 先做以下假设: 。
* 当前目录是 /HelloWorld (或 c:\HelloWorld, 以后都使用前一个) 。
* jdk 版本为 1.2.2 (linux 下的) 。
* PATH 环境变量设置正确. (这样可以在任何目录下都可以使用工具) 。
* 文件是 HelloWorld.java, 内容是: 。
public class HelloWorld 。
{
public static void main(String[] args) 。
{ 。
System.out.println("Hello World!\n"); 。
System.exit(0); 。
} 。
}
首先这个文件一定要写对, 如果对 c 熟悉的话, 很有可能写成这样: 。
public static void main(int argc, String[] argv) 。
{
.... 。
}
这样是不对的, 不信可以试一试. 由于手头没有 java 的规范, 所以 。
作如下猜想: java 的 application 程序, 必须以 public static void main(String[ 。
])
开始, 其他不一样的都不行. 。
到现在为止, 我们设置方面只设置了 PATH. 。
1, 当前路径就是指你的 .class 文件在当前目录下, 。
[HelloWorld]$ javac HelloWorld.java //这一步不会有多大问题, 。
[HelloWorld]$ java HelloWorld // 这一步可能就会有问题. 。
如果出了象开头那样的问题, 首先确定不是由于敲错命令而出错. 如果没有敲错命 。
令,
那么接着做:
[HelloWorld]$ echo $CLASSPATH 。
或者 。
c:\HelloWorld>echo %CLASSPATH% 。
看看 CLASSPATH 环境变量是否设置了, 如果设置了, 那么用以下命令: 。
[HelloWorld]$ CLASSPATH= 。
或者 。
c:\HelloWorld> set CLASSPATH= 。
来使它为空, 然后重新运行. 这次用户类路径缺省的是 ".", 所以应该不会有相 。
同的问题了. 还有一个方法就是把 "." 加入到 CLASSPATH 中. 。
[/]$ CLASSPATH=$CLASSPATH:. 。
或者 。
c:\HelloWorld> set CLASSPATH=%CLASSPATH%;. 。
同样也可以成功. Good Luck. 。
2, 当你的程序需要第三方的类库支持, 而且比较常用, 就可以采用此种方法.比如 。
常
用的数据库驱动程序, 写 servlet 需要的 servlet 包等等. 设置方法就是在环境变量 。
中
加入 CLASSPATH. 然后就可以直接编译运行了. 还是以 HelloWorld 为例, 比如你想在 。
根
目录中运行它, 那么你直接在根目录下执行 。
$ java HelloWorld 。
或者 。
c:\>java HelloWorld 。
这样肯定会出错, 如果你的 CLASSPATH 没有改动的话. 我想大家应该知道为什么错 。
了
吧, 那么怎么改呢? 前面说过, 用户类路径就是一些包含你所需要的类的目录, .jar 档 。
案
包, .zip 包. 现在没有生成包, 所以只好把 HelloWorld.class 所在的目录加到 CLAS 。
SPAT
了, 根据前面的做法, 再运行一次, 看看, 呵呵, 成功了, 换个路径, 又成功了!! 不仅 。
仅?br /> 以直接运行其中的类, 当你要 import 其中的某些类时, 同样处理. 。
不知道你想到没有, 随着你的系统的不断的扩充, (当然了, 都是一些需要 java 的 。
东挝?
如果都加到这个环境变量里, 那这个变量会越来越臃肿, 虽然环境变量空间可以开很大 。
, 总
觉得有些不舒服. 看看下面一个方法. 。
3, 在命令行参数中指明 classpath. 。
还是和上面相同的目标, 在任何目录下执行 HelloWorld, 用这个方法怎么实现呢? 。
[/]$ java -cp /HelloWorld HelloWorld 。
或者 。
c:\>java -cp c:\HelloWorld HelloWorld 。
就可以了. 这是这种方法的最简单的应用了. 当你使用了另外的包的时候, 还可以 。
采用用?br /> 种方法. 例如: 。
$ javac -classpath aPath/aPackage.jar:. myJava.java 。
$ java -cp aPath/aPackage.jar:. myJava 。
或者 。
c:\> javac -classpath aPath\aPackage.jar;. myJava.java 。
c:\> java -cp aPath\aPackage.jar;. myJava 。
这种方法也有一个不方便的的地方就是当第三方包所在的路径较长或者需要两个以 。
上包包?br /> 时候, 每次编译运行都要写很长, 非常不方便, 这时候可以写脚本来解决. 比 。
如一个例子:
compile (文件, 权限改为可执行, 当前目录) 。
$ cat compile 。
--------------------------- 。
#!/bin/bash 。
javac -classpath aPath\aPackage.jar:anotherPath\anotherPackage.jar:. m 。
yJavva.java 。
--------------------------- 。
run (文件, 权限改为可执行, 当前目录) 。
$cat run 。
--------------------------- 。
#!/bin/bash 。
java -cp aPath\aPackage.jar:anotherPath\anotherPackage.jar:. myJava 。
--------------------------- 。
或者: 。
compile.bat 。
c:\HelloWorld> type compile.bat 。
------------------------- 。
javac -classpath aPath\aPackage.jar:anotherPath\anotherPackage.jar:. m 。
yJavva.java 。
------------------------- 。
run.bat 。
c:\HelloWorld> type run.bat 。
------------------------ 。
java -cp aPath\aPackage.jar:anotherPath\anotherPackage.jar:. myJava 。
------------------------ 。
就可以了. 试试看. 。
前面提到了扩展类, 扩展类是什么呢? java 的扩展类就是应用程序开发者用来 。
扩展核心平台功能的 java 类的包(或者是 native code). 虚拟机能像使用系统类一 。
样使用这些扩展类. 有人建议可以把包放入扩展目录里, 这样, CLASSPATH 也不用设了 。
,
也不用指定了, 岂不是很方便? 确实可以正确运行, 但是个人认为这样不好, 不能什么 。
东西都往里搁, 一些标准的扩展包可以, 比如, JavaServlet, Java3D 等等. 可以提个 。
建议, 加一个环境变量, 比如叫 JARPATH, 指定一个目录, 专门存放用户的 jar zip 。
等包, 这个要等 SUN 公司来做了. 。
windows98 下, 我原来安装的时候, 一直装不上, 总是死机, 好不容易装上了, 缺 。
省的是不能运行正确的, 然后把 tool.jar 放入 CLASSPATH 后工作正常. 现在作测试, 。
去掉仍然是正确的. 经过多次测试, 发现如果原来曾装过 jdk 的都很好, 没有装过的 。
装的时候会死机, 多装几次就可以了. 如果你发现正确安装后, 不能正常工作, 就把 。
tools.jar 加入 CLASSPATH, 试一下.。
在MyEclipse中自己的项目鼠标右键,选择build path 里面找到Add External Archives...然后将你的MYSQL驱动找到,导入进来。