JVM虚拟机系列:内存管理&类加载机制(二)

类加载机制相关问题

2. 谈谈java.lang.Class和java.lang.Object之间的悖论

3. 谈谈java.lang.Class和类加载器之间的悖论

4 java.lang.Class对象和对象的内存布局

5 Class对象中到底存了什么?

从已有资料来看,Class对象在不同的虚拟机在实现上存储的内容都不一致,但是理论上来讲, Class对象内部一定存储了方法区中该类的所有方法签名,属性签名,和每个方法对应的字节码的地址。

实例和实例方法之间的关系?

看一个例子

obj.setName(“zhang san”)

在实际执行过程中等价于

setName(obj, “zhang san”)

  1. 也就是对象时作为参数传递到实例方法里面的,对象本身不含指向该类方法的指针(Class对象并不含有Class类的方法的指针,但含有表示该类的所有方法的指针)。
  2. 方法的具体实现都位于方法区中相应的代码段中。当虚拟机调用该方法时,只要将虚拟机执行引擎的PC(程序计数器)指向该方法的地址,然后将实例存入该方法的栈帧中即可。

7 Class对象有哪些功能?

7.1 反射

Class对象表示是Class类本身用于反射的对象(所以该对象的getClass方法返回它本身)或者说表示Class类本身的Class对象。

7.2 多态

我们通过以下代码来讲解Class对象在多态中的应用

package demo;
 
public class ClassObjectDemo1 {
     
    /*定义两个具有继承关系的类,两个类内部有同一个方法的不同实现*/
    public static class Person{
        public void speak(){
            System.out.println("i am a person");
        }
    }
     
    public static class Coder extends Person{
        public void speak(){
            System.out.println("i am a coder");
        }
    }
     
    /*定义了一个静态方法,静态方法会调用对应类型的speak方法*/
    public static void speakByType(Person p){
        p.speak();
    }
     
    public static void main(String[] args) {
        Person p0 = new Coder();
        speakByType(p0);
         
        Person p1 = new Person();
        speakByType(p1);
    }
}


运行结果
i am a coder
i am a person
  1. 我们定义的静态方法speakByType,显然编译器在编译这个方法的时候不能确定到底调用哪一个speak方法,需要依据对象的具体类型才能确定符号引用。
  2. 现在我们通过字节码工具重点查看一下speakByType的字节码
public static void speakByType(demo.ClassObjectDemo1$Person p) {
    /* L20 */
    0 aload_0;                /* p */
    1 invokevirtual 16;       /* void speak() */
    /* L21 */
    4 return;
}
  •  我们发现里面出现了一条字节码调用语句 invokevirtual方法。而invokevirtual指令在执行时,首先会找到当前对象的类的Class对象,然后通过该Class对象查找sepak方法,如果通过该Class对象查找到了签名一致的的sepak方法就会调用它。
  • 每个Class对象都会持有表示父类的Class对象的引用(通过Class的getSuperClass方法获取),当在子类的Class对象中没有找到签名一致的speak方法时,就从其父类的Class对象中继续查找签名一致的speak方法。
  • 显然如果还没有找到,则会沿着有继承关系的Class的路径继续向下查找,如果直到Object.class对象中还未找到就会抛出异常。

8 Class.class对象存在的意义是什么?

  • 用Class对象进行类型判断,即判断一个对象是不是Class对象还是普通对象(判断obj.getClass() == Class.class是否成立)
  • 另一种可能就是保持概念的完整性,每一个类都有一个Class对象与之对应。

假设B类继承了A类,那么B类的实例在内存中应该是什么样子的?

10 Java在运行时的内存布局

      10.1 首先看一下Class对象

         

Class类和Class对象

  1. 所谓的Class对象,是Class类的实例,而Class类是描述所有类的,比如Person类,Student类
  2. jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

创建Class对象

  1.  首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。

10.2  用一个例子来看看javaVM运行时,类、对象、Class对象、ClassLoader的关系

package demo;
 
import java.lang.reflect.InvocationTargetException;
 
 
public class ClassObjectDemo0 {
     
    /*定义两个具有继承关系的类,两个类内部都为空*/
    public static class Person{
         
    }
     
