JVM类加载

标签:cas   应用程序   产生   检查   位置   指针   有一个   技术   根据   

JVM类加载

1. Java对象的创建过程

类加载检查 ===> 分配内存 ===> 初始化零值 ===> 设置对象头 ===> 执行init方法

1.1 类加载检查

虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须执行相应的类加载过程。

1.2 分配内存

在类加载检查通过后,虚拟机将为新生对象分配内存。

对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。分配方式包括指针碰撞空闲列表选择哪种方式取决于Java堆是否规整,Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

  • 指针碰撞:适用于堆内存规整(没有内存碎片)的情形。原理:用过的内存整理到一边,未使用的内存放在另一边,中间有一个分界值指针,只需要向着未使用过的内存方向将该指针移动对象内存大小位置即可。采用的GC收集器有Serial和ParNew(因为使用标记-整理,不存在内存碎片)。
  • 空闲列表:适用于堆内存不规整的情形。原理:虚拟机维护一个列表,该列表中记录哪些内存块可用,在分配的时候,找一块足够大的内存块来划分给对象实例,最后更新列表记录。采用GC收集器为CMS(因为采用标记-清除算法,堆内存不规整)。

内存分配的并发问题,虚拟机采用两种方式来保证线程安全。

  • CAS+失败重试:CAS是乐观锁的一种实现方式。所谓乐观锁就是每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
  • TLAB:为每一个线程先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中剩余内存或TLAB内存用尽时,再采用CAS进行内存分配。
1.3 初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),保证对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

1.4 设置对象头

初始化零值之后需要对对象头进行必要的设置。

1.5 执行init方法

上面工作完成后,在虚拟机的视角,新的对象已经产生。从Java程序的视角,执行new指令后就会执行init方法,把对象按照程序员的医院进行初始化,从而得到一个真正可用的对象。

2. 对象访问定位的方式

①使用句柄:在Java堆中创建句柄池,句柄包含对象实例数据和对象类型数据,本地变量表中的reference存储对象的句柄地址。

②直接指针:本地变量表中存储的直接就是对象的地址。

直接指针速度快,而使用句柄的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

3. Java中Class文件字节码结构

image-20200811233007896

4. 类加载过程和类的生命周期

类加载过程:加载、连接、初始化

类的生命周期:加载、连接、初始化、使用、卸载

  • 加载:通过全类名获取定义此类的字节流;将字节流所代表的静态存储结构转换为方法去的运行时数据结构;在堆内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口。
  • 连接:包括三步:验证、准备、初始化。①验证:验证文件格式、元数据、字节码符号引用;②准备:为类的静态变量分配内存,并将其初始化默认值;③解析:把类中的符号引用转换为直接引用。
  • 初始化:维蕾德静态变量赋予正确的初始值。
  • 使用:new出对象在程序中使用。
  • 卸载:执行垃圾回收。

5. 类加载器

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

JVM中内置了三个重要的ClassLoader,除了BootstrapClassLoader,其它类加载器均由Java实现且全部继承自java.lang.ClassLoader。

  • BootstrapClassLoader(启动类加载器):最顶层的加载器,由C++实现,负责加载Java核心类库,即%JAVA_HOME%/lib目录下的jar包和类。
  • ExtensionClassLoader(扩展类加载器):负载加载Java扩展库,即%JRE_HOME%/lib/ext目录下的Jar包和类。
  • AppClassLoader(应用程序加载器):负责加载当前拥有classpath下的所有jar包和类。

6. 双亲委派

6.1 双亲委派机制

每一个类都有一个对于它的类加载器。系统中的ClassLoader在协同工作时会默认使用双亲委派机制。

在类加载的时候,首先判断该类是否被加载,已经加载过的类无需加载会直接返回,负责会自己尝试加载。加载的时候,首先会把该请求委派给父类加载器进行处理,因此所有的请求最终都会传送到顶层的启动类加载BootstrapClassLoader加载器中。当父类加载加载器无法处理时,才会自己进行处理。当父类加载器为null时,会使用BootstrapClassLoader作为父类加载器。

22

111

6.2 双亲委派机制的作用
  • 避免类的重复加载。即使是相同的类文件,被不同的类加载器加载后产生的也是不同的两个类。
  • 保证了核心API不被篡改。
  • 保证了Java程序的稳定运行。
6.3 打破双亲委派机制的方法
  • 自定义一个类加载器,重写loadClass()方法。
  • 引入线程上下文类加载器
6.4 打破双亲委派机制的场景
6.4.1 JDBC

JDBC:使用线程上下文类加载器(Thread Context ClassLoader)

JDBC的Driver接口定义在JDK中,其实现由数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,其由BootstrapClassLoader加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootstrapClassLoader还要去加载jar包中的Driver接口的实现类。然而BootstrapClassLoader默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的类,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。

6.4.2 Tomcat

Tomcat:自定义类加载器,Tomcat的类加载器如下图。

33

每个Tomcat的WebappClassLoader加载自己目录下的class文件,不会传递给父类加载器,破坏了双亲委派机制。

Tomcat自定义了很多来加载器,可能处于以下目的:

  • 对于各个 webapp中的 classlib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况,而对于许多应用,需要有共享的lib以便不浪费资源;
  • JVM一样的安全性问题。使用单独的 ClassLoader去装载 Tomcat自身的类库,以免其他恶意或无意的破坏;
  • 热部署。Tomcat`修改文件不用重启就会自动重新装载类库。

JVM类加载

标签:cas   应用程序   产生   检查   位置   指针   有一个   技术   根据   

原文地址:https://www.cnblogs.com/chiaki/p/13488293.html

版权声明:完美者 发表于 2020-08-12 15:40:12。
转载请注明:JVM类加载 | 完美导航

暂无评论

暂无评论...