深入理解JAVA虚拟机读书笔记(16)

这一节内容是对深入理解Java虚拟机的第7、8章和部分第6章总结。

Class字节码内容



JVM运行时数据区内容

PC、虚拟机栈、本地方法栈、堆、方法区
PC:
  即程序计数器,是一块很小的内存空间,存储了下一条西药执行的字节码指令的地址。每个线程的PC记录了当前线程要执行的指令,每个线程都有自己的PC,如果执行的是本地方法,则PC为空。

虚拟机栈
  此栈中的元素叫做栈帧,线程在调用Java方法时,会为每一个方法创建一个栈帧,栈帧中存放的是局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈生命周期同线程。

栈帧——局部变量表
  每一个栈帧有一个局部变量表,局部变量的数据在编译期就确认了,存储到了方法的Code属性中

栈帧——操作数栈
  JVM地产字节码指令集是基于栈类型,所有的操作码都是对操作数栈上的数据进行操作,对于一个方法调用,JVM会建立一个操作数栈,以供计算,栈的深度在编译成class中已经确定,其深度属性存储在方法的Code属性中

栈帧——动态链接
  每个栈帧内部包含了一个指向运行时常量池的引用来支持当前的代码实现动态链接

本地方法栈
  线程私有,功能和虚拟机栈相似,存储线程调用本地方法时,本地方法的局部变量、操作栈等信息。


  线程共享,在虚拟机启动时创建,用了存放对象实力,垃圾回收器主要管理的区域。

方法区
  线程共享,用于存储被虚拟机加载的类信息、常量、静态变量等数据。



类加载整个生命周期:

  加载、连接(验证、准备、解析)、初始化、使用、卸载。

类加载之加载:

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

类加载之验证:

   确保Class文件的字节流中包含的信息符合当前虚拟机邀请,且不会危害虚拟机自身安全。大致上分4个阶段进行验证:文件格式验证、元数据验证、字节码验证和符合引用验证。
  文件格式验证:例如验证文件是否是魔数0xCAFFBABE开头、主次版本号是否在此JVM处理范围内、常量池中是否有不被支持的常量类型、是否存在指向不存在的常量或不符合类型常量的索引、CONSTANT_Utf8_info类型常量是否有不符合UTF8编码等
  元数据验证:对字节码信息进行语义分析,保证不存在不符合Java语言规范的元数据信息,如该类是否继承了不被允许继承的final修饰的类、继承抽象类是否实现了其他所有抽象方法等。
  字节码验证:对类的方法体进行校验。例如校验操作数栈的数据类型和指令代码序列能配合上,如操作栈放了int类型数据,使用却按照long类型加载到本地变量表中
  符合引用验证:将符号引用转化为直接引用是进行验证,如通过字符串描述的全限定名是否能找到对应的类、符号引用中的类、字段、方法的访问性是否可被当前类访问等。

类加载之准备:

   准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。注意:这里说的内存分配指的是被static修饰的变量,并不包括实例变量,实例变量是在对象实例化时随着对象一起分配在java堆中。

类加载之解析:

  解析是虚拟机将常量池内的符号引用替换为直接引用的过程。
  符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量。例如之前Class文件中CONSTANT_CLASS_inf代表的是Class的符号引用。
  直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

类加载之初始化:

  初始化是真正开始执行类中定义的JAVA程序代码(字节码)。初始化阶段是执行类构造器 <clinit >()方法的过程。
  <clinit>()方法:是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。收集按照顺序收集,所有静态语句块中只能访问到定义在静态语句块之前的变量。虚拟机会保证子类的clinit执行前,其父类的clinit执行完毕,所有虚拟机中第一个clinit是Object类。因此父类中定义的静态变量要优先于子类的静态变量进行赋值操作。当然,如果接口和类中没有静态的东西,虚拟机可以不为这个类生成clinit方法,同时一个类只会加载一次clinit



类加载器/双亲委派机制
类加载器

  类加载器是在类加载的阶段实现的,是加载中三步的第一步“通过一个类的全限定名来获取定义此类的二进制字节流”的实现动作。一个类如果是不同类加载器加载的,那么他们是不相同的。

public class ClassLoaderTest {  
    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 obj=myLoader.loadClass("com.tong.test.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        Object obj2 = new ClassLoaderTest();
        System.out.println(obj2.getClass());
        System.out.println(obj instanceof com.tong.test.ClassLoaderTest);
        System.out.println(obj2 instanceof com.tong.test.ClassLoaderTest);
    }
}

  输出结果:

class com.tong.test.ClassLoaderTest
class com.tong.test.ClassLoaderTest
false
true

  

双亲委派机制

  JVM角度讲,类加载器分为两种:1.启动类加载器(C++实现)2.其他类加载器,Java开发来讲,可细分如下:
  一.启动类加载器Bootstrap ClassLoader:这个类负责将<JAVA_HOME>\lib目录中类库加载到虚拟机内存中,启动类加载器无法被JAVA程序直接引用
   二.扩展类加载器Extension ClassLoader:这个类加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中类库
   三.应用程序类加载器Application ClassLoader:这个类加载器由sun.misc.Launcher$AppClassLoader实现,这个类是类加载器ClassLoader中getSystemClassLoader()返回的。负责加载用户类路径下指定的类库

类加载器关系


  

双亲委派工作过程

  如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是这样,因此所有的加载请求都会到启动类加载器Bootstrap ClassLoader中,只有父类加载器反馈自己无法加载这个请求(它搜索范围中没有找到需要的类)时,子加载器才会尝试自己去加载。
  具体流程:对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:
    1. 委托父类加载器帮忙加载;
    2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
    3. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
    4. 若加载成功,返回 对应的Class 对象;若失败,抛出异常“ClassNotFoundException”。
  请注意:
    双亲委派模型中的”双亲”并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。上面的步骤中,有两个角色:
  1. 父类加载器(parent classloader):它可以替子加载器尝试加载类
  2. 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;换句话说,就是子类加载器不能接触到引导类加载器,引导类加载器对其他类加载器而言是透明的。
  

个人总结:

  对于自定义类来说,JVM加载这个类的时候是调用launcher.getClassLoader() ,返回的是AppClassLoader应用类加载器实例,然后调用的是loadClass方法,这个方法具体步骤是
  1.首先从方法区(对应加载器控制的那块区域)中查找是否加载了此类。如果有则直接返回这个类的Class实例 。
  2.如果没有加载,判断此加载器有没有父类,如果有父类,调用父类的getClassLoader去加载(递归),而AppClassLoader对应的父类是ExtClassLoader扩展类加载器,首先查询:findLoadedClass,如果这个类加载器找到就返回,没有找到就调用findBootstrapClassOrNull方法(因为ExtClassLoader父类是null),去BootstrapClassLoader启动器中查找有没有被加载过,如果没有,那么ExtClassLoader会调用findClass自己去尝试加载,如果加载不了,返回null,那AppClassLoader自己调用findClass去加载,如果自己也加载不了,就报错ClassNotFoundException。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值