同步操作将从 icanci/Java-Review 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
在 Java 中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载
按照 Java 虚拟机规范,从 Class 文件到加载到内存中的类,到类卸载出内存位置,它的整个生命周期包括如下七个阶段:
其中验证、准备、解析3个部分统称为链接(Linking)
从程序中使用类的过程来看
蚂蚁金服:
百度:
类加载的机制
Java 类加载过程?
简述 Java 类加载机制?
腾讯:
滴滴:
美团:
外部可以通过访问代表Order类的Class对象来获取Order的类数据结构
再说明
验证阶段(Verification)
/**
* 基本数据类型:非 final 修饰的变量,在准备环节进行默认初始化赋值
* final 修饰以后,在准备环节直接进行显式赋值
*
* 拓展:如果使用字面量的方式定义一个字符串的常量的话,也是在准备环节直接进行显式赋值
*/
public class LinkingTest {
private static long id;
private static final int num = 1;
public static final String constStr = "CONST";
public static final String constStr1 = new String("CONST");
}
解析阶段(Resolution),简而言之,就是将类、接口、字段和方法的符号引用转为直接引用
具体描述
举例:输出操作 System.out.println() 对应的字节码
小结
字符串的复习
/**
*
* 哪些场景下,Java 编译器就不会生成<clinit>()方法
*/
public class InitializationTest1 {
//场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public int num = 1;
//场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法
public static int num1;
//场景3:比如对于声明为 static final 的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法
public static final int num2 = 1;
}
/**
*
* 说明:使用 static + final 修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值?
* 情况1:在链接阶段的准备环节赋值
* 情况2:在初始化阶段<clinit>()中赋值
*
* 结论:
* 在链接阶段的准备环节赋值的情况:
* 1. 对于基本数据类型的字段来说,如果使用 static final 修饰,则显式赋值(直接赋值常量,而非调用方法)通常是在链接阶段的准备环节进行
* 2. 对于 String 来说,如果使用字面量的方式赋值,使用 static final 修饰的话,则显式赋值通常是在链接阶段的准备环节进行
*
* 在初始化阶段<clinit>()中赋值的情况
* 排除上述的在准备环节赋值的情况之外的情况
*
* 最终结论:使用 static + final 修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行
*/
public class InitializationTest2 {
public static int a = 1; //在初始化阶段<clinit>()中赋值
public static final int INT_CONSTANT = 10; //在链接阶段的准备环节赋值
public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100); //在初始化阶段<clinit>()中赋值
public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000); //在初始化阶段<clinit>()中赋值
public static final String s0 = "helloworld0"; //在链接阶段的准备环节赋值
public static final String s1 = new String("helloworld1"); //在初始化阶段<clinit>()中赋值
}
Java程序对类的使用分为两种:主动使用和被动使用
主动使用:Class 只有在必须要首次使用的时候才会被装载,Java 虚拟机不会无条件地装载 Class 类型。Java 虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的"使用",是指主动使用,主动使用只有下列几种情况:(即:如果出现如下的情况,则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成)
当创建一个类的实例时,比如使用 new 关键字,或者通过反射、克隆、反序列化
当调用类的静态方法时,即当使用了字节码 invokestatic 指令
当使用类、接口的静态字段时(final 修饰特殊考虑),比如,使用 getstatic 或者 putsttic 指令。(对应访问变量、赋值变量操作)
当使用 java.lang.reflect 包中的方法反射类的方法时。比如:Class.forname("com.atguigu.java.Test")
当初始化子类时,如果发现其分类还没有进行过初始化,则需要先触发其父类的初始化
如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化
当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类
当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析 REF_getStatic、REF_putStatic、REF_invokeStatic 方法句柄对应的类)
针对5,补充说明:
当 Java 虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化,只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化
针对7,补充说明:
被动使用:除了以上的情况属于主动使用,其他的情况均属于被动使用。被动使用不会引起类的初始化,也就是说:并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化
当访问一个静态字段时,只有真正声明这个字段的类才会被初始化
当通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类引用,不会触发此类的初始化
引用变量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了
调用 ClassLoader 类的 loadClass() 方法加载一个类,并不是对类的主动使用,不会导致类的初始化
如果针对代码,设置参数 -XX:+TraceClassLoading,可以追踪类的加载信息并打印出来
任何一个类型在使用之前都必须经历过完整的加载、链接和初始化3个类加载步骤。一旦一个类型成功经历过这3个步骤之后,便“万事俱备,只欠东风”,就等着开发者使用了
开发人员可以在程序中访问和调用它的静态类成员信息(比如:静态字段、静态方法),或者使用 new 关键字为其创建对象实例
类、类的加载器、类的实例之间的引用关系
类的生命周期
具体例子
类的卸载
启动类加载器加载的类型在整个运行期间是不可能被卸载的(JVM 和 JSL 规范)
被系统类加载器和扩展类加载器加载的类型在运行期间不太可能被卸载,因为系统类加载器实例或者扩展类的实例基本上在整个运行期间总能直接或者间接的访问的到,其达到 unreachable 的可能性极小
被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载,而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到。可以预想,稍微复杂点的应用场景(比如:很多时候用户在开发自定义类的加载器实例的时候采用缓存的策略以提高系统性能),被加载的类型在运行期间也是几乎不太可能被卸载的(至少卸载的时间是不确定的)
综合以上三点,一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的。同时我们可以看的出来,开发者在开发代码时候,不应该对虚拟机的类型卸载做任何假设的前提下,来实现系统中的特定功能
方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型
HotSpot 虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收
判定一个常量是否"废弃"还是相对简单,而要判定一个类型是否属于"不再被使用的类"的条件就比较苛刻了。需要同时满足下面三个条件:
Java 虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是"被允许",而并不是和对象一样,没有引用了就必然会回收
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。