标签:互调 步骤 默认 获得 数据结构 获取 vat end run
补习一下jvm内存模型中的各个组成部分
java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
我们讲的把类加载进内存,说的就把把类加载进了方法区,方法区里面存放着类的方法,常量,静态变量等等所有信息
下面的类加载机制,主要讲的就是这个阶段
这个错误号称是所有程序员都犯过,我也错了,原因就是类加载的过程不清楚,通过这次学习,搞懂了咋个过程,自然可以搞懂结果
/**
*
@Data
public class text {
private static text text = new text();
public static int count1;
public static int count2 = 0;
/*
3 类被加载内存,把静态的变量全部附默认值, 这就是 18-20 分别为 null 0 0
4 再然后 初始化 首先, 调用构造方法18行, 得到 text 顺道 count1 = 1 count2 = 1 ,
5 往下执行19 20 分别为 1 0
*/
public text(){
count1++;
count2++;
}
public static text getSingleton(){
return text;
}
}
class mytext{ //1 启动类
public static void main(String[] args) {
text singleton = text.getSingleton(); //2 主动使用
System.out.println(singleton.count1);
System.out.println(singleton.count2);
}
}
类的加载是将类的.class文件中的二进制数据,(由硬盘)加载到内存中,并将其放在运行时的数据区的方法区,然后在堆中创建一个java.lang.Class对象,用来封装整个类在方法区中的数据结构
这也是Class对象是反射的入口的原因,也可以看到,一旦类被加载进虚拟机,Class就会被虚拟机创建出来
注意一个地方,上面的第二步中的阶段二,准备阶段,以及第三步:初始化似乎有些重复,但是实际上是两回事准备阶段:初始化默认值整型0,对象为null,布尔为false但是初始化:他是将用户指定的初始化的值覆盖默认值
其实总体上看,这就是类加载的整个过程,下面是细化很多知识点,比如什么时候触发类的加载,
被动使用(除了上面的六种,其他都是被动)
所有的java虚拟机的实现必须在每个类或者接口被java程序"首次主动使用"时,才初始化他们
除了上面的六种,其他都是被动,而被动使用都不会导致类的初始化(也就是图1的第三步)
它没有父类加载器,他负责加载虚拟机的核心类库,java.lang.* 等.它从系统属性sun.boot.class.path所制定的目录中加载类库,它的实现依赖于底层的操作系统,并没继承java.lang.ClassLoader
后两种使用java代码实现
它的父类加载器是根类加载器,它会从系统属性java.ext.dirs系统属性所指定的目录加载类库,或者从JDK的安装目录jre/lib/ext子目录(扩展目录)加载类库,(如果用户把自己的jar包放在=这个目录下,也会自动被Extension ClassLoader加载),这个类加载器本身是个纯java类,是java.lang.ClassLoader的子类
它的父类加载器是扩展类加载器,它会从环境变量classpath(初学java的配置的环境变量)或者系统属性java.class.path所指定的目录去加载类,同时,他也是用户自定义的类加载器的默认父类(ClassLoader类的一个空参构造指明,如果我们不传递任何参数,它就会调用getSystemClassLoader()作为当前类的父类加载器),它本身同样也是一个纯java类,是java.lang.ClassLoader的子类
提一下动态代理,第一个参数要求我们传递一个类加载器,使用这个类加载器,把我们的类加载进内存
类被加载后,就进入了连接阶段,这个阶段就是将已经读入内存的类的二进制数据合并到虚拟机的运行时环境中去
因为多个.class文件之间是没关系的,但是类之间可能存在调用关系,这个阶段会内存中处理好类之间的相互调用的复杂关系
类验证的内容:
比如下面: 为a 分配四个字节的内存空间,并初始化为0, 注意是0
为 b 分配8个字节的内存空间,并初始化为0;
public class A{
private static int a =1;
private static Long b;
}
在解析阶段,jvm 会把类的二进制文件数据中的符号引用替换为直接引用,
例如:
public class worker{
public void driver(){
car.run();
}
}
在worker类的二进制文件中,包含了一个对car的run()方法的符号引用,在类的解析阶段,java虚拟机会把这个符号引用替换为一个指针,指向car类的run()方法,在方法区 的内存位置,这个指针就是直接引用
public class parent {
static int a = 3;
static{
System.out.println("Parent static block");
}
}
class child extends parent{
static {
System.out.println("child static block");
}
}
class text4{
public static void main(String[] args) {
System.out.println(child.a);
}
}
结果
Parent static block
3
这段代码的结果很好的验证了类的加载时机之,只有静态变量或静态方法,确实在当前类或者当前接口中定义时,才可以理解为,是对当前类或者当前接口的主动使用
public class textClassLoader {
static{
System.out.println("textClassLoader static block");
}
}
class text5{
public static void main(String[] args) throws ClassNotFoundException {
// 获取应用类加载器
ClassLoader loader = ClassLoader.getSystemClassLoader();
//把类加载进内存
loader.loadClass("com.tryjvm.textClassLoader");
System.out.println("---------");
//反射
Class.forName("com.tryjvm.textClassLoader");
}
}
private static int a =1;
private static int b;
static {
b=1;
}
第一段代码:
public class text3 {
public static final String x = "hello";
static{
System.out.println("world");
}
}
class text33{
public static void main(String[] args) {
System.out.println(text3.x);
}
}
结果:
hello
第二段代码:
public class text3 {
public static final String x =new String("hello");
static{
System.out.println("world");
}
}
class text33{
public static void main(String[] args) {
System.out.println(text3.x);
}
}
结果
hello
world
因此,父接口,并不会应为他的子接口,或者实现类的初始化而初始化,而是当程序首次使用接口的静态变量时,才会导致接口的初始化
这样做有个很明显的好处就是,java的核心类库,不会被用户自定义的类加载器加载, 用户自定义的类基本上也不会被高层的类加载器加载
加载器之间的父子关系,不一定就是真真正正的继承关系,一对父子类加载器,可能是同一个加载器类的两个实例,具体谁是父,要看谁被包装,是一种包装关系
class MyClassLoader extends ClassLoader{
}
ClassLoader loader1 = new MyClassLoader();
//将loader1作为loader2的父类加载器
ClassLoader loader2 = new MyClassLoader(loader1);
* 当前的这个类加载器和它所有的父类加载器所加载的类组成一个命名空间,在这个命名空间中,不会出现类的完整名字(包括类的包名)相同的 两个类, 而在不同的命名空间,就有可能出现这种情况
也就是说,假如有两个类加载器,并且他们没有仍和父子关系,那么,他们可以都去加载同一各类,加载进内存
我们知道,在一个包下的不同类是可以相互访问的,那么假如我自己创建一个包叫 java.lang.SP ,我这个包里面的类,是否可以直接访问真正的java.lang.*里面的核心类呢? 答案是不可以的
* 继承java.lang.ClassLoader类,重写 findClass(String name)方法, 根据制定的类的名字,返回CLass对象的引用
标签:互调 步骤 默认 获得 数据结构 获取 vat end run
原文地址:https://www.cnblogs.com/ZhuChangwu/p/11150427.html