JVM笔记

准备工作 :java对象的创建过程(内存分析),首先准备如下Pet对象,这个类中默认有一个无参构造器,并在main函数中调用Pet中的无参构造方法,生产了一个Pet对象,此时,这个对象的属性是没有值的,然后我们分别给它的name和age进行了赋值,并调用了它的shout()方法,这是从表面可以看到的,那么它在内存当中是如何执行的?

 

首先,我们要知道,程序一运行,它需要加载一些程序要运行的模板,所以第一步,它肯定会把我们Application,这个类的一些代码信息放到我们的内存中来,比如它首先加载Application这个类到方法区中,那这个类里面有个方法叫做main方法,然后它还有一些字符串,我们把它一般叫做常量,然后它里面其实一般有个常量池的,来专门存放一些常量,然后这个常量里面,现在有个什么东西,叫 旺财 这样一个东西,当然,还有一个age=3,这个3就不是常量了,是一个int类型的数字,OK,这是咱们的main方法,然后,走到这个地方,加载完Application类,然后现在要执行运行一下,是不是要执行main方法,我们说了, 这个main方法都在哪里面,那main方法是在栈里面的最底下,然后因为栈里面的所有东西结束的时候,那main方法一开始执行在最底下,当main方法结束,它便弹出去的时候,栈结束了,其实这个程序,差不多也就结束了。

然后,说一下main方法执行的时候,它走到了第一步,那我们在Pet dog = new Pet();,这里打一个断点,边看debuger,边来调试;那么它第一步首先new了一个Pet,它就走到Pet这个类中来,所以它一旦new的话,它肯定要去加载Pet这个类,加上Pet类之前并没有加载过,那现在肯定会把Pet这个类,也加载到方法区中:

 

 

 

那这个宠物类Pet现在就加载进来了,加载进来,这个宠物类,它有一些它自己的一些属性,什么name,age,shout方法,3个东西;那现在就把这Pet类也加载进来,Pet类里也有个常量池,它的常量池里面,也有一些字符串,但是我们这个shout方法中的字符串,是输出的,叫了一声,其实它并不算常量池里的,所以先把常量池这个地方先简单的忽略掉,以便于理解,那现在这个Pet类就有了,就相当于我们在 Application类中,new Pet的时候, 它就进入了Pet这个类,进入了Pet这个类,那我们看下它里面的值,当前name属性等于null,age等于0,所以说,我们这个时候,加载到方法区中的Pet类中的属性,它是肯定没有值的,因为它都是默认值,String类型默认等于null,int类型默认等于0,那现在对象的模板加载进方法区里来了,加载进来之后,main方法里是不是要为Pet这个类,生成一个具体对象,通过方法区中的这个Pet类的模板,生成一个Dog对象,所以说,当等于dog的时候,又在栈中又生成了一个变量,比如说就叫dog,那这个dog是放在栈里面的,然后栈里面,它真正new出来的东西,它在堆里面会生成;

 那dog是在栈里面的,它只是一个变量名字,是一个引用,或者叫变量名;然后真正的对象在哪个位置呢,是在咱们堆里面的,通过方法区中的这个Pet模板,去new了一个Pet,比如堆里这个地方的Pet,我们给它简单写个内存地址,叫0x0001,假设是这个,然后我们栈中的dog变量,是不是就引用到了这个0x0001,堆中的这个地址,那现在我们栈中的dog,就指向了堆中的Pet,它有个地址叫0x0001,然后现在的话,默认的name是不是等于null,age=0,然后它还有自己的一个方法,这个方法叫做shout(),它这个方法其实new的时候,它去调用了方法区中,Pet模板里的shout()方法,它是这样一个关系,有了这个shout,它是不是会"叫了一声",那它就有一些基本的操作,那它这个模板就差不多了。

 

 然后咱们第一个线,就走完了,就new Pet(),这一步就走完了,它其实进了无参构造的时候,它就做了这个事情。 

