一、JVM与Java体系结构
由于跨平台性的设计,java的指令都是根据栈来实现的
栈:跨平台性、指令集小、指令多;执行性能比寄存器差
1、JVM的生命周期
- 开始
Java虚拟机的启动时通过引导类加载器创建一个初始类来完成的,这个类是由虚拟机的具体实现来指定的
- 执行
- 一个运行中的java虚拟机有一个清晰的任务:执行java程序
- 程序开始执行时他才运行,程序结束时他就停止
- 执行一个所谓的java程序的时候,真正执行的是一个叫做java虚拟机的进程
- 退出
有如下的几种情况:
- 程序正常执行结束
- 程序在执行过程中遇到了异常或者错误而终止
- 由于操作系统出现错误而导致java虚拟机进程终止
- 某线程调用Runtime类或者System类的exit方法,或Runtime类的halt方法,并且java安全管理器也允许这次exit或halt操作
- 除此之外,JNT(Java Native Interface)规范描述了用JNI(Java Invocation API)来加载或卸载java虚拟机时,Java虚拟机的退出情况
二、类加载子系统
- 类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
- ClassLoader只醋则class文件的加载,至于他是否可以运行,由ExecutionEngine决定
- 加载的类信息存放于一块称为方法区的内存空间。除了类的信息歪,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
1.类的加载过程
- 加载:
- 通过一个类的全限定名获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 链接
- 验证(Verify)
- 目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载的正确性,不会危害虚拟机自身安全
- 主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
- 准备(Prepare)
- 为类变量分配内存并且设置该类变量的默认初始值,即0值
- 这里不包含用final修饰的static,因为final在编译的时候就会分配,准备阶段会显示初始化
- 这类不会为实例变量分配初始化,类变量会分配在方法区中,而是萝莉变量是会宿舍对象一起分配到java堆中
- 解析(Resolve)
- 初始化
- 初始化阶段就是执行类构造器方法()的过程
- 此方法不许定义,是javac编译器自动手机类中的所有类变量(就是静态变量)的复制动作和静态代码块中的语句合并而来,所以无赋值 动作和静态代码块时不执行。
- 构造方法中指令按语句在源文件中出现的顺序执行
- ()不同于类的构造器(关联:构造器是虚拟机视角下的())
- 若该类具有父类,JVM会保证子类的()执行前,父类的()已经执行完毕。
- 虚拟机必须保证一个类的()方法在多线程下被同步加锁(多线程时加同一个类只会加载一次)
2.类加载器的分类
启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 使用C/C++语言实现。嵌套在JVM内部
- 他用来加载JAVA的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
- 并不继承自java.lang.ClassLoader,没有父加载器
- 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
- 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
扩展类加载器(Extension ClassLoader)
-
java语言编写,由sun.misc.Launcher$ExtClassLoader实现
-
派生于ClassLoader类
-
父类加载器为启动类加载器
-
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果是用户创建的JAR放在此目录下,也会自动扩展类加载器加载
应用程序类加载器(系统类加载器,AppClassLoader)
- java语言编写,由sun.misc,Launcher$AppClassLoader实现
- 派生于ClassLoader类
- 父类加载器为扩展类加载器
- 他负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载器是程序中默认的加载器,一般来说java应用的类都是由他来完成加载
- 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
3.类的执行过程
4.双亲委派机制和沙箱安全机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将他的class文件加载到内存生成class对象。而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式,即把请求交给父类处理,它是一种任务委派模式。
工作原理
- 如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类加载器,则进一步向上委托,一次递归,请求最终达到顶层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制。
实例1(测试使用,真实开发中不会这样去写)
新建一个名叫java.lang的包,在这个类下新建String类,String类中写入static代码块
package java.lang;
public class String {
static {
System.out.println("String程序执行");
}
}
另外新建test包,新建test类,在test类中实例化String对象
package Test;
public class test {
public static void main(String[] args) {
java.lang.String s = new java.lang.String();
System.out.println("test程序执行");
}
}
包结构如图所示:
运行结果如图所示:
可以看到,java.lang.Stirng类中的静态代码块没有执行,所以双亲委派机制就类似如此,子类收到类加载请求,但是先不加载,把这个请求委托给父类(Object类)加载器去执行,父类加载器可以完成类加载请求,因为java.lang.Stirng在java的核心类库中存在,所以父类加载器加载执行,成功返回,所以子类不在加载执行,静态代码块不执行
实例2
同实例1,在java.lang包下新建YTest类
package java.lang;
public class YTest(){
public static void main(String[] args){
System.out.pritnln("语句执行");
}
}
运行结果如下图所示:
同理受到双亲委派机制的保护,向上走到引导类加载器时,发现类库java.lang下不存在YTest类,为了防止用户自定义修改,所以报错抛出异常,也称为沙箱安全机制。
5.其他
- 在JVM中表示两个class对象是否为同一个类存在的两个必要条件
- 类的加载类名必须完全一致
- 加载这这个类的CkassLoader(指ClassLoader实例对象)必须相同
换句话说,在jvm中,及时这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但是只要加载他们的ClassLoader实例对象不同,那么这两个类对象也不同
- 对类加载器的引用
JVM必须知道一个类型是由启动类加载器加载的还是用户类加载器加载的,如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的
- 类的主动使用和被动使用-
-
主动使用
-
-
创建类的实例
-
访问某个类或接口的静态变量,或者对该静态变量赋值
-
调用类的静态方法
-
反射(比如:Class.forName(“com.at.Test”))
-
初始化一个类的子类
-
Java虚拟机启动时被标明为启动类的类
-
JDK7开始提供的动态语言支持:
java.lang.invoke.MethodHandle实例的解析结果
REF_getStatic、REF_putStatic、REF_invoeStatic句柄对应的类没有初始化,则初始化
-
-
除了以上七中情况,其他使用java类的方式都被看做是对类的被动使用,都不会导致类的初始化
未完待续