- 浏览: 565261 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (267)
- 随笔 (4)
- Spring (13)
- Java (61)
- HTTP (3)
- Windows (1)
- CI(Continuous Integration) (3)
- Dozer (1)
- Apache (11)
- DB (7)
- Architecture (41)
- Design Patterns (11)
- Test (5)
- Agile (1)
- ORM (3)
- PMP (2)
- ESB (2)
- Maven (5)
- IDE (1)
- Camel (1)
- Webservice (3)
- MySQL (6)
- CentOS (14)
- Linux (19)
- BI (3)
- RPC (2)
- Cluster (9)
- NoSQL (7)
- Oracle (25)
- Loadbalance (7)
- Web (5)
- tomcat (1)
- freemarker (1)
- 制造 (0)
最新评论
-
panamera:
如果设置了连接需要密码,Dynamic Broker-Clus ...
ActiveMQ 集群配置 -
panamera:
请问你的最后一种模式Broker-C节点是不是应该也要修改持久 ...
ActiveMQ 集群配置 -
maosheng:
longshao_feng 写道楼主使用 文件共享 模式的ma ...
ActiveMQ 集群配置 -
longshao_feng:
楼主使用 文件共享 模式的master-slave,produ ...
ActiveMQ 集群配置 -
tanglanwen:
感触很深,必定谨记!
少走弯路的十条忠告
Class Loader 类加载器:
类加载器负责加载 JAVA 类的字节代码到JAVA 虚拟机中,可以根据指定的类名(如java.lang.Object)来装载class文件的内容到Runtime data area中的method area(方法区域)。JAVA程序员可以extends java.lang.ClassLoader类来写自己的Class loader。
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载入JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。
关于唯一标识符:在Java中,一个类用其全限定类名(包括包名和类名)作为标识,但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
命名空间概念:
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
特别注意:
在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
由子加载器加载的类能看见父加载器的类,由父亲加载器加载的类不能看见子加载器加载的类。
每个类只能被加载一次,其实这样说是不够准确的,怎样才算是准确的呢?那就涉及到命名空间的概念了!只有在相同的命名空间中,每个类才只能被加载一次,反过来说就是一个类在不同的命名空间中是可以被加载多次的,而被加载多次的Class对象是互相独立的。
类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机默认采用的是双亲委派模式。
双亲委派模型工作过程:
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派机制工作过程:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。
优点:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在tools.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
双亲委派模型意义就是:
1、系统类防止内存中出现多份同样的字节码
2、保证Java程序安全稳定运行
站在JVM的角度讲,主要有两种类型加载器:启动类加载器和所有其它的类加载器。
启动类加载器是JVM实现的一部分,使用C++语言实现,其它类加载器都由java语言实现 ,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader
(1) Bootstrap ClassLoader 启动类加载器
这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME$中jre\lib\rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。启动类加载器无法被JAVA程序直接引用。
(2) Extension ClassLoader 扩展类加载器
扩展类加载器负责加载<JAVA_HOME>\lib\ext目录中或者java.ext.dirs系统变量所指定的所有类库,开发者可以直接使用扩展类加载器。
(3)Application ClassLoader 应用程序类加载器
JVM用此classloader来加载用户类路径 (Classpath)上所指定的类库,包含指定的jar包以及目录,该加载器有时也称为系统类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
(4) User-Defined ClassLoader 用户自定义类加载器
User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。
类加载器之类加载过程:
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。类的加载、连接、与初始化过程都是在程序运行期间完成的(类从磁盘加载到内存中经历的三个阶段)。
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括 7 个阶段:加载-->验证-->【准备-->解析-->初始化】-->使用-->卸载,而验证、准备、解析 3 个阶段统称为连接。
加载、验证、准备、初始化 和 卸载这 5 个阶段的顺序是固定确定的,类的加载过程必须按照这种顺序开始,而解析阶段则不一定:它在某些情况下可以在初始化后再开始,这是为了支持 Java 语言的运行时绑定【也就是java的动态绑定/晚期绑定】。
1. 加载
加载阶段指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
在加载阶段,虚拟机需要完成以下三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
Class对象是存放在堆区的,不是方法区。类的元数据才是存在方法区的。【元数据并不是类的Class对象。Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的】
总结:
加载阶段简单来说就是:
.class文件(二进制数据)——>读取到内存——>数据放进方法区——>堆中创建对应Class对象——>并提供访问方法区的接口
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个 java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
加载.calss文件的方式:
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用不同的类加载器,可以从不同来源加载类的二进制数据,二进制数据通常有如下几种来源:
(1)从本地系统中直接加载
(2)通过网络下载.class文件
(3)从zip,jar等归档文件中加载.class文件
(4)从专用数据库中提取.class文件
(5)将java源文件动态编译为.class文件
2. 连接
连接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
(1)验证:确保被导入类的正确性
(文件格式验证、数据验证、字节码验证、符号引用验证)
验证是连接阶段的第一阶段,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
(2)准备:为类变量分配内存,并将其初始化为默认值
(准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
首先是这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。)
当完成字节码文件的校验之后,JVM 便会开始为 类变量 分配内存并初始化。准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在 方法区 中分配。
这里需要注意两个关键点,即内存分配的对象以及初始化的类型。
内存分配的对象:要明白首先要知道Java 中的变量有类变量以及类成员变量两种类型,类变量指的是被 static 修饰的变量,而其他所有类型的变量都属于类成员变量。在准备阶段,JVM 只会为类变量分配内存,而不会为类成员变量分配内存。类成员变量的内存分配需要等到初始化阶段才开始。
例如下面的代码在准备阶段,只会为 LeiBianLiang属性分配内存,而不会为 ChenYuanBL属性分配内存:
public static int LeiBianLiang = 666;
public String ChenYuanBL = "jvm";
初始化的类型:在准备阶段,JVM 会为类变量分配内存,并为其初始化(JVM 只会为类变量分配内存,而不会为类成员变量分配内存,类成员变量自然这个时候也不能被初始化)。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的默认值,而不是用户代码里初始化的值。
例如下面的代码在准备阶段之后,LeiBianLiang 的值将是 0,而不是 666。
public static int LeiBianLiang = 666;
但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。例如下面的代码在准备阶段之后,ChangLiang的值将是 666,而不再会是 0。
public static final int ChangLiang = 666;
之所以 static final 会直接被复制,而 static 变量会被赋予java语言类型的默认值。两个语句的区别是一个有 final 关键字修饰,另外一个没有。而 final 关键字在 Java 中代表不可改变的意思,意思就是说 ChangLiang的值一旦赋值就不会在改变了。既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值。而没有被 final 修饰的类变量,其可能在初始化阶段或者运行阶段发生变化,所以就没有必要在准备阶段对它赋予用户想要的值。
(3)解析:把类中的符号引用转换为直接引用
(解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的。
解析动作主要针对 类或接口、字段、类方法、接口方法四类符号引用进行。)
当通过准备阶段之后,进入解析阶段。解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
3. 初始化
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。
到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。
Java程序对类的使用方式可分为两种:主动使用与被动使用。一般来说只有当对类的首次主动使用的时候才会导致类的初始化,所以主动使用又叫做类加载过程中“初始化”开始的时机。类的主动使用包括以下六种:
(1)、 创建类的实例,也就是new的方式
(2)、 访问某个类或接口的静态变量,或者对该静态变量赋值(凡是被final修饰,其实更准确的说是在编译器把结果放入常量池的静态字段除外)
package com.mason.techtest;
class Test{
static {
System.out.println("static 静态代码块");
}
// public static final String str= UUID.randomUUID().toString();
public static final double str=Math.random(); //编译期不确定
}
public class FinalUUidTest {
public static void main(String[] args) {
System.out.println(Test.str);
}
}
运行结果:
static 静态代码块
0.81822509078862
解析:
其实final不是重点,重点是编译器把结果放入常量池!当一个常量的值并非编译期可以确定的,那么这个值就不会被放到调用类的常量池中,这时在程序运行时,会导致主动使用这个常量所在的类,所以这个类会被初始化。
package com.mason.techtest;
class YeYe{
static {
System.out.println("YeYe静态代码块");
}
public YeYe()
{
System.out.println("YeYe构造函数");
}
}
class Father extends YeYe{
public final static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
public Father()
{
System.out.println("Father构造函数");
}
}
class Son extends Father{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
public Son()
{
System.out.println("Son构造函数");
}
}
public class InitialValues {
public static void main(String[] args) {
System.out.println(Son.strFather);
}
}
运行结果:
HelloJVM_Father
解析:
唯一的特点就在于final static !是的Son.strFather所对应的变量是final static修饰的
(3)、 调用类的静态方法
(4)、 反射(如 Class.forName(“com.lms.ts”))
(5)、 初始化某个类的子类,则其父类也会被初始化
(6)、 Java虚拟机启动时被标明为启动类的类( JavaTest ),还有就是Main方法的类会首先被初始化
注意:对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块),这句话在继承、多态中最为明显。
package com.mason.techtest;
class YeYe{
static {
System.out.println("YeYe静态代码块");
}
public YeYe()
{
System.out.println("YeYe构造函数");
}
}
class Father extends YeYe{
public static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
public Father()
{
System.out.println("Father构造函数");
}
}
class Son extends Father{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
public Son()
{
System.out.println("Son构造函数");
}
}
public class InitialValues {
public static void main(String[] args) {
System.out.println(Son.strFather);
}
}
运行结果:
YeYe静态代码块
Father静态代码块
HelloJVM_Father
解析:
Son.strFather中的静态字段是属于父类Father的对吧,也就是说直接定义这个字段的类是父类Father,所以在执行 System.out.println(Son.strFather); 这句代码的时候会去初始化Father类而不是子类Son
如果是如下:
public static void main(String[] args) {
System.out.println(Son.strFather);
}
运行结果:
YeYe静态代码块
Father静态代码块
Son静态代码块
HelloJVM_Son
Java代码编译成字节码之后,最开始是没有构造方法的概念的,只有 类初始化方法 和 对象初始化方法
类初始化方法:编译器会按照其出现顺序,收集:类变量(static变量)的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。
对象初始化方法:编译器会按照其出现顺序,收集:成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法,值得特别注意的是,如果没有监测或者收集到构造函数的代码,则将不会执行对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。
package com.mason.techtest;
class Father {
String str="father非static变量";
static String strSon = "father static变量";
static {
System.out.println("Father静态代码块");
}
{
System.out.println("Father普通代码块");
}
public Father() {
System.out.println("Father构造函数");
}
}
class Son extends Father {
String str="son非static变量";
static String strSon = "son static变量";
static {
System.out.println("Son静态代码块");
}
{
System.out.println("Son普通代码块");
}
public Son() {
System.out.println("Son构造函数");
}
}
public class ClassAndObjectLnitialize {
public static void main(String[] args) {
Son son=new Son();
System.out.println("输出的打印语句");
}
}
运行结果:
Father静态代码块
Son静态代码块
Father普通代码块
Father构造函数
Son普通代码块
Son构造函数
输出的打印语句
类的加载顺序:
(1).父类的静态代码块
(2).子类的静态代码块
(3).父类成员变量初始化
(4).父类普通代码块
(5).父类构造函数
(6).子类成员变量初始化
(7).子类普通代码块
(8).子类构造函数
4. 使用
当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。
5. 卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。
6.结束生命周期
在如下几种情况下,Java虚拟机将结束生命周期
(1)、 执行了 System.exit()方法
(2)、 程序正常执行结束
(3)、 程序在执行过程中遇到了异常或错误而异常终止
(4)、 由于操作系统出现错误而导致Java虚拟机进程终止
JVM 加载类的阶段:
第一个阶段是找到.class文件并把这个文件包含的字节码加载到内存中。
第二个阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析及相应的内存分配和最后的符号表的链接。
第三个阶段是类中静态属性和初始化赋值,以及静态块的执行等。
加载类的三种方式:
1、静态加载,也就是通过new关键字来创建实例对象。
2、动态加载,也就是通过Class.forName()方法动态加载(反射加载类型),然后调用类的newInstance()方法实例化对象。
3、动态加载,通过类加载器的loadClass()方法来加载类,然后调用类的newInstance()方法实例化对象
三种方式的区别:
1、第一种和第二种方式使用的类加载器是相同的,都是当前类加载器。(this.getClass.getClassLoader)。而3由用户指定类加载器。
2、如果需要在当前类路径以外寻找类,则只能采用第3种方式。第3种方式加载的类与当前类分属不同的命名空间。
3、第一种是静态加载,而第二、三种是动态加载。
类加载器负责加载 JAVA 类的字节代码到JAVA 虚拟机中,可以根据指定的类名(如java.lang.Object)来装载class文件的内容到Runtime data area中的method area(方法区域)。JAVA程序员可以extends java.lang.ClassLoader类来写自己的Class loader。
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载入JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。
关于唯一标识符:在Java中,一个类用其全限定类名(包括包名和类名)作为标识,但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
命名空间概念:
每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
特别注意:
在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
由子加载器加载的类能看见父加载器的类,由父亲加载器加载的类不能看见子加载器加载的类。
每个类只能被加载一次,其实这样说是不够准确的,怎样才算是准确的呢?那就涉及到命名空间的概念了!只有在相同的命名空间中,每个类才只能被加载一次,反过来说就是一个类在不同的命名空间中是可以被加载多次的,而被加载多次的Class对象是互相独立的。
类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机默认采用的是双亲委派模式。
双亲委派模型工作过程:
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派机制工作过程:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。
优点:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在tools.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
双亲委派模型意义就是:
1、系统类防止内存中出现多份同样的字节码
2、保证Java程序安全稳定运行
站在JVM的角度讲,主要有两种类型加载器:启动类加载器和所有其它的类加载器。
启动类加载器是JVM实现的一部分,使用C++语言实现,其它类加载器都由java语言实现 ,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader
(1) Bootstrap ClassLoader 启动类加载器
这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME$中jre\lib\rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。启动类加载器无法被JAVA程序直接引用。
(2) Extension ClassLoader 扩展类加载器
扩展类加载器负责加载<JAVA_HOME>\lib\ext目录中或者java.ext.dirs系统变量所指定的所有类库,开发者可以直接使用扩展类加载器。
(3)Application ClassLoader 应用程序类加载器
JVM用此classloader来加载用户类路径 (Classpath)上所指定的类库,包含指定的jar包以及目录,该加载器有时也称为系统类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
(4) User-Defined ClassLoader 用户自定义类加载器
User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。
类加载器之类加载过程:
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。类的加载、连接、与初始化过程都是在程序运行期间完成的(类从磁盘加载到内存中经历的三个阶段)。
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括 7 个阶段:加载-->验证-->【准备-->解析-->初始化】-->使用-->卸载,而验证、准备、解析 3 个阶段统称为连接。
加载、验证、准备、初始化 和 卸载这 5 个阶段的顺序是固定确定的,类的加载过程必须按照这种顺序开始,而解析阶段则不一定:它在某些情况下可以在初始化后再开始,这是为了支持 Java 语言的运行时绑定【也就是java的动态绑定/晚期绑定】。
1. 加载
加载阶段指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
在加载阶段,虚拟机需要完成以下三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构化为方法区的运行时数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
Class对象是存放在堆区的,不是方法区。类的元数据才是存在方法区的。【元数据并不是类的Class对象。Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的】
总结:
加载阶段简单来说就是:
.class文件(二进制数据)——>读取到内存——>数据放进方法区——>堆中创建对应Class对象——>并提供访问方法区的接口
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个 java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
加载.calss文件的方式:
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用不同的类加载器,可以从不同来源加载类的二进制数据,二进制数据通常有如下几种来源:
(1)从本地系统中直接加载
(2)通过网络下载.class文件
(3)从zip,jar等归档文件中加载.class文件
(4)从专用数据库中提取.class文件
(5)将java源文件动态编译为.class文件
2. 连接
连接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
(1)验证:确保被导入类的正确性
(文件格式验证、数据验证、字节码验证、符号引用验证)
验证是连接阶段的第一阶段,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
(2)准备:为类变量分配内存,并将其初始化为默认值
(准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
首先是这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。)
当完成字节码文件的校验之后,JVM 便会开始为 类变量 分配内存并初始化。准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在 方法区 中分配。
这里需要注意两个关键点,即内存分配的对象以及初始化的类型。
内存分配的对象:要明白首先要知道Java 中的变量有类变量以及类成员变量两种类型,类变量指的是被 static 修饰的变量,而其他所有类型的变量都属于类成员变量。在准备阶段,JVM 只会为类变量分配内存,而不会为类成员变量分配内存。类成员变量的内存分配需要等到初始化阶段才开始。
例如下面的代码在准备阶段,只会为 LeiBianLiang属性分配内存,而不会为 ChenYuanBL属性分配内存:
public static int LeiBianLiang = 666;
public String ChenYuanBL = "jvm";
初始化的类型:在准备阶段,JVM 会为类变量分配内存,并为其初始化(JVM 只会为类变量分配内存,而不会为类成员变量分配内存,类成员变量自然这个时候也不能被初始化)。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的默认值,而不是用户代码里初始化的值。
例如下面的代码在准备阶段之后,LeiBianLiang 的值将是 0,而不是 666。
public static int LeiBianLiang = 666;
但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。例如下面的代码在准备阶段之后,ChangLiang的值将是 666,而不再会是 0。
public static final int ChangLiang = 666;
之所以 static final 会直接被复制,而 static 变量会被赋予java语言类型的默认值。两个语句的区别是一个有 final 关键字修饰,另外一个没有。而 final 关键字在 Java 中代表不可改变的意思,意思就是说 ChangLiang的值一旦赋值就不会在改变了。既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值。而没有被 final 修饰的类变量,其可能在初始化阶段或者运行阶段发生变化,所以就没有必要在准备阶段对它赋予用户想要的值。
(3)解析:把类中的符号引用转换为直接引用
(解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的。
解析动作主要针对 类或接口、字段、类方法、接口方法四类符号引用进行。)
当通过准备阶段之后,进入解析阶段。解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
3. 初始化
初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。
到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。
Java程序对类的使用方式可分为两种:主动使用与被动使用。一般来说只有当对类的首次主动使用的时候才会导致类的初始化,所以主动使用又叫做类加载过程中“初始化”开始的时机。类的主动使用包括以下六种:
(1)、 创建类的实例,也就是new的方式
(2)、 访问某个类或接口的静态变量,或者对该静态变量赋值(凡是被final修饰,其实更准确的说是在编译器把结果放入常量池的静态字段除外)
package com.mason.techtest;
class Test{
static {
System.out.println("static 静态代码块");
}
// public static final String str= UUID.randomUUID().toString();
public static final double str=Math.random(); //编译期不确定
}
public class FinalUUidTest {
public static void main(String[] args) {
System.out.println(Test.str);
}
}
运行结果:
static 静态代码块
0.81822509078862
解析:
其实final不是重点,重点是编译器把结果放入常量池!当一个常量的值并非编译期可以确定的,那么这个值就不会被放到调用类的常量池中,这时在程序运行时,会导致主动使用这个常量所在的类,所以这个类会被初始化。
package com.mason.techtest;
class YeYe{
static {
System.out.println("YeYe静态代码块");
}
public YeYe()
{
System.out.println("YeYe构造函数");
}
}
class Father extends YeYe{
public final static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
public Father()
{
System.out.println("Father构造函数");
}
}
class Son extends Father{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
public Son()
{
System.out.println("Son构造函数");
}
}
public class InitialValues {
public static void main(String[] args) {
System.out.println(Son.strFather);
}
}
运行结果:
HelloJVM_Father
解析:
唯一的特点就在于final static !是的Son.strFather所对应的变量是final static修饰的
(3)、 调用类的静态方法
(4)、 反射(如 Class.forName(“com.lms.ts”))
(5)、 初始化某个类的子类,则其父类也会被初始化
(6)、 Java虚拟机启动时被标明为启动类的类( JavaTest ),还有就是Main方法的类会首先被初始化
注意:对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块),这句话在继承、多态中最为明显。
package com.mason.techtest;
class YeYe{
static {
System.out.println("YeYe静态代码块");
}
public YeYe()
{
System.out.println("YeYe构造函数");
}
}
class Father extends YeYe{
public static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
public Father()
{
System.out.println("Father构造函数");
}
}
class Son extends Father{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
public Son()
{
System.out.println("Son构造函数");
}
}
public class InitialValues {
public static void main(String[] args) {
System.out.println(Son.strFather);
}
}
运行结果:
YeYe静态代码块
Father静态代码块
HelloJVM_Father
解析:
Son.strFather中的静态字段是属于父类Father的对吧,也就是说直接定义这个字段的类是父类Father,所以在执行 System.out.println(Son.strFather); 这句代码的时候会去初始化Father类而不是子类Son
如果是如下:
public static void main(String[] args) {
System.out.println(Son.strFather);
}
运行结果:
YeYe静态代码块
Father静态代码块
Son静态代码块
HelloJVM_Son
Java代码编译成字节码之后,最开始是没有构造方法的概念的,只有 类初始化方法 和 对象初始化方法
类初始化方法:编译器会按照其出现顺序,收集:类变量(static变量)的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行。
对象初始化方法:编译器会按照其出现顺序,收集:成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法,值得特别注意的是,如果没有监测或者收集到构造函数的代码,则将不会执行对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。
package com.mason.techtest;
class Father {
String str="father非static变量";
static String strSon = "father static变量";
static {
System.out.println("Father静态代码块");
}
{
System.out.println("Father普通代码块");
}
public Father() {
System.out.println("Father构造函数");
}
}
class Son extends Father {
String str="son非static变量";
static String strSon = "son static变量";
static {
System.out.println("Son静态代码块");
}
{
System.out.println("Son普通代码块");
}
public Son() {
System.out.println("Son构造函数");
}
}
public class ClassAndObjectLnitialize {
public static void main(String[] args) {
Son son=new Son();
System.out.println("输出的打印语句");
}
}
运行结果:
Father静态代码块
Son静态代码块
Father普通代码块
Father构造函数
Son普通代码块
Son构造函数
输出的打印语句
类的加载顺序:
(1).父类的静态代码块
(2).子类的静态代码块
(3).父类成员变量初始化
(4).父类普通代码块
(5).父类构造函数
(6).子类成员变量初始化
(7).子类普通代码块
(8).子类构造函数
4. 使用
当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。
5. 卸载
当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。
6.结束生命周期
在如下几种情况下,Java虚拟机将结束生命周期
(1)、 执行了 System.exit()方法
(2)、 程序正常执行结束
(3)、 程序在执行过程中遇到了异常或错误而异常终止
(4)、 由于操作系统出现错误而导致Java虚拟机进程终止
JVM 加载类的阶段:
第一个阶段是找到.class文件并把这个文件包含的字节码加载到内存中。
第二个阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析及相应的内存分配和最后的符号表的链接。
第三个阶段是类中静态属性和初始化赋值,以及静态块的执行等。
加载类的三种方式:
1、静态加载,也就是通过new关键字来创建实例对象。
2、动态加载,也就是通过Class.forName()方法动态加载(反射加载类型),然后调用类的newInstance()方法实例化对象。
3、动态加载,通过类加载器的loadClass()方法来加载类,然后调用类的newInstance()方法实例化对象
三种方式的区别:
1、第一种和第二种方式使用的类加载器是相同的,都是当前类加载器。(this.getClass.getClassLoader)。而3由用户指定类加载器。
2、如果需要在当前类路径以外寻找类,则只能采用第3种方式。第3种方式加载的类与当前类分属不同的命名空间。
3、第一种是静态加载,而第二、三种是动态加载。
发表评论
-
Stack 的实现原理深入剖析
2020-04-06 13:26 486Stack 介绍: Stack是栈。 ... -
Vector 的实现原理深入剖析
2020-04-06 13:17 363Vector介绍: Vector 是矢量队列,它是JDK1. ... -
JDK 分析工具
2020-04-05 17:30 362常用分析工具: jps:显示指定系统中所有的HotSpot虚 ... -
二叉树的深度优先遍历和广度优先遍历
2020-03-10 09:33 594概述: 1、深度优先遍历(Depth-First-Sear ... -
Hashtable 的实现原理深入剖析
2020-02-18 20:59 529一、Hashtable的基本方法: 1、定义: HashT ... -
jdk 1.8 新特性
2020-02-17 13:43 3611、default关键字 ... -
Java IO 架构
2019-11-11 16:39 350主要两类: 磁盘I/O 网络I/O 基于字节 ... -
Java 数据结构与算法
2019-04-03 10:25 515程序=数据结构+算法 ... -
Java语言异常(Exception)
2018-10-09 11:40 548异常,是Java中非常常用 ... -
Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
2018-08-17 09:47 1469首先介绍一些乐观锁与 ... -
Java 高性能编程注意事项
2016-11-17 09:55 6451. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担, ... -
Netty 解析
2017-03-07 13:47 1219Linux网络IO模型: Linux ... -
2016年Java 面试题总结
2016-01-18 13:34 54792多线程、并发及线程的基础问题: 1)Java 中能创建 vo ... -
java 内存模型
2015-12-29 13:44 816JAVA内存模型: Java内存 ... -
JVM 深入剖析
2015-12-29 12:51 1091JVM是JAVA虚拟机(JAVA Virtual Machin ... -
Java 并发编程_Synchronized
2015-12-16 12:42 870硬件的效率和一致性: 由于计算机的运算速度和它的存储和通讯子 ... -
Java 并发编程_Volatile
2015-12-15 13:42 618术语定义: 共享变量:在多个线程之间能够被共享的变量被称为共 ... -
Java 并发编程_ConcurrentLinkedQueue
2015-12-15 13:32 907ConcurrentLinkedQueue 的分析和使用: ... -
Java 并发编程_ConcurrentHashMap
2015-11-10 11:30 826ConcurrentHashMap 的分析和 ... -
JVM 垃圾回收原理
2015-10-29 13:38 489基本回收算法: 1.引用 ...
相关推荐
《深入Java虚拟机(七)深入源码看java类加载器ClassLoader》 Java类加载器(ClassLoader)在Java运行环境中扮演着至关重要的角色。它负责将类的字节码加载到Java虚拟机(JVM)中,使得程序能够运行。ClassLoader是...
2. **Extension ClassLoader** (扩展类加载器): 负责加载扩展类库,通常位于`JAVA_HOME/lib/ext`目录下的jar包。 3. **Application ClassLoader** (应用程序类加载器): 也称为系统类加载器,负责加载用户定义的类...
1. **Bootstrap Class Loader(启动类加载器)**:该类加载器使用C++编写,是JVM自身的一部分,用于加载位于`JAVA_HOME/jre/lib/rt.jar`中的类库,以及其他一些核心类库(如`java.lang.*`等)。Bootstrap Class ...
Java类加载器分为三种主要类型:引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader,也称为系统类加载器)。它们共同工作,确保了Java...
### Java 类加载器详解 #### 一、类加载器概述 在Java中,类加载器(Class Loader)是一项核心机制,用于将字节码(.class文件)加载到JVM中,使其成为运行时的对象。类加载器不仅实现了类的加载功能,还确保了...
2. **扩展类加载器(Extension ClassLoader)**:由sun.misc.Launcher$ExtClassLoader实现,加载JRE扩展目录`jre/lib/ext`下的jar文件,或者系统属性`java.ext.dirs`指定的路径。 3. **应用程序类加载器(Application ...
在Java编程语言中,ClassLoader是一个至关重要的组成部分,它负责加载类到JVM(Java虚拟机)中。了解和掌握ClassLoader的工作原理以及如何自定义ClassLoader对于深入理解Java应用程序的运行机制非常有帮助。以下是对...
### Java ClassLoader (类加载器)详解 #### 一、教程提示 如果你正在查看这份文档,在线版中你可以点击下面的任何主题直接跳转到相应的部分。 1. **教程提示** 2. **介绍** 3. **类加载器结构** 4. **编译类加载...
Java 类加载器 ClassLoader 用法解析 Java 中的类加载器(ClassLoader)是一种机制,负责将类从文件系统、JAR 文件或网络等来源加载到 Java 虚拟机中。类加载器的作用是将类的二进制数据加载到内存中,并为其创建一...
Java的ClassLoader类加载器机制 在 Java 虚拟机(JVM)中,类加载器(ClassLoader)扮演着非常重要的角色。类加载器负责加载 Java 类,包括核心类和用户自定义类。在 JVM 运行过程中,类加载器会形成一个层次结构,...
2. **Extension ClassLoader(扩展类加载器)**:它加载位于`JAVA_HOME/lib/ext`目录下的JAR包和用户指定的扩展目录下的类库。 3. **System ClassLoader(系统类加载器)**:也称为应用程序类加载器,它负责加载用户...
通过掌握类加载的过程以及类加载器的工作原理,可以帮助开发者更好地处理类加载过程中可能出现的问题,同时也可以为实现某些特殊需求提供支持。希望本文能够帮助读者更加深入地理解和应用Java类加载机制。
通过掌握类加载的过程、类加载器的工作原理以及类加载的线程安全性等方面的知识,开发者能够更好地利用Java的动态特性,优化程序性能,并避免常见的异常问题,如`ClassNotFoundException`等。此外,对于自定义类加载...
下面我们将深入探讨Java类加载器以及Tomcat中的类加载器。 在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt...
在Java编程语言中,ClassLoader是核心组件之一,它负责加载类到JVM(Java虚拟机)中执行。本文将深入探讨ClassLoader的工作原理和类加载机制,帮助开发者理解这个至关重要的概念。 1. 类加载机制概述 Java的类加载...
例如,`MemoryClassLoader.java`可能就是一个自定义类加载器的实现,它可以在内存中动态加载或更新类。 **JarinJAR**是一种打包技术,它可以将多个JAR文件打包成一个大的JAR文件。在热加载场景下,JarinJAR使得在...
在Java虚拟机文档(如`java虚拟机.doc`)中,通常会包含关于JVM架构、指令集、内存模型、异常处理、线程以及类加载机制等全面的信息。阅读这些文档有助于深入理解JVM的工作方式,对于进行Java性能调优或开发JVM相关...
理解类加载器的工作原理对于进行JVM优化、插件系统开发以及理解类的生命周期至关重要。类加载器的机制保证了Java的类加载过程是有序且安全的,同时也支持了Java的动态加载和模块化特性。在自定义类加载器时,需要...
2. **Extension ClassLoader**:扩展类加载器,负责加载`<JAVA_HOME>\lib\ext`目录下的JAR包,或者被`-Djava.ext.dirs`指定的路径中的类。 3. **System ClassLoader**:也称为应用类加载器,负责加载`CLASSPATH`...
在“java 类加载器 加密”这个主题中,我们将探讨如何利用类加载器实现类的加密和解密,以及如何通过反射执行main方法。 首先,我们理解一下类加载器的工作原理。Java中的类加载器主要有三种:Bootstrap ...