类从倍加早到虚拟机内存中开始,到卸载出内存位置,它的整个生命周期包括:
加载(Loading)、验证(Vertification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)7个阶段。
其中 验证、准备、解析 3个阶段统称为:连接(Linking)
加载 是 类加载过程中的一个阶段。JVM做三件事:
验证是连接阶段的第一步,这一阶段的目的:是为了确保Class文件的字节流中包含的信息符合当前JVM的要求,并且不会危害JVM自身的安全。
如果验证到输入的字节流不符合Class文件格式的约束,JVM就会抛出一个java.lang.VertifyError异常或其子类异常。
第一段要验证字节流是否符合
这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证,字节流才会进入内存的方法区中进行存储。so…..后面的3个阶段的验证都是基于方法区的存储结构进行的,不会再直接操作字节流。
第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
第二阶段主要是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。
第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
在第二阶段对元数据信息中的数据类型做完校验后,这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害JVM安全的事件。e.g.
如果一个方法体没有通过了字节码验证,那么肯定是有问题;如果通过了,也不能说明其一定就是安全的。
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三个阶段——解析阶段中发生。
符号引用验证可以看做是是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
符号引用验证的目的:确保解析动作能正常执行,如果无法通过符号引用验证,那么将会抛出一个 java.lang.IncompatibleClassChangeError异常的子类,如:java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
验证阶段很重要,但不是一定必要的(因为对程序运行期没有影响)。如果所运行的代码(包括自己编写的及第三方包中的代码)都已经被反复使用和验证过,那么在实施阶段就可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施,以缩短JVM类加载的时间。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
注意:
public static int value = 123;
那变量value在准备阶段过后的初始值是0,而不是123,因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstatic执行时程序被编译后,存放于类构造器\()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
“特殊情况”:如果类字段的字段属性中存在 ConstantValue属性,那么准备阶段变量value就会被初始化为ConstantValue属性所指定的值,
public static final int value = 123;
编译时Javac将会把value生成ConstantValue属性,在准备阶段JVM就会根据ConstantValue的设置将value赋值为123.
解析阶段是JVM将常量池内的符号引用替换为直接引用的过程。
解析的时间并没有做规定,只要发生在 xxxx(太多了,自己百度或者看书吧) 指定执行之前就可以。
解析动作主要针对:类或接口、字段、类方法、接口方法、方法类型、方法句柄 和调用点限定符 7类符号引用进行。
(太复杂,待深入…..日后再来补充)
类初始化阶段是类加载的最后一步。前面的加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由JVM主导控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说字节码)
初始化阶段是执行类构造器\()方法的过程。
特点:
public class Test{
static{
i = 0; // 给变量赋值正常通过
System.out.println(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
static class Parent{
public static int A = 1;
static{
A = 2;
}
}
static class Sub extends Parent{
public static int B = A;
}
public static void main(String[] args){
System.out.println(Sub.B); // 字段B的值为2而不是1,因为优先父类的static语句块,而不是子类赋值操作。
}
JVM设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到JVM外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码模块称为“类加载器”。
类加载器虽然只用于实现类的加载动作。但作用却不限于此。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。 即:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类就必定不相等。ps:此处的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用 instanceof 关键字做对象的所属关系判定等情况。
package com.happy.common.util;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Derek.Wu
* @Date 2016年09月18日
*/
public class ClassLoaderTest {
public static void fun(){
System.out.println("com.happy.common.util.ClassLoaderTest.fun()");
}
public static void main(String[] args) throws Exception{
ClassLoader myLoader = new ClassLoader() {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try{
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is ==null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj1 = myLoader.loadClass("com.happy.common.util.ClassLoaderTest").newInstance();
System.out.println(obj1.getClass()); // class com.happy.common.util.ClassLoaderTest
System.out.println(obj1 instanceof com.happy.common.util.ClassLoaderTest); // false
System.out.println(obj1 instanceof java.lang.Object); // true
//obj1.fun();
Object obj2 = new Object();
System.out.println(obj2.getClass()); // class java.lang.Object
System.out.println(obj2 instanceof Object); //
//ClassLoaderTest obj3 = myLoader.loadClass("com.happy.common.util.ClassLoaderTest").newInstance();
ClassLoaderTest obj3 = new ClassLoaderTest();
System.out.println(obj3.getClass()); //class com.happy.common.util.ClassLoaderTest
System.out.println(obj3 instanceof com.happy.common.util.ClassLoaderTest); // true
System.out.println(obj3 instanceof java.lang.Object); //
obj3.fun();//ok
}
}
Output:
class com.happy.common.util.ClassLoaderTest
false
true
class java.lang.Object
true
class com.happy.common.util.ClassLoaderTest
true
true
Some thing interesting……还需要再深入(TODO)
从Java虚拟机的角度来讲,只存在两种不同的类加载器:
所有其它的类加载器,都是由Java语言实现的,独立于JVM外部,并且全部都继承自 java.lang.ClassLoader
ps:还可以划分的更细些。
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*
* If a security manager is present, and the caller's class loader is
* not null and the caller's class loader is not the same as or an ancestor of
* the class loader for the class whose class loader is requested, then
* this method calls the security manager's {@code checkPermission}
* method with a {@code RuntimePermission("getClassLoader")}
* permission to ensure it's ok to access the class loader for the class.
*
*
If this object
* represents a primitive type or void, null is returned.
*
* @return the class loader that loaded the class or interface
* represented by this object.
* @throws SecurityException
* if a security manager exists and its
* {@code checkPermission} method denies
* access to the class loader for the class.
* @see java.lang.ClassLoader
* @see SecurityManager#checkPermission
* @see java.lang.RuntimePermission
*/
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
java.lang.ClassLoader.getSystemClassLoader()如下:
/**
* Returns the system class loader for delegation. This is the default
* delegation parent for new ClassLoader instances, and is
* typically the class loader used to start the application.
*
* This method is first invoked early in the runtime's startup
* sequence, at which point it creates the system class loader and sets it
* as the context class loader of the invoking Thread.
*
*
The default system class loader is an implementation-dependent
* instance of this class.
*
*
If the system property "java.system.class.loader" is defined
* when this method is first invoked then the value of that property is
* taken to be the name of a class that will be returned as the system
* class loader. The class is loaded using the default system class loader
* and must define a public constructor that takes a single parameter of
* type <tt>ClassLoadertt> which is used as the delegation parent. An
* instance is then created using this constructor with the default system
* class loader as the parameter. The resulting class loader is defined
* to be the system class loader.
*
*
If a security manager is present, and the invoker's class loader is
* not null and the invoker's class loader is not the same as or
* an ancestor of the system class loader, then this method invokes the
* security manager's {@link
* SecurityManager#checkPermission(java.security.Permission)
* checkPermission} method with a {@link
* RuntimePermission#RuntimePermission(String)
* RuntimePermission("getClassLoader")} permission to verify
* access to the system class loader. If not, a
* SecurityException will be thrown.
*
* @return The system ClassLoader for delegation, or
* null if none
*
* @throws SecurityException
* If a security manager exists and its checkPermission
* method doesn't allow access to the system class loader.
*
* @throws IllegalStateException
* If invoked recursively during the construction of the class
* loader specified by the "java.system.class.loader"
* property.
*
* @throws Error
* If the system property "java.system.class.loader"
* is defined but the named class could not be loaded, the
* provider class does not define the required constructor, or an
* exception is thrown by that constructor when it is invoked. The
* underlying cause of the error can be retrieved via the
* {@link Throwable#getCause()} method.
*
* @revised 1.4
*/
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
有必要的话,可以写自定义的类加载器。
类加载器之间的这种层次关系,称之为:类加载器的双亲委派模型(图自己百度一个吧)
双亲委派模型要求:除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不会以继承的关系来实现,而是都是使用组合(Composition)关系来复用父加载器的代码。
如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的家在请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是:Java类随着它的类加载器一起具备了一种带有优先级关系的层次关系。例如:java.lang.Object,存放在rt.jar中,由Bootstrap ClassLoader加载。如果自己写一个Object的同名类。
我自己试了一下,写了一个Object同名类,然后Run,然后报错如下:第一个Information
《深入理解Java虚拟机》说,可以正常编译,但是无法被加载运行。这里好像编译都出错了。
然后,我又只单独写了一个TestObject.java
public class TestObject{
public static void main(String[] args){
Object obj = new Object();
obj.fun();
}
}
class Object{
public void fun(){
System.out.println("自定义Object");
}
}
编译OK,运行OK,很困惑啊!!!(TODO Derek 先放着,回过头来再研究一遍这里。)
双亲委派模型的代码如下:
/**
* Loads the class with the specified "#name">binary name.
* This method searches for classes in the same manner as the {@link
* #loadClass(String, boolean)} method. It is invoked by the Java virtual
* machine to resolve class references. Invoking this method is equivalent
* to invoking {@link #loadClass(String, boolean) loadClass(name,
* false)}.
*
* @param name
* The "#name">binary name of the class
*
* @return The resulting Class object
*
* @throws ClassNotFoundException
* If the class was not found
*/
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
/**
* Loads the class with the specified "#name">binary name. The
* default implementation of this method searches for classes in the
* following order:
*
*
*
* Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded.
*
* Invoke the {@link #loadClass(String) loadClass} method
* on the parent class loader. If the parent is null the class
* loader built-in to the virtual machine is used, instead.
*
* Invoke the {@link #findClass(String)} method to find the
* class.
*
*
*
* If the class was found using the above steps, and the
* resolve flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting Class object.
*
*
Subclasses of ClassLoader are encouraged to override {@link
* #findClass(String)}, rather than this method.
*
* Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock getClassLoadingLock} method
* during the entire class loading process.
*
* @param name
* The "#name">binary name of the class
*
* @param resolve
* If true then resolve the class
*
* @return The resulting Class object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
/**
* Returns the class with the given "#name">binary name if this
* loader has been recorded by the Java virtual machine as an initiating
* loader of a class with that "#name">binary name. Otherwise
* null is returned.
*
* @param name
* The "#name">binary name of the class
*
* @return The Class object, or null if the class has
* not been loaded
*
* @since 1.1
*/
protected final Class> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class> findLoadedClass0(String name);
// true if the name is null or has the potential to be a valid binary name
private boolean checkName(String name) {
if ((name == null) || (name.length() == 0))
return true;
if ((name.indexOf('/') != -1)
|| (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
return false;
return true;
}
逻辑:先检查是否已经被加载过(Native Method),若没有加载,则调用父加载器的loadClass()方法,若父加载器为空,则默认使用启动类加载器作为父加载器作为父类加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。