堆、栈、常量池、静态区

堆区:(存放所有new出来的对象)

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 

2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

栈区:(存放基本类型的变量数据和对象的引用,对象(new出来的对象)本身并不存在栈中,而是存放在堆中或者常量池中(字符串常量对象存放在常量池中))

1.每个线程包含一个栈区,栈中只保存基础数据类型的对象(比如int i=1中1就是基础类型的对象)和自定义对象的引用(不是对象)而真实对象都存放在堆区中 

2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 

3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

常量池:存放基本类型常量和字符串常量。

方法区:

1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 

2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量名,不是常量(它在堆栈中)。

 

 

进一步解析:

 (指数据共享时)对于栈和常量池中的数据可以共享(具有多个引用对象),对于堆中的对象不可以共享(一个引用对象)

eg: int i1=9; int i2=9; ====>只有一个9存放在栈中,i1,i2也存放在栈中,共享一个9         

(指线程共享时)比如:同一个进程的多个线程堆栈共享状况哪个描述正确?线程共享堆,但是每个线程有自己的寄存器和栈

解析:线程占有的都是不共享的,其中包括:栈、寄存器、状态、程序计数器

线程间共享的有:堆,全局变量,静态变量,主要指共享进程的资源

进程占有的资源有:地址空间,全局变量,打开的文件,子进程,信号量、账户信息。

栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。

而对于字符串来说,其对象的引用都是存储在栈中的,如果是编译期已经创建好(即指用双引号定义的)的就存储在常量池中,如果是运行期(new出来的对象)则存储在堆中。对于equals相等的字符串,在常量池中是只有一份的,在堆中则有多份。

关于:方法区理解

在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。 

因为方法区是被所有线程共享的,所以必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加载,而另一个线程等待。 

 

方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺寸。 

 

方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,一些类也会成为垃圾。jvm可以回收一个未被引用的(没有实例对象)类所占的空间,以使方法区的空间最小。 

 

方法区存在类成员表的理解

Java方法调用面临两个新的问题是方法重载和方法重写,方法重载使用的技术叫符号毁坏,方法重写的技术叫做动态派发。Java中的所有方法都支持重写,要实现在运行时动态确定调用的具体方法就需要一张方法表,这张方法表记录当前对象所对应的类含有的所有方法包括父类的方法。方法调用的时候需要从子类方法开始搜索这张表,如果子类未找到就到父类找,再父类。。。;符号毁坏沿用了C++的解决方法,就是给方法重命名如method+参数个数(args) 这样方法就唯一了。所以核心问题还是方法重写,也就是动态派发,也是java的性能瓶颈,因为C++只有virtual方法才会产生动态派发。

 

要处理好动态派发,一种可能的实现方式是专门开辟一个区,单独管理所有方法,可以按照稳定性对于该对象的所有方法(当然包括父类方法)进行稳定性排序,使相同方法聚集在一起。当方法调用时在方法区搜索的时候一次定位到相同方法的位置的起始处,不管第一个命中方法是父类还是子类,也不管此处相同方法的个数,始终执行第一个。

 

猜你喜欢

转载自blog.csdn.net/sullivan_jia/article/details/81843364
今日推荐