设为首页 加入收藏

TOP

关于Class对象、类加载机制、虚拟机运行时的内存布局的全面解析和推测(三)
2017-10-10 12:31:24 】 浏览:8257
Tags:关于 Class 对象 加载 机制 虚拟 行时 内存 布局 全面 解析 推测
lassLoader.class。

 

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

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

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

 

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

obj.setName(“zhang san”)

在实际执行过程中等价于

setName(obj, “zhang san”)

也就是对象时作为参数传递到实例方法里面的,对象本身不含指向方法的指针(这里说的是非Class对象)。方法的具体实现都位于方法区中相应的代码段中。当虚拟机调用该方法时,只要将虚拟机执行引擎的PC(程序计数器)指向该方法的地址,然后将实例对象传递给该方法即可。通过实例直接调用方法时,实际上没有,也没有必要通过Class对象。

下面的示例表示了,锁住Person.Class对象不能阻止其它线程的代码创建该对象的实例,并调用实例方法。

package javalearning;

public class ClassLockTest {
	
	public static class T1 extends Thread{
		private Class<?> cls;
		private boolean done;
		public T1(Class<?> cls){
			this.cls = cls;
		}
		
		@Override
		public  void run() {
			synchronized(cls){
				while(!done){
					
				}
			}
		}
		
		public void done(){
			done = true;
		}
	}
	
	public static void main(String[] args) throws InterruptedException{
		/*我们先让线程t1锁住Person.class对象,然后在主线程中创建该对象的实例,并调用toString方法*/
		Class<?> cls = Person.class;
		T1 t1 = new T1(cls);
		t1.start();
		
		while(!t1.isAlive()){
			System.out.println("t1 is not alive");
			Thread.sleep(500);
		}
		
		Person p = new Person();
		System.out.println(p);
		t1.done();
		System.out.println("over");
	}
}

 

运行结果

zx 18
over

 

4.3 Class对象有哪些功能?

1)反射(关于反射的使用会在后续博客中讲解)

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(coder);
		
		Person p1 = new Person();
		speakByType(person);
	}
}

 

运行结果

i am a coder
i am a person

 

我们定义的静态方法speakByType,显然编译器在编译这个方法的时候不能确定到底调用哪一个speak方法,需要依据对象的具体类型才能确定符号引用。

现在我们通过工具重点查看一下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方法获取),Object.class除外,自己想想为啥?否则从其父类的Class对象中继续查找签名一致的speak方法。显然如果没有找到,会沿着有继承关系的Class的路径继续向下查找,如果直到Object.class对象中还未找到就会抛出异常。

 

3)instace of和向上转型

当我们判断某个对象是否属于某个类时,比如 a instance of A,显然只要判断

a.getClass() == A.class && a.getClass().classLoader() == A.classLoader()即可,如果不满足就沿着getSuperClass的路径继续向下找,如果直到Object.class还不满足条件就返回false。同理在运行时,我们还能依据Class对象判断向上转型是否正确。

 

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

我们通常不会通过Class.class对象来间接访问forName方法和其它相应方法,而是直接使用该类的方法。所以一种可能的情况就是利用Class对象进行类型判断,即判断一个对象是不是Class对象还是普通对象(判断obj.getClass() == Class.class是否成立)。另一种可能就是保持概念的完整性,每一个类都有一个Class对象与之对应。

 

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

image

 

java语言的设计者考虑到对象向上转型等问题,每一个类的数据成员显然要按照继承关系的先后顺序排列,同时考虑执行效率,还存在数据对齐等问题。

 

4.6 Jav

首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇设计模式精要 下一篇没有了

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目