您的位置:首页 > 健康 > 美食 > JVM 调优篇1 类的加载器与加载过程

JVM 调优篇1 类的加载器与加载过程

2024/12/24 3:41:09 来源:https://blog.csdn.net/u011066470/article/details/141981083  浏览:    关键词:JVM 调优篇1 类的加载器与加载过程

一  基本知识

1.1 JIT&AOT

JIT:  Just Time compilation  即时编译器

在程序运行时将字节码或中间表示转换为机器代码。

AOT: Ahead of  Tmie  Compilation : 预编译

在程序运行之前将高级语言代码完全编译成机器代码。

1.2 字面量和符号引用*

字面量:在java中,字面量是指在代码中直接出现具体的值,如 String str=“123”;

符号引用:是对某个方法、字段或类的引用,这个引用指向元数据的引用。

public class Jia
{int field;void method(){}public static void main(String[] args) {Jia j = new Jia();j.field = 33;//字段引用j.method();//方法引用}
}

field是对类的字段的符号引用,method()是对类的方法的符号引用。这些引用指向的是元数据,而不是具体的值。

二  类的加载

2.1 jvm的类加载器*

ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例。然后交给Java虚拟机进行链接、初始化等操作。

类加载器分类:

1)启动类加载器(引导类加载器,Bootstrap ClassLoader)

这个类加载使用C/C++语言实现的,嵌套在JVM内部。它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类。

并不继承自java.lang.ClassLoader,没有父加载器。

出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。加载扩展类和应用程序类加载器,并指定为它们的父类加载器。

 2)扩展类加载器(Extension ClassLoader)

Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。

继承于ClassLoader类;父类加载器为启动类加载器

从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

3)应用程序类加载器(系统类加载器,AppClassLoader)

java语言编写,由sun.misc.Launcher$AppClassLoader实现

继承于ClassLoader类;父类加载器为扩展类加载器

它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库 

应用程序中的类加载器默认是系统类加载器。它是用户自定义类加载器的默认父加载器

通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器

4)用户自定义类加载器

自定义类加载器体现Java语言强大生命力和巨大魅力的关键因素之一便是:Java开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的JAR包,也可以是网络上的远程资源。

所有用户自定义类加载器通常需要继承于抽象类java.lang.ClassLoader。

通过类加载器可以实现非常绝妙的插件机制

自定义加载器能够实现应用隔离

5)获取类的加载器

2.2 Class.forName与Class.getClassLoader()的区别与联系?

Class.forName():是一个静态方法,最常用的是Class.forName(String className);根据传入类的全路径包名,返回一个 Class 对象。该方法在将 Class文件加载到内存的同时,会执行类的初始化。

如: Class.forName("com.atguigu.java.HelloWorld");

ClassLoader.loadClass():这是一个实例方法,需要一个 ClassLoader 对象来调用该方法。该方法将 Class 文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个 ClassLoader 对象,所以可以根据需要指定使用哪个类加载器。

如:ClassLoader cl=.......;    cl.loadClass("com.atguigu.java.HelloWorld");

String.class.getClassLoader().loadClass() 与Class.forName()的区别_.class.getclassloader();会调用loadclass方法吗-CSDN博客

2.3 类的双亲委托策略

类加载器用来把类加载到Java虚拟机中,类的加载过程采用双亲委派机制,这种机制能更好地保证Java平台的安全。

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。

优点:

避免类的重复加载,确保一个类的全局唯一性

Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

保护程序安全,防止核心API被随意篡改

缺点:

检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

2.4 编译后不包含<clinit>方法

java编译器在以下情况,经过编译字节码后,文件不包含<clinit>方法

1)一个类中并没有声明任何的类变量,也没有静态代码块时。

2)一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时

3)一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式。

如截图所示:

2.5 初始化阶段与链接-准备阶段

 static+final修饰的成员变量,为全局常量;给全局常量的赋值为字面量或常量,不涉及到方法和构造器的调用,则是链接阶段的准备环节;否则是初始化阶段赋值。 

public class TestM

{

    public static int a = 1;//初始化阶段

    public static final int INT_CONSTANT = 10;//链接阶段

    public static Integer INTEGER_CONSTANT1 = Integer.valueOf(100);//初始化阶段

    public static final Integer INTEGER_CONSTANT2 = Integer.valueOf(1000);//初始化阶段

    public static final String s0 = "hello";//链接阶段

    public static final String s1 = new String("hello");//初始化阶段

    public static String  s2 = "hello";//初始化阶段

    public static final int NUM = new Random().nextInt(10);//初始化阶段

    static int a = 10; //初始化阶段

    static final  int b = a ;//初始化阶段

    //static+final修饰的成员变量,为全局常量;给全局常量的赋值为字面量或常量,不涉及到方法和构造器的调用,

    // 则是链接阶段的准备环节;否则是初始化阶段赋值。

}

三 类的加载过程

3.1 概述

按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包括如下7个阶段:在Java中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载。

3.2 加载具体阶段*

1加载:

所谓装载,简而言之就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型——类模板对象。

2.链接:

1)验证:当类加载到系统后,就开始链接操作,验证是链接操作的第一步。它的目的是保证加载的字节码是合法、合理并符合规范的。验证的内容则涵盖了类数据信息的格式验证、语义检查、字节码验证,以及符号引用验证等。

2)准备:

简言之,为类的静态变量分配内存,并将其初始化为默认值

在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置默认初始值。

注意:Java并不支持boolean类型,对于boolean类型,内部实现是int,由于int的默认值是0,故对应的boolean的默认值就是false。

3)解析:

将类、接口、字段和方法的符号引用转为直接引用;也就是得到类、字段、方法在内存中的指针或者偏移量。

3.初始化执行类变量赋值和静态代码块

又父及子,静态先行。在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的<clinit>总是在子类<clinit>之前被调用。也就是说,父类的static块优先级高于子类。 

4.使用阶段:

任何一个类型在使用之前都必须经历过完整的加载、链接和初始化3个类加载步骤。一旦一个类型成功经历过这3个步骤之后,开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用new关键字为其创建对象实例。

5.卸载

当对象不再被使用时,java虚拟机的垃圾收集器将会回收堆中的对象,方法区中不再被使用的Class也要被卸载,否则方法区(Sun HotSpot永久代)会内存溢出。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com