JVM指令集、类加载子系统介绍

Jvm整体架构图:

Jvm指令集架构:

指令集架构分为两种:
    1.基于栈的指令集架构(Java编译器是基于栈的指令集架构)。
    2.基于寄存器指令架构。

栈的指令架构:
    优势:跨平台、零地址指令、指令集更小、移植性高。(编译器更容易实现)
    劣势:指令多,性能下降(实现同样的功能需要更多指令)
寄存器指令架构:
    优势:性能优秀、执行效率高,指令少
    劣势:移植性差

Jvm生命周期:

跟随线程的结束或终止生命周期就结束(System.exit()方法或Runtime类的Halt方法)。

Jvm类加载子系统图:

 

类加载子系统作用:

类加载子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定文件标识(Ca Fe Ba Be)。ClassLoad只负责class文件的加载,是否可以运行由Execution Engine决定。

Jvm加载器:

1.启动类加载器(引导类加载器 Bootstrap ClassLoader):
    1) 使用C/C++语言实现,嵌套在Jvm内部。
    2) 用来加载Java核心类库。
    3) 加载扩展类和系统类,指定为它们的父类加载器2.扩展类加载器(Extension ClassLoader):
    1) Java语言编写。
    2) 派生于ClassLoader类。
    3) 从java.ext.dirs系统属性所指定目录中加载或JDK的安装目录jre/lib/ext子目录下加载。
    4) 如果用户创建的Jar放在jre/lib/ext子目录下,自动由扩展类加载器加载3.系统类加载器(App ClassLoader):
    1) Java语言编写。
    2) 派生于ClassLoader类。
    3) 父类为扩展类加载器。
    4) 负责加载环境变量classpath或系统属性java.class.path指定下的类库。
    5) Java应用类,自定义类都是由系统加载器完成加载自定义加载器的好处:
    1) 隔离加载类 2) 修改类的加载方式 3) 扩展加载源 4) 防止源码泄漏

如何自定义加载器:
    1) 通过继承抽象类java.lang.ClassLoader的方式,实现自己的类加载器。
    2) 如果没有复杂的需求,可以直接继承URLClassLoader类,可以避免编写findClass()方法及获取字节码的方式,使自定义加载器更加简洁。
/**
 * 自定义类加载器
 */
public class MyClassLoader extends ClassLoader{

 private String classPath;

 public MyClassLoader(String classPath){
 this.classPath = classPath;
 }

 @Override
 protected Class<?> findClass(String name) {
 byte[] classByte = loadClassByte(name);
 return defineClass(name, classByte, 0, classByte.length);
 }

 private byte[] loadClassByte(String name) {
 // 获取该类在文件系统中保存的格式 (即路径加文件名)
 String fileName = classPath + File.separator + name.replace(".",File.separator) +".class";

 File file = new File(fileName);
 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 try(InputStream is = new FileInputStream(file)){

 int len;
 byte[] b = new byte[1024*8];
 while ((len = is.read(b)) != -1) {
 outputStream.write(b, 0, len);
 }
 } catch (FileNotFoundException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 return outputStream.toByteArray();
 }
}
public static void main(String[] args) throws Exception {
 //路径请使用classpath外的一个class文件,并且类路径下不能包含此class,才能使用到自定义的类加载器
 MyClassLoader myClassLoader = new MyClassLoader("F:\\chenxi");
 Class clazz = myClassLoader.loadClass("com.MyClassLoading");
 System.out.println(clazz + "-" + clazz.newInstance()+ "-" +clazz.getClassLoader());
}

类加载过程:

分为三个阶段:1.加载  2.链接  3.初始化

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

     加载.class文件的方式:
         1.Jar,War格式的基础,从zip压缩包中读取
         2.动态代理技术,运行时计算生成。

链接阶段:
    1.验证:确保class文件中的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全(四种验证:1.文件格式 2.元数据 3.字节码 4.符号引用)。
            
     
    2.准备:为变量分配内存并设置该类变量的默认初始值(零值)(不包含用final修饰的static,因为final在编译时就分配了,准备阶段会显式初始化)。

    3.解析:将常量池内的符号引用转换为直接引用的过程,解析操作随着Jvm在执行空初始化后再执行(解析主要针对于:类、接口、字段、类方法、接口方法、方法类型等)。

初始化阶段:
    执行类构造器的方法<clinit>()的过程。此方法不需要定义,是Javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
    static int a = 1;
    static{
      a = 2;
    }
    构造器方法中指令按语句在源码文件中出现的顺序执行。
    <clinit>()不同于类的构造器。若该类具有父类,Jvm会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
    虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。(保证一个类只加载一次)
    <init>对应类中的构造器。

    IDEA安装jclasslib插件:





双亲委派机制

工作原理:

    1) 如果一个类加载器收到了类加载的请求,它并不会自己去加载,而是先将这个请求委托给父类的加载器去执行。

    2) 如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终达到顶层的引导类加载器(启动类加载器)。

    3) 如果父类加载器可以完成此加载任务,就成功返回,如果父类加载器无法完成此加载任务,子类加载器才会尝试自己加载

优势:
    1) 避免重复加载 
    2) 保护程序安全,防止核心Api被随意篡改(沙箱安全机制)。

发表评论

邮箱地址不会被公开。 必填项已用*标注