JAVA代码经过编译从源码变为字节码,字节码可以被JVM解读,使得JVM屏蔽了语言级别的限制。才有了现在的kotlin、Scala、Clojure、Groovy等语言。
字节码文件中描述了类的各种信息,都需要加载到虚拟机之后才能运行和使用。
简单学习了类加载进制后,写一篇文章记录一下以便加深记忆与理解。
Java天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。例如,编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类,用户可以通过Java预置的或自定义类加载器,让某个本地的应用程序在运行时从网络或其他地方上加载一个二进制流作为其程序代码的一部分。
图中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。强调这点是因为这几个阶段通常都是互相交叉地混合进行的,会在一个阶段执行的过程中调用、激活另一个阶段。
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,不要混淆这两个看起来很相似的名词。在加载阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
-XX:+TraceClassLoading
与-XX:+TraceClassUnloading
虚拟机参数简介:
-XX:+<option> 表示开启option选项
-XX:-<option> 表示关闭option选项
-XX:<option>=<value> 表示把option选项的值设置为value
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
Java语言本身是相对安全的编程语言(起码对于C/C++来说是相对安全的),但Class文件并不一定只能由Java源码编译而来,它可以使用包括靠键盘0和1直接在二进制编辑器中敲出Class文件在内的任何途径产生。上述Java代码无法做到的事情在字节码层面上都是可以实现的,至少语义上是可以表达出来的。Java虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有错误或有恶意企图的字节码流而导致整个系统受攻击甚至崩溃,所以验证字节码是Java虚拟机保护自身的一项必要措施。
验证阶段将做一下几个工作:
这个地方要说一点和开发者相关的。.class文件的第5~第8个字节表示的是该.class文件的主次版本号,验证的时候会对这4个字节做一个验证,高版本的JDK能向下兼容以前版本的.class文件,但不能运行以后的class文件,即使文件格式未发生任何变化,虚拟机也必须拒绝执行超过其版本号的.class文件。举个具体的例子,如果一段.java代码是在JDK1.6下编译的,那么JDK1.6、JDK1.7的环境能运行这个.java代码生成的.class文件,但是JDK1.5、JDK1.4乃更低的JDK版本是无法运行这个.java代码生成的.class文件的。如果运行,会抛出java.lang.UnsupportedClassVersionError,这个小细节,务必注意。
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
这些变量所使用的内存在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了。
解析阶段因为自己对字节码文件格式还不是非常明白,暂时不太懂,先留着,后面加强了字节码文件格式后,再来补充。
类加载一直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。
我们也可以从另外一种更直接的形式来表达:初始化阶段就是执行类构造器
但是这个<clinit>()
还不是很懂。自己需要学习class文件结构。
对于初始化阶段,《Java虚拟机规范8》(下方引用文字是原文)则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用。
引用官网虚拟机jvms8规范5.5章节Initialization
A class or interface C may be initialized only as a result of:
1 The execution of any one of the Java Virtual Machine instructions new,
getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or
indirectly through either a field reference or a method reference.
Upon execution of a new instruction, the referenced class is initialized if it has not been initialized already.
Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been
initialized already.
2 The first invocation of a java.lang.invoke.MethodHandle instance which was the result of method handle resolution (§5.4.3.5) for a method handle of kind 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial).
This implies that the class of a bootstrap method is initialized when the bootstrap method is invoked for an invokedynamic instruction (§invokedynamic), as part of the continuing resolution of the call site specifier.
3 Invocation of certain reflective methods in the class library (§2.12), for example, in class Class or in package java.lang.reflect.
4 If C is a class, the initialization of one of its subclasses.
5 If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.
6 If C is a class, its designation as the initial class at Java Virtual Machine startup (§5.2).
class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
run with +XX:+TraceClassLoading输出如下,子类会被加载,但是不会初始化。
...
[Loaded com.jamie.basicstudy.jvm01.NotInitialization from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
...
[Loaded com.jamie.basicstudy.jvm01.SuperClass from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
[Loaded com.jamie.basicstudy.jvm01.SubClass from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
SuperClass init!
123
class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] a = new SuperClass[10];
}
}
run with +XX:+TraceClassLoading输出如下,子类不会被加载,父类被会加载,但是父类不会初始化。
...
[Loaded com.jamie.basicstudy.jvm01.NotInitialization from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
...
[Loaded com.jamie.basicstudy.jvm01.SuperClass from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final int value = 123;
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstClass.value);
}
}
run with +XX:+TraceClassLoading输出如下,ConstClass类不会被加载,只会加载主类。
...
[Loaded com.jamie.basicstudy.jvm01.NotInitialization from file:/D:/CodeFolder/jamie/basic_study/target/classes/]
...
123
原文:https://www.cnblogs.com/1626ace/p/13308427.html