java中内存分为3块分别为:栈、堆、方法区(实际上方法区存在在堆当中也可以说内存分为栈、堆,但是方法区比较特殊所以单独拿出来说)。
1)栈
1、表示方法执行的内存模型,每一个方法被调用时都会创建一个栈帧(栈帧存储在栈当中),栈帧存储局部变量,操作数,方法出口等。
2、jvm会为每一个线程都创建一个栈,用来存放该线程执行方法的信息。
3、栈属于线程私有的,不能实现两个线程的共享
4、栈的存储特性是先进后出,后进先出!
5、栈是系统自动分配的,速度快!栈是一个连续的内存空间!
6、存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
看到这时我想到了一个问题:javaweb中假如从网页上发起了2次请求,这是两个线程,还是属于一个线程?
上网询问得知:同一用户2次请求一个线程,不同用户请求两个线程。(同一个用户可以理解为同一台电脑)
2)堆
1、堆用于存储创建好的对象(数组也是对象)
2、jvm中只有一个堆,被所有线程共享!
3、堆是一个不连续的内存空间,分配灵活,速度慢!、
3)方法区(静态区)
1、jvm只有一个方法区,被所有线程共享!
2、方法区实际上也在堆中,只存储类、常量相关的信息。
3、用来存放程序中永远不变或唯一的内容。(类信息[java代码],class对象,静态变量,字符串常量)
下面是一个关于java内存的代码
public static void main(String[] args) {
User u1=new User("小明");
User u2=new User("小明");
System.out.println(u1.getUserName()==u2.getUserName());
String a=new String("小明");
System.out.println(u1.getUserName()==a);
String b=new String();
b="小明";
System.out.println(a==b);
System.out.println(b==u1.getUserName());
}
输出打印
true
false
false
true
原理:
1)String创建时,如果是通过=赋值方式创建的话,就会先查询常量池中是否存在该值,如果不存在,在常量池中放入该值,并且栈中的变量引用常量池。
2)如果是new String("小明")这种方式创建的话,那么会将字符串存储在堆中,栈中的变量引用堆。
public static void main(String[] args) {
String x = "ab";
change(x);
System.out.println(x);
}
public static void change(String x) {
x = "cd";
}
原理:
开始我看这段代码时,认为打印cd(实际输出ab),我犯了2个错,
1、我错误的认为形参x和变量x为同一个(实际上形参x和变量x是两个)
2、因为认为String类型为引用变量,那么String x和形参x引用同一个常量池的字符串(指向同一个引用)。那么形参x被改变,那么String 相应的被改变。
以下详细的解释第二种错误:
String类型虽然为引用类型但是String类型比较特殊。
String创建时,如果是通过=赋值方式创建的话,就会先查询常量池中是否存在该值,如果存在直接引用常量池,如果不存在,在常量池中放入该值,并且栈中的变量引用常量池。(这样的String它在修改值时,实际上并没有在原来的值上修改,而是相当于在创建[存在引用,不存在常量池种创建再引用]),因此通过=赋值方式的String可以看作和基本类型一样,而通过new String("小明")这种方式创建的可以看作和对象一样。
3、所以以上的代码其实是这样的,栈中存在了2个变量x,String x开始把内存地址拷贝给了形参x(此时它们是一致的),但运行到x="cd"时,它在常量池中创建了一个字符串cd,并且将内存地址引用给了形参x,那么此时形参x和String x并没有引用同一个地址
(形参x引用常量池中的cd,而String x引用常量池中的ab)
而且 change(x);方法过后形参x的生命周期已过(被gc回收),实际上打印的是String x,当然是ab!
关于String使用连接符
String a="小明";
String b="小明";
String c=a+b;
String d="小明小明";
System.out.println(c==d);
1、我错误的认为这里会输出true,实际上输出false,错误理解如下
String a先在常量池中创建了"小明",那么a引用常量池中小明的地址。String b时发现常量池中有"小明",那么ba引用常量池中小明的地址,现在a==b为true。(错误理解开始)String c发现"小明小明"常量池中没有,那么在常量池中创建"小明小明",c的引用指向常量池中的小明小明,String d发现发现"小明小明"常量池中存在,那么d的引用指向常量池中的小明小明。所以c==d为true。
上网查询网友,然后纠正了自己的错误理解,从开始错误的地方开始解释:
String c=a+b;这行代码并没有在常量池当中创建,它是在堆的内存中创建(+号连接符中有变量创建的String就在堆内存中,而不在常量池中)。而d发现常量池中没有"小明小明"那么创建,此时c和d的内存地址当然不同,那么输出false。
如果将以上的代码改为如下代码:
final String a="小明";
String c=a+"小明";
String d="小明小明";
System.out.println(c==d);
用final修饰a变量的话会输出true,因为
final修饰后就是常量了(不可修改的)。当对这样的字符串常量调用+运算的时候,jvm会在编译的时候进行替换,比如这儿 c = a +"明";因为a是final修饰的,所有会替换为小明,c就相当于c="小明"+“明”
那么String c会在常量池中创建,那么c和的地址相同,那么c==d为true
基本类型的参数传递:
传递的是值的副本,副本的传递不会影响到原件。
引用类型参数的传递:
传递的是对象的地址,副本和原参数都指向了地址。那么改变副本指向对象的值,那么原参数的值也发生了改变。
引用类型中String比较特殊(在上面已经详细说了!)
还有引用类型中的八大类型的包装类也比较特殊(一般情况下可以看作基本类型,但也不一样)
对于包装类说的很好的链接
https://www.cnblogs.com/dolphin0520/p/3780005.html
https://www.cnblogs.com/guodongdidi/p/6953217.html(Integer之间的比较)
对于存储位置很好的链接
https://www.iteye.com/problems/96058
int[] a=new int[0];
一个长度为0的数组其实也在堆内存中开辟了空间!
2019-01-09