JAVA学习之JVM相关(2)

1. 类加载机制

首先类加载是分成:加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载 七个阶段。其中 加载、验证、准备、初始化、卸载 这五个步骤之间的顺序是固定的,解析也可以在初始化之后才开始。

在JAVA语言上,通常是根据new 关键字来创建对象的。但是从JVM的角度来看又是怎么来创建对象的?

当JVM 遇到一条new 指令的时候,就会去找这个指令的参数能不能在常量池中找到一个对应的符号引用,并检查这个符号引用代表的类有没有被加载、解析和初始化过。没有的话就进行类的加载。

下面是对应的加载过程:

1. 根据名称生成对应的二进制字节流。
2. 把这个字节流对应的静态存储结构放入方法区。
3. 内存中生成对应的 CLASS 对象,作为访问方法区数据的入口。

在加载之后会进行验证,验证分为:

1. 文件格式验证:验证文件字节流是否符合Class 文件格式的规范。
2. 元数据验证:验证语义是否正确。
3. 字节码验证:对类的方法体进行校验,确保类方法在运行时不会危害到虚拟机的安全。
4. 符号引用验证:将符号引用转化为直接引用(解释阶段发生)

当上面的验证也结束之后才会到准备阶段,这个时候对象在内存中所要占用大小就明确了,因此给对象分配内存大小。

目前内存大小的分配主要使用两种方式:

  1. 指针碰撞:一般用于如serial、ParNew、Parallel Scavenge 等带Compact 过程的收集器,因为这些收集器垃圾回收之后会有比较规整的内存空间,所以直接就在内存中将堆指针向空闲的内存移动一段与对象大小相等的距离就可以了。
    【标记-整理算法、复制算法对应的收集器都采用这种内存分配的方式】

  2. 空闲列表:一般用于如CMS这种使用标记-清理算法的收集器的时候,就会导致内存空间碎片化,因此就要有张表去记录维护内存空间信息,所以当有新的对象需要被分配内存空间的时候,就先去列表中找到一块足够大的空间划分给对象,然后再更新内存空闲表中的信息。

因为对象的创建动作是比较频繁的,所以在并发的情况下也会在内存分配的时候产生线程的安全问题,A对象被创建且分配好内存但指针位置还未改变,B对象又进来被分配到了同一块内存空间,为了避免这种情况的发生,有两种解决方案:
1. 把分配空间的动作进行同步操作。
2. 使用TLAB(本地线程分配缓冲),就是在对内存中给每个线程分配一个独立的小空间,在自己的空间上进行分配,当空间分配完才需要进行同步锁定。

对象的内存空间分配之后就要进行默认的初始化复制,这样才能保证对象实例字段不赋值也可以使用。

当到初始化阶段的时候才会真正执行类中的代码。这个时候去执行类构造器() 方法,执行的时候是隐式调用父类的类构造器的,保证在子类的类构造器执行之前完成,里面执行的语句是编译器按照代码中出现的顺序自动收集类中的所有类变量的赋值动作和静态代码块中的语 句合并产生的。
执行接口的类构造器不需要先执行父接 口的类构造器。只有当父接口中定义的变量使用时,父接口才会被初始化,另外,接口的实现类在初始化时也一样不会执行接口的类构造器。

初始化结束类的加载也就结束了。

2. 双亲委派机制

在类加载的时候,就会有双亲委派机制,简单的说,双亲委派机制就是当一个类加载器收到了类加载请求的时候,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,且每一层加载器都是如此,因此所有的加载请求最终都会被传送到启动类加载器中,只有当父加载器反馈自己无法完成加载的时候(不在其加载的范围内),才会由子加载器自己去加载。

下面就是双亲委派机制类加载的流程:
双亲委派机制类加载流程
加载的时候存在两个原则:

  1. 自底向上检查类是否已经被加载
  2. 自上而下尝试类的加载

使用双亲委派机制的好处:

  1. 避免重复加载相同的字节码文件
  2. 确保使用加载的是同一个类,如果没有双亲委派机制,那每个类加载器都去加载自己的类,且要是用户也编写了一个Object类,都加载进来系统就会出现多个不同的 Object 类。

3. 类的主动引用和被动引用

先来解释一下什么是主动引用,什么是被动引用。

主动引用可以理解为,肯定会对类进行初始化的引用;被动引用就是引用类的方式不会出发初始化。
被动引用有且只有以下5种方式:

  1. new 对象
  2. 当前类的所有父类
  3. main 方法所在的类
  4. 反射调用的时候
  5. 引用静态变量和静态方法(被final关键字修饰的静态变量是存在于常量池中的,访问并不会出发初始化)

被动引用:
1. 通过子类引用父类的静态字段,子类不会被初始化
2. 通过数组来进行引用
3. 引用常量池中的数据

4. 对象的内存布局

说了对象的加载,加载进来的对象在内存中的存储可以分成三个区域:

  1. 对象头
    对象头中又存储了自身的运行时数据、类型指针用于明确是哪个类的实例。
  2. 实例数据:存储的是对象真正的有效信息。
  3. 对齐填充:只起到占位符的作用。因为虚拟机中规定对象的大小必须是8字节的整数倍,对象头就是8字节的整数倍大小,因此当实例数据不满足的时候则进行填充来满足要求。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值