Java 堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 里“几乎”所有的对象实例都在这里分配内存。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩
展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再
扩展时,Java虚拟机将会抛出OutOfMemoryError异常。原因有二:
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占有的内存。
当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
Java虚拟机栈(Java Virtual Machine Stacks)它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
这里需要关注一下入栈的过程:
如果方法里声明了基本数据类型(byte、short、int、long、double、float、char、boolean)的变量,那么它们都存储在栈中;如果方法中new了新的对象,那么会先去堆中创建该对象,然后栈中存储该对象的引用地址。如果方法中引用了已创建的对象,那么栈中存储该对象的引用地址。
如果某个线程的线程栈的内存被耗尽,没有足够的内存资源去创建栈帧,就会发生内存溢出。
例如如下代码:
public class Test {
public static void m2(){
m2();
}
public static void main(String[] args) {
m2();
}
}
上面这串代码的执行过程是:线程先执行main方法,同时会创建main方法的栈帧插入到该线程的线程栈中,当执行到m2()方法时,创建m2()方法的栈帧插入到该线程的线程栈中,执行到m2()方法里的m2()方法时,创建栈帧,插入到线程栈中,后面进行无脑创建栈帧、入栈。当创建一定数量的栈帧后,剩下的线程资源无法再创建新的栈帧
就会报StackOverflowError异常(堆栈溢出异常)(当前虚拟机栈不可以动态扩展)
异常截图:
如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
程序计数器(Program Counter Register)也被称为 PC 寄存器,是一块较小的内存空间。它可以看作是当前线程所执行的字节码的行号指示器。程序计数器的作用是记录当前线程下一条要运行的指令,这样保证了线程在切换回来时能回到正确的位置继续开始执行。
public class UserParam {
public static int a=0;
private String userName;
private String nickName;
private UserParam userParam;
public int getTest() {
return test;
}
public void setTest(int test) {
this.test = test;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
private int test;
public UserParam getUserParam() {
return userParam;
}
public void setUserParam(UserParam userParam) {
this.userParam = userParam;
}
@Override
public String toString() {
return "UserParam{" +
"userName='" + userName + '\'' +
", nickName='" + nickName + '\'' +
", userParam=" + userParam +
", test=" + test +
'}';
}
}
public static void main(String[] args) {
UserParam userParam = new UserParam();
UserParam.a=2;
UserParam userParam1 = new UserParam();
System.out.println(userParam);
System.out.println(userParam1);
}
打印结果如下:
UserParam{userName='null', nickName='null', userParam=null, test=0}
UserParam{userName='null', nickName='null', userParam=null, test=0}
public class Test {
public static void main(String[] args) {
UserParam userParam = new UserParam();
UserParam userParam1 = new UserParam();
userParam1.setTest(userParam.getTest());
System.out.println(userParam1);
userParam.setTest(10);
System.out.println(userParam1);
}
}
打印结果:
UserParam{userName='null', nickName='null', userParam=null, test=0}
UserParam{userName='null', nickName='null', userParam=null, test=0}
public class Test {
public static void main(String[] args) {
UserParam userParam = new UserParam();
UserParam userParam1 = new UserParam();
userParam1.setUserName("aaaaa");
func(userParam,userParam1);
System.out.println(userParam);
}
public static void func(UserParam userParam,UserParam userParam1){
userParam.setUserParam(userParam1);
userParam1.setUserName("bbbbbbbb");
}
}
打印结果:
UserParam{userName='null', nickName='null', userParam=UserParam{userName='aaaaa', nickName='null', userParam=null, test=0}, test=0}
UserParam{userName='null', nickName='null', userParam=UserParam{userName='bbbbbbbb', nickName='null', userParam=null, test=0}, test=0}
可以看到,随着userParam1中userName的改变,userParam中的userParam也变了。原因是栈帧中引用类型变量存储的是堆中实例对象的地址,当实例对象改变,也意味着引用类型变量改变。
public class Test {
public static void main(String[] args) {
UserParam userParam = new UserParam();
userParam.setTest(10);
UserParam userParam1 = new UserParam();
userParam1.setTest(userParam.getTest());
userParam.setTest(100);
System.out.println(userParam1);
}
}
打印结果如下:
UserParam{userName='null', nickName='null', userParam=null, test=10}
public class Test {
public static void main(String[] args) {
UserParam userParam = new UserParam();
userParam.setUserName("yhz");
UserParam userParam1 = new UserParam();
userParam1.setUserName(userParam.getUserName());
userParam.setUserName("aaaa");
System.out.println(userParam1);
}
}
打印结果:
UserParam{userName='yhz', nickName='null', userParam=null, test=null}
原因是:
关于字符串常量池的一些内幕