我在跟着[张龙老师]学习JVM 之 类加载器连接与初始化过程详解
类的使用方式
- java 程序对类的使用方式可分为两种
- -主动使用
- -被动使用
- 所有的Java虚拟机实现必须再每个类或接口被 java 程序 “首次主动使用”时才初始化他们。
- 只有主动使用才进行初始化(话句话说,被动使用就不会进行初始化)
- 并且只有首次使用才会进初始化(意思就是只会初始化一次)
类的主动使用(七种)
- -创建类的实例
如: 创建一个类的实例 Object obj =new Object()
- -访问某个类或接口的静态变量,或者对该静态变量赋值
- -调用类的静态变量方法
- -反射(如:Class.forName("com.test.Test")
- -初始化一个类的子类
我们定义两个类 public class Parent{ } public class Children extends Parent{ } 当对 Children 类 进行初始化的时候,类加载器会先初始化 Parent 类,而这样的操作也是对Parent 类的主动使用。
- -java 虚拟机启动时被标明为启动类的类
用过SpringBoot 就应该知道,类启动方式 才采用 main 的方式。 也就是说凡是定义再类中 定义了 public static void main(Stirng args[]){} 那么该类就被别主动使用
- JDK1.7开始提供的动态语言支持;
java.lang.invoke.MethodHandle 实例的解析结过REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化则初始化。
jdk1.7 开始 java 开始动态语言支持
类的被动使用
- 除了以上七种情况,其他使用java类的方式都被看作对类的 被动使用,都不会导致类的初始化。
- 初始化属于 第三个阶段,不会被初始化,并不表示不会加载和连接(也有可能不会加载和连接,这些都有可能的)。
类的加载
- 类的加载指的是将类的.class文件种的二进制数据读入到内存中
,将其放再运行时数据区的方法区内。然后在内存中创建一个 java.lang.class 对象
jdk1.7 之前都用一个方法区, jdk1.8后提出了一个改造叫MetaBase(元空间)(规范并没有说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区的数据结构。一个类不管他生成了多个实例,所有的这些实例他们所对应的Class对象只有唯一的一份

加载.class文件的方式
- -从本地系统中直接加载
- -通过网络下载.class文件
- -从zip、jar等归档文件中加载.class文件
- -从专有数据库中提取.class文件
- -将java源文件动态编译为.class文件(动态代理、jsp转换成java文件)
类的被动使用
- 主动使用,按照文字描述也就很明白,很简单
- 那么聊聊类的被动使用
- 以真是案例作为演示(我会跟着张龙老师,一个一个案例敲代码)
- 学会自己思考(送给自己)
案例一(代码):查看代码,推敲 程序结果
public static void main(String[] args) { System.out.println(Childer.name); } class Parent{ public static String name="static Parent"; static { System.out.println("my name is Parent"); } } class Childer extends Parent{ static { System.out.println("my name is Childer"); } }
案例一(结果):
#执行 Parent 类 静态块
my name is Parent
#打印 Parnet 类 静态变量信息
static Parent
案例一(分析):
- 对于静态变量来说,只有直接定义了该字段的类才会被初始化
- 通过 Childer 调用 Parent 类的 name ,而 name 是由 Parent类所定义,因此这叫对 Parent 类的主动使用。虽然用了 Childer类,但并没有对进行主动使用(参考主动使用的七种方式)。
- 因为没有对 Childer 类的主动使用,所以没执行 Childer 的静态块。
案例二(代码):
public static void main(String[] args) { System.out.println(Childer.name); } } class Parent{ public static String name="static Parent"; static { System.out.println("my name is Parent"); } } class Childer extends Parent{ public static String name="static Childer"; static { System.out.println("my name is Childer"); }
}
案例二(结果):
my name is Parent my name is Childer static Childer
案例二(分析):
- 当子类被初始时,会率先初始化所有父类(父类还有父类,同样会按照顺序往上执行),所以会先执行 Parent 类的静态块
- 按照 "自己有用自己"的原则,Chiler 类 同样有一个 静态变量 name ,当调用 name时按照只有定义该变量的类才会被初始化规则,所以对Childer进行了主动使用。
- 最终结果打印了 Childer 类的 静态变量 name
- 这些初始化都只会首次主动使用进行初始化
类的加载信息
结合案例一的结果来看,Childer 并没有被初始化,而初始化位于第三个阶段(类的加载——类的连接(验证、准备、解析)——类的初始化),那如何知道该类是否被加载呢?
介绍一个 JVM 参数 配置 -XX:+TraceClassLoading ,用于追踪类的加载信息并打印到控制台。
为了演示效果,先将 代码运行一遍
public class Test1 { public static void main(String[] args) { System.out.println(Childer.name); } } class Parent{ public static String name="static Parent"; static { System.out.println("my name is Parent"); } } class Childer extends Parent{ static { System.out.println("my name is Childer"); } }
运行结果:并不会执行(初始化)Childer 类 中的代码。
my name is Parent static Parent
那我们看看 Childer 是否被加载
第一步:配置JVM运行时参数
①:点击 Edit Configurations 打开弹窗
②:VM options 中添加 -XX:+TraceClassLoading
③:点击保存,并允许代码

第二步:启动程序,查看控制台(由于打印内容过多,不利于阅读,只取了前后两个部分,重点信息用 红色标记出来了)
[Opened D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.String from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.reflect.Type from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Class from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Cloneable from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.ClassLoader from D:\java\jdk8\jre\lib\rt.jar]
....
[Loaded java.net.PlainSocketImpl$1 from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$BinaryToASCIIConverter from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$ExceptionalBinaryToASCIIBuffer from D:\java\jdk8\jre\lib\rt.jar]
[Loaded com.test.Test1 from file:/D:/workspace/test/out/production/test/]
[Loaded sun.launcher.LauncherHelper$FXHelper from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$BinaryToASCIIBuffer from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$1 from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Void from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$ASCIIToBinaryConverter from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$PreparedASCIIToBinaryBuffer from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.misc.FloatingDecimal$ASCIIToBinaryBuffer from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.net.DualStackPlainSocketImpl from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.net.Inet6Address from D:\java\jdk8\jre\lib\rt.jar]
[Loaded com.test.Parent from file:/D:/workspace/test/out/production/test/]
[Loaded com.test.Childer from file:/D:/workspace/test/out/production/test/]
[Loaded java.net.Inet6Address$Inet6AddressHolder from D:\java\jdk8\jre\lib\rt.jar]
my name is Parent
static Parent
[Loaded java.net.SocksSocketImpl$3 from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Shutdown from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.net.ProxySelector from D:\java\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from D:\java\jdk8\jre\lib\rt.jar]
[Loaded sun.net.spi.DefaultProxySelector from D:\java\jdk8\jre\lib\rt.jar]
说明:
虚拟机在当前程序启动下从上往下加载类的信息,他们都有应该共同的父类,也就是Object类,他会第一个加载出来(毋庸置疑),然后再加载其他相依赖的信息,如JDK 相关的,第三方jar 等。然后一步一步加载到我们定义的类,从运行结果中可以看出首先会加载标记为程序启动类的类,然后再是 Parent 类及 Childer 类。
其中 标记为蓝色 的是程序进行打印的结果。
这里用了一个相依赖,这又是什么意思呢?看下面案例:
在创建一个文件 Test2

在运行一下 Test1 程序看 Test2 是否被加载(我就直接在 控制进行检索)。

由此看出,Test2类并没有被加载(不用细究也能相通,都没用到也被加载到内存中,对内存消耗会有多大)。
笔后语录:
这篇博客位于 张龙 老师 视频教程的 第五~第六集前部分内容补充。张龙老师的JVM视频,这是我看的第二篇了,上一遍大概在八月份,后来好几个月不怎么关注JVM 虚拟机,然后给忘了七七八八。当时也做了笔记,当时写写在笔记软件中的。这次是结合复习的目的,在跟着学习一下JVM知识。有兴趣的朋友可以去看看他的视频。收获挺多的。

浙公网安备 33010602011771号