说到Java中堆、栈和常量池,首先还是看看他们各自存放的数据类型吧!
栈:
Java的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)也叫静态存储区。
堆区:(存放所有new出来的对象;)
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:(存放基本类型的变量数据和对象的应用,对象(new出来的对象)本身并不存在栈中,而是存放在堆中或者常量池中(字符串常量对象存放在常量池中))
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象(比如int i=1中1就是基础类型的对象)和自定义对象的引用(不是对象)而真实对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
常量池:存放基本类型常量和字符串常量。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量名,不是常量(它在堆栈中)。
进一步解析:
(指数据共享时)对于栈和常量池中的数据可以共享(具有多个引用对象),对于堆中的对象不可以共享(一个引用对象)。 (指线程共享时)比如:同一个进程的多个线程堆栈共享状况哪个描述正确?线程共享堆,但是每个线程有自己的寄存器和栈
解析: 线程占有的都是不共享的,其中包括:栈、寄存器、状态、程序计数器
线程间共享的有:堆,全局变量,静态变量,主要指共享进程的资源;
进程占有的资源有:地址空间,全局变量,打开的文件,子进程,信号量、账户信息。
栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会自动消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
而对于字符串来说,其对象的引用都是存储在栈中的,如果是编译期已经创建好(即指用双引号定义的)的就存储在常量池中,如果是运行期(new出来的对象)则存储在堆中。对于equals相等的字符串,在常量池中是只有一份的,在堆中则有多份。
关于:方法区理解
在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。
因为方法区是被所有线程共享的,所以必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加载,而另一个线程等待。方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺寸。方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,一些类也会成为垃圾。jvm可以回收一个未被引用的(没有实例对象)类所占的空间,以使方法区的空间最小。
方法区存在类成员表的理解
Java方法调用面临两个新的问题是方法重载和方法重写,方法重载使用的技术叫符号毁坏,方法重写的技术叫做动态派发。Java中的所有方法都支持重写,要实现在运行时动态确定调用的具体方法就需要一张方法表,这张方法表记录当前对象所对应的类含有的所有方法包括父类的方法。方法调用的时候需要从子类方法开始搜索这张表,如果子类未找到就到父类找,再父类。。。;符号毁坏沿用了C++的解决方法,就是给方法重命名如method+参数个数(args) 这样方法就唯一了。所以核心问题还是方法重写,也就是动态派发,也是java的性能瓶颈,因为C++只有virtual方法才会产生动态派发。
要处理好动态派发,一种可能的实现方式是专门开辟一个区,单独管理所有方法,可以按照稳定性对于该对象的所有方法(当然包括父类方法)进行稳定性排序,使相同方法聚集在一起。当方法调用时在方法区搜索的时候一次定位到相同方法的位置的起始处,不管第一个命中方法是父类还是子类,也不管此处相同方法的个数,始终执行第一个。
作者:寻寒
链接:https://www.zhihu.com/question/23599282/answer/25076568
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
举个例子吧!
Java代码
String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
如以下代码:
Java代码
int i1 = 9;
int i2 = 9;
int i3 = 9;
public static final int INT1 = 9;
public static final int INT2 = 9;
public static final int INT3 = 9;
对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。
形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。
成员变量存储在堆中的对象里面,由垃圾回收器负责回收。
如以下代码:
Java代码
class BirthDate {
private int day;
private int month;
private int year;
public BirthDate(int d, int m, int y) {
day = d;
month = m;
year = y;
}
省略get,set方法………
}
public class Test{
public static void main(String args[]){
int date = 9;
Test test = new Test();
test.change(date);
BirthDate d1= new BirthDate(7,7,1970);
}
public void change1(int i){
i = 1234;
}
对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化:
1. main方法开始执行:int date = 9;
date局部变量,基础类型,引用和值都存在栈中。
2. Test test = new Test();
test为对象引用,存在栈中,对象(new Test())存在堆中。
3. test.change(date);
i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。
4. BirthDate d1= new BirthDate(7,7,1970);
d1为对象引用,存在栈中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。 day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
5.main方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。