    public static class Coder extends Person{
         
    }
     
    /*哈哈,main函数抛出的异常似乎有点多*/
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InterruptedException{
         
        /*通过对象找到该类的Class对象*/
        Coder coder = new Coder();
        System.out.println(coder.getClass());
         
        System.out.println("========================");
         
        /*通过Class对象可以看出继承关系*/
        System.out.println(Coder.class);
        System.out.println(Coder.class.getSuperclass());
        System.out.println(Coder.class.getSuperclass().getSuperclass());
         
        System.out.println("========================");
         
        /*查看每个Class对象的类加载器*/
        System.out.println(Coder.class.getClassLoader());
        System.out.println(Person.class.getClassLoader());
        System.out.println(Object.class.getClassLoader());
         
        System.out.println("========================");
         
        /*每个Class对象都是Class类的实例*/
        System.out.println(Coder.class.getClass());
        System.out.println(Person.class.getClass());
        System.out.println(Object.class.getClass());
         
        System.out.println("========================");
         
        /*Class.class的getClass方法会返回它本身*/
        System.out.println(Class.class.getClass());
 
        /*产看Class对象的的父类*/
        System.out.println(Class.class.getSuperclass());
    }
}


运行结果

/*通过对象找到该类的Class对象*/
class demo.ClassObjectDemo$Coder
========================
/*通过Class对象可以看出继承关系*/
class demo.ClassObjectDemo$Coder
class demo.ClassObjectDemo$Person
class java.lang.Object
========================
/*查看每个Class对象的类加载器*/
sun.misc.Launcher$AppClassLoader@4e0e2f2a
sun.misc.Launcher$AppClassLoader@4e0e2f2a
null /*说明Object类的加载器是BootstrapClassLoader*/
========================
/*每个Class对象都是Class类的实例*/
class java.lang.Class
class java.lang.Class
class java.lang.Class
========================
/*Class.class的getClass方法会返回它本身*/
class java.lang.Class
========================
/*查看Class.class对象的父类*/
class java.lang.Object

通过上面的结果,我们可以推测这些对象,方法,加载器等在堆和栈中布局的一种可能。

 

  1. 在堆区中,我们一般的对象我们用橘色表示,Class对象用浅蓝色表示 
  2.  在堆区中,绿色箭头表示getClass方法返回的对象。
  • 显然,非Class对象(如Code对象,Person对象)的getClass方法返回这个类对应的Class对象。(Class类继承Object类,Object类定义了getClass方法,所有的Class对象也有getClass方法。)
  • Class对象它的getClass方法就会返回表示整个Class类的Class对象,即Class.class。而Class.class对象的getClass方法返回它本身。
  1. 堆区中,每个Class对象的黑色虚线都指向了方法区中表示该类的全部信息,所以我们能够通过Class对象进行反射操作。
  2. 堆区中的黑色实线表示Class对象的getSupperClass方法返回的对象,由于所有的类都继承于Object类,所以有的Class对象最终都指向于Object.clss,而Object.class没有父类。
  3. 我们想要实现反射,一般使用Class.forName方法进行类加载,forName方法本质上就是调用ClassLoadr实例的loadClass方法。推测,为了方便每个加载器查找某个类是否已加载器过,每个类加载器可能都有一张表,记录每个已加载的类和对应Class对象的地址。

11 数组与Class对象

  • 不同数据类型,不同维度的数组都对应不同的Class对象
  • 所有具有相同元素类型和维数的数组都共享该Class对象。
package javalearning;
 
public class ArrayTest {
    public static void main(String[] args){
        int[] a1 = new int[10];
        int[][] a2 = new int[5][3];
        Class<?> c1 = a1.getClass();
        Class<?> c2 = a2.getClass();
        System.out.println(c1 == c2);/*结果false*/
        System.out.println(c2.getComponentType() == c1);/*结果true*/
    }
}

12:为什么静态方法不能调用非静态方法和变量

13:静态类和非静态类程序的初始化顺序

14:参考文献

关于Class对象、类加载机制、虚拟机运行时内存布局的全面解析和推测 - nullzx - 博客园

百度安全验证

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值