那做完之后,我们现在main方法中,做一件事情,把dog.name,是不是等于旺财啊,相当于给这个name要赋值了,赋值了方法区里的Application类中,这个常量池里的旺财,它先做了一个这样的事情,它把这个方法区中常量池中旺财,丢给了这堆中的这个name,于是乎这个name才有了值啊,是这么一个过程。

 

然后堆中的这个name, 现在 这个地方就叫做旺财,ok,那这个旺财有了,age是不是等于3,然后,它把3也赋值给堆中的这个age,ok,那age也有值了,那么shout()方法,堆里面的这个new出的Pet,它是调用了我们下面方法区里面Pet当中的shout()方法,因为方法区里的Pet的shout() 和,堆中new pet里的shout,这两个方法是一样的,它并没有传递任何的参数,那这样的话,我们现在走到这一步,在main方法中调用shout()方法这一步,那所有的值就都赋完了呀!

 

所有的值赋完了,那么这个对象,它就有这些值,它在堆中就有这些值了,然后我们就可以去使用这个堆中new出的Pet了,使用它的时候,它就存在这个旺财跟age=3了。

那我们再来做一件事情,我们现在new了一个dog,那我们再来new一个,比如说new了一个猫cat,Pet cat = new Pet();,那我们现在有只小猫了,对不对,那这个猫cat,它怎么在内存里面存在呢,其实,它是在栈里面,又加了一个引用变量名,叫cat,引用变量名,那它就是个引用,它引用的,是要指向真实的,那cat,它是不是等于new Pet(),所有它又去堆中new了一个Pet, 那它new这个Pet的时候,比如它是0x0002,这是堆中的第二个对象了吧,它的name默认new的时候,又是null,age=0,那这样的话,第二个对象是不是就有了,那cat就指向了,这第二个Pet对象,这两个dog和cat所指向的堆中的对象,就是完全不一样的对象了。为什么,我们说类是一个模板new出来的对象,就在栈中,这个栈中的对象,它就是一个引用名,要是给cat所指向的堆中的Pet对象,那么猫就有自己的名字了,它就是这么一步一步来做的。 

 

 

 

 那在引用变量,它本身在栈里,就是个引用变量名,它真正指向的是堆中的具体的对象,只是我们通过栈,给它起了一个名字,相当于本质上它们还是一样的东西,那面向对象的 封装继承多态,也可能让同一个变量名,或者同一个类型,它也可能有不同的一些状态。然后,这个方法区的最后,我还留了一个静态方法区,那被static声明的东西, 它就是在这个地方的,我们所有带static的关键字的,一进来就加载在这个地方,所以说它是一开始就加载的,和类一起加载,所以说,如果如果想为了方便大家去调用的,我们都会用static修饰,也就是放在这个静态方法区,因为类一加载,它就加载了,那么类一加载,这个静态方法区里的东西就加载了,那它会有一个什么样的效应呢,是不是所有对象,在堆中的对象,都可以去用到这个静态方法区里的东西,就是无论是哪个对象,都是可以直接调用静态方法区中的东西,而无需去new出一个新的实例。

 

 

 那另外需要说明的是,这个关于堆,栈,以及方法区,其实堆和方法区是一个整体,我们左侧的叫做栈,然后右边的一系列都是堆,然后堆里面有个特殊的区域,叫做方法区, 这个方法也是属于堆的。

 

 

 堆一般是存放我们具体创建出来的对象,而栈里面,都是一些方法,加一些变量的引用,那现在我们就可以知道,为什么我们同时new的都是Pet,但是会产生不一样对象,因为它是存在于堆中不同区域,就是它的地址可能不同,然后它在栈中的引用变量名也不同,所以造成了这种都是new的Pet对象,确实两个不同的对象。

  

 

 

 

posted @ 2021-02-24 23:54  HarryVan  阅读(48)  评论(0编辑  收藏  举报