原文网址:JVM原理系列--类加载过程(有实例)_IT利刃出鞘的博客-CSDN博客

简介

本文介绍Java的类加载流程,有实例。

加载过程

流程概述

加载=> 链接(验证+准备+解析)=> 初始化=> 使用=> 卸载

  1. 加载(将硬盘上的Java二进制文件(class文件)转为内存中的Class对象)

    1. 通过一个类的全限定名获取定义此类的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在内存(不一定在堆中,HotSpot是在方法区)中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
  2. 链接(给静态变量赋初始值,符号引用替换成直接引用)
    1. 验证:检查载入的class文件数据的正确性
    2. 准备:给类变量(静态变量)分配内存(方法区)并设置为零值(0、false、null等)。
      1. 例外:static final类型的String或基本类型,直接赋值为最终值,如:static final int a = 12; 在准备阶段就将a赋值为12。
    3. 解析(可选):将常量池内的符号引用替换成直接引用。
      1. 符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用 ,那引用的目标必定已经在内存中存在。
  3. 初始化(初始化类变量(静态变量)、执行静态语句块)
    1. 执行类变量(静态变量)的赋值动作和静态语句块(按定义的顺序从上往下执行)。优先级:静态、父类、子类

      1. 注意:初始化是操作类变量(也就是静态变量),不是对象的变量。
  4. 使用(以new一个对象为例)
    1. 若是第一次创建 Dog 对象(对象所属的类没有加载到内存中)则先执行上面的初始化操作。
    2. 在堆上为 Dog 对象(包括实例变量)分配空间,所有属性都设成默认值(数字为 0,字符为 null,布尔为 false,引用被设成 null)
    3. 初始化实例:给实例变量赋值、执行初始化语句块
    4. 执行构造函数检查是否有父类,如果有父类会先调用父类的构造函数
    5. 执行本类的构造函数。

对类初始化的场景

虚拟机规范严格规定: 有且只有 5 种情况 必须立即对类进行初始化(加载、验证、准备自然需要在之前执行):

  1. 遇到 new 、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,若没有对类进行初始化,则要先触发其初始化。

    1. 这4个指令含义是:使用 new 新建一个 Java 对象,访问或者设置一个类的静态字段,访问一个类的静态方法。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  4. 当虚拟机启动的时候,用户需要指定一个需要执行的主类(包含 main 方法的那个类),虚拟机会先初始化这个类。
  5. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

static与final属性赋值的区别

  1. final修饰的属性,在实例创建的时候才会赋值。
  2. static修饰的属性,在链接阶段的准备阶段赋初值,初始化阶段赋值。
  3. static+final修饰的String类型或基本类型,JVM规范是在初始化阶段赋值,但是HotSpot VM在准备阶段就赋值了。
  4. static+final修饰的其他类型,跟单纯static修饰(第2种)的流程是一样的。

类加载过程的实例验证

类初始化顺序

package org.example.a;class Test{int a = instanceMethod();static int b = staticMethod();static {System.out.println("static block");}{System.out.println("instance block");}public Test(){System.out.println("constructor");}public int instanceMethod(){System.out.println("instance method");return 2;}public static int staticMethod(){System.out.println("static method");return 2;}
}public class Demo {public static void main(String[] args) {Test child = new Test();}
}

运行结果

static method
static block
instance method
instance block
constructor

手动装载/初始化

package com.example.a;class Test{int a = instanceMethod();static int b = staticMethod();static {System.out.println("static block");}{System.out.println("instance block");}public Test(){System.out.println("constructor");}public int instanceMethod(){System.out.println("instance method");return 2;}public static int staticMethod(){System.out.println("static method");return 2;}
}public class Demo {public static void main(String[] args) {ClassLoader cl = ClassLoader.getSystemClassLoader();try {//装载类System.out.println("装载类开始");cl.loadClass("com.example.a.Test");System.out.println("装载类结束");//初始化类System.out.println("初始化开始");Class.forName("com.example.a.Test");System.out.println("初始化结束");Test test = new Test();test.instanceMethod();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

执行结果

装载类开始
装载类结束
初始化开始
static method
static block
初始化结束
instance method
instance block
constructor
instance method

静态域的访问

访问类或接口的静态域时,只有真正声明这个域的类或接口才会被初始化

package org.example.a;class B {static int value = 100;static {System.out.println("Class B is initialized."); //输出}
}
class A extends B {static {System.out.println("Class A is initialized."); //不会输出}
}public class Demo {public static void main(String[] args) {System.out.println(A.value); //输出100}
}

执行结果

Class B is initialized.
100

静态方法不能访问非静态方法和变量

静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接去访问。非静态成员(变量和方法)属于类的对象,只有该对象实例化之后才存在,然后通过类的对象去访问。

加载阶段的第2步(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)会将所有static修饰的内容(包括静态属性、静态代码块、静态方法等)转为方法区的运行时数据结构,但此时非静态的方法和变量根本没经过初始化(没有内存),所以会失败。

类加载过程的线程安全

简介

Java类的加载和初始化过程都是线程安全的。具体原因如下:

  • 加载

    • 类加载整个过程是线程安全的,因为loadClass方法内有synchronized。
  • 初始化
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

总结

类的静态资源都是线程安全的,而且是单例的。因此,在写单例模式时,经常会使用static标记实例变量。比如:

public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

加载源码

见java.lang.ClassLoader#loadClass:

下边方法前边有个注释:
Unless overridden, this method synchronizes on the result of <getClassLoadingLock> method during the entire class loading process.
//除非被重写,否则整个装载过程中是线程安全的。注意:本处装载是装载、链接、初始化的装载,而不是整个加载流程

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

Class对象

其他网址

《深入理解Java虚拟机 JVM高级特性与最佳实践 第2版》=> 第7章 虚拟机类加载机制

代码实例

一旦类被加载了到了内存中,不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址(对于HotSpot是方法区)上的Class引用。

package org.test.a;class Cat{static {System.out.println("static cat");}
}public class Demo {public static void main(String[] args) throws ClassNotFoundException {Class c1 = Cat.class;Class c2 = new Cat().getClass();Class c3 = new Cat().getClass();Class c4 = Class.forName("org.test.a.Cat");System.out.println(c1 == c2);System.out.println(c2 == c3);System.out.println(c3 == c4);}
}

输出结果

static cat
true
true
true

分析

内存的字节码块就是完整的把整个类装到了内存。使用的主要步骤如下:

  1. 当一个ClassLoder启动的时候,ClassLoader生存在jvm中的堆。
  2. ClassLoder去主机硬盘上将A.class装载到jvm的方法区。
  3. new A()时:虚拟机使用方法区中的字节文件在堆内存生成了一个A字节码的对象
  4. 然后A字节码这个内存文件有两个引用:一个指向A的class对象,一个指向加载自己的classLoader

静态内部类的加载时机

说明

只有使用类的时候,才会将类初始化。比如:新建一个静态内部类。

new一个外部类的时候,加载阶段会扫描static修饰的东西,初始化阶段只会执行static修饰的属性的赋值以及static代码块。利用这个特性,可以做基于静态内部类的单例模式:Java设计模式系列--单例模式(6种写法)_IT利刃出鞘的博客-CSDN博客

测试

package org.example.a;class OuterClass {static {System.out.println("OuterClass static");}public static class InnerClass {static int a = 1;static{System.out.println("InnerClass static");}static void innerMethod(){System.out.println("innerMethod");}}
}public class Demo {public static void main(String[] args) {//OuterClass outerClass = new OuterClass();//OuterClass.InnerClass.a = 2;//OuterClass.InnerClass.innerMethod();}
}

只放开第一个测试的执行结果

OuterClass static

只放开第二个测试的执行结果

InnerClass static 

只放开第三个测试的执行结果

InnerClass static
innerMethod

其他网址

Java类加载的过程_Java_持之以恒!-CSDN博客
《深入理解Java虚拟机 JVM高级特性与最佳实践 第2版》=> 第7章 虚拟机类加载机制

线程安全

类加载过程的线程安全性保证(让实现线程安全的单例,又不让使用synchronized!)_zhangustb-CSDN博客
Java类的加载、链接和初始化-HollisChuang's Blog

《深入理解Java虚拟机 JVM高级特性与最佳实践 第2版》=> 第7章 虚拟机类加载机制=> 7.3 类加载的过程=> 7.3.5 初始化

JVM原理系列--类加载过程(有实例)相关推荐

  1. JVM原理系列--元空间(MetaSpace)与永久代(PermGen)的区别

    原文网址:JVM原理系列--元空间(MetaSpace)与永久代(PermGen)的区别_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍JVM中元空间(MetaSpace)与永久代(PermG ...

  2. JVM原理系列--双亲委派模型

    原文网址:JVM原理系列--双亲委派模型_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java虚拟机的双亲委派模型. 工作过程 说明 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求, ...

  3. 2021年度最全面JVM虚拟机,类加载过程与类加载器

    前言 类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关. 一般来说,Java 类的虚拟机使用 Java 方式如下: Java 源程序(.java 文件)在经过 Java 编译器编译之后 ...

  4. JVM——详解类加载过程

    导航 一.过程概述 二.Loading 2.1 类加载器 2.2 双亲委派机制 2.3 类在内存中的结构 三.Linking 四.Initializing 一.过程概述 java 源文件编译后会生成一 ...

  5. jvm原理总结转载来的

    前言: 想提高Java开发,了解jvm是必不可少的.它让开发者了解他们的代码,jvm是如何变异与运行.深入了解jvm:会让你的代码写的高效,逐步成为大神 下面介绍jvm的基本知识 >>数据 ...

  6. 《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程

    参考网址:http://blog.csdn.net/luanlouis/article/details/50529868 0.前言 读完本文,你将了解到: 一.为什么说Jabalpur语言是跨平台的 ...

  7. JVM系列(一):JVM类加载过程详解

    Java 通过引入字节码和 JVM 机制,提供了强大的跨平台能力,理解 Java 的类加载机制是深入 Java 开发的必要条件. 一.Java代码执行流程 Java程序运行时,必须经过编译和运行两个步 ...

  8. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  9. jvm类加载过程_JVM知识点——深入理解JVM的类加载

    前言: 前面又说到Java程序实际上是将.class文件放入JVM中运行.虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的Java类 ...

最新文章

  1. python京东商品采集_利用Python正则表达式抓取京东网商品信息
  2. php 通知客户端,PHP+SSE服务器向客户端推送消息
  3. dephi中单击鼠标手动窗口
  4. 随身风暴英雄接入云信,玩家即时通讯轻松搞定
  5. Uva 1025 - A Spy in the Metro(DP)
  6. UVa 129 - Krypton Factor(回溯法)
  7. 从入门到入土:基于C语言采用UDP协议实现远程控制|详细说明|利用流套接字实现一个简单的远程控制系统|代码展示
  8. 理解 Delphi 的类(十) - 深入方法[8] - 如果忘了返回值
  9. 中科大研发的FTP搜索工具~
  10. dcdc升压计算器excel_DC-DC电路计算器app下载|DC-DC电路计算器安卓版下载_v1.0.1_9ht安卓下载...
  11. 866 数据结构模拟题(一)及解析
  12. 初学编程丨从零开始进修编程的基本路线,BAT程序员亲手总结!
  13. 5.3 千亿访问量下的开放平台技术揭秘
  14. 我的世界神秘时代安卓java版_我的世界神秘时代4
  15. 人工智能召唤“神龙”,阿里云发布首个云上异构超算集群...
  16. html中背景固定,css背景固定样式background-attachment属性介绍
  17. 如何把meshlab中的圆环去掉_MeshLab中插件的添加过程
  18. esp8266 + 温湿度的vfd 时钟
  19. vue做混合式app_Vue Cordova教程-Vue+Cordova打造跨平台可安装的混合APP视频教程(大地)...
  20. java怎么设置按钮凹凸状态,FragmentTabHost实现中间按钮凸出效果

热门文章

  1. 凹凸贴图简析(应景之作--写给美术朋友们的凹凸贴图白皮书)
  2. Iphone死机怎么办?
  3. UC浏览器视频播放缓存以及视频下载分析
  4. 영어 단어 읽기 규칙 - 모음편
  5. fateskins国内最新可直接取回饰品皮肤的CSGO网页开箱子网站!
  6. java sql in 预编译_JAVA使用预编译防止SQL注入
  7. word另起一个自动编号,另外从头编号
  8. 锋迷商城项目数据库设计(四)
  9. springboot整合curator实现分布式锁模拟抢购场景
  10. Android Dialog实现全选反选