一、内存分配区域如下:
1. 内存分配时涉及的区域:
- 寄存器:在程序中无法控制;
- 栈:存放基本类型的数据和对象的引用,但是对象本身不存放在栈中,而是存放在堆中;
- 堆:存放用new产生的数据;
- 静态域:存放在对象中用static定义的静态成员;
- 常量池: 存放常量。
2. 内存分配中的栈和堆
1. 栈
在函数中定义的一些基本类型的变量数据,还有对象的引用变量都在函数的栈内存中分配。当在一段代码中定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,java会自动释放掉为该变量分配的内存空间。
栈内存,是java程序的运行区,是在线程创建时创建的。它的生命周期跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程结束,该栈就结束了。
栈中的数据是以栈帧(stackframe)的格式存在的。栈帧是一个内存区块,是一个数据集,是有关方法(method)和运行期数据的数据集。当一个方法A被调用时就会产生一个栈帧F1被压入到栈中,A方法再调用B方法,就会产生栈帧F2被压入栈,执行完毕后,先弹出F2,在弹出F1。
2. 堆
堆内存用来存放由关键字new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾收集器来管理。
在堆中创建一个对象后,还可以在栈中定义一个变量,让这个变量的值等于对象在堆内存中的首地址,栈中的变量就是对象的引用,相当于java中的指针。当程序运行到对象所在的语句块之外,对象占据的内存不会自动释放,在没有引用变量指向它时,随后一个不确定的时间被垃圾收集器回收掉。
3.常量池(constantpool)
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(int,long等)和对象型(string、数组等)的常量值(final),还包含一些以文本形式出现的符号引用。
类和接口的全限定名;
字段的名称和描述符;
方法和名称的描述符;
在程序执行时,常量池会存储在MethodArea(方法区)中,而不是堆中。
一个java虚拟机实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需要把类、方法、常变量(const修饰的变量)放到堆内存中,堆内存分为三部分:
1)PermanentSpace永久存储区
永久存储区是一个常驻内存区域,用于存放jdk自身所携带的ClassInterface的元数据。也就是说它存储的是运行环境必需的类信息,被装载到此区域的数据不会被垃圾收集器回收,关闭java虚拟机才会释放此区域占的内存。
2)YoungGeneration Space新生区
新生区是类的诞生、成长、消亡的区域,新生区又分两部分:伊甸区(Edenspace)和幸存区(Survivorspace),所有的类都是在伊甸区被new出来的。幸存区有两个:0区(survivor0 space)和1区(survivor1space)。当伊甸区的空间用完时,程序再创建对象,虚拟机将对伊甸区进行垃圾回收,将伊甸区中的不再被引用的对象进行销毁,然后将伊甸区中的剩余对象移动到幸存0区,如果幸存0区也满了,将对该区进行垃圾回收,然后移动到1区,如果1区也满了,就会移动到老年区。
3)Tenuregeneration space老年区
老年区保存从新生区帅选出来的java对象。
4.Java虚拟机中为什么分堆区,栈区?
1)从软件的角度,栈区代表了处理逻辑,而堆代表了数据。分开,使得处理逻辑更为清晰,体现了模块化的思想。
2)虚拟机堆、栈的分离,使得堆中的内容可以被多个虚拟机栈共享(也可以理解为多个线程访问同一个对象,因为虚拟机栈是随着线程的创建而创建的),这种共享的益处很多,一方面提供了一种有效的数据交互方式(如共享内存);另一个方面,堆中的共享常量和缓存可被多有虚拟机栈访问,节省了空间。
5. 堆和栈的合作
堆是一个运行时数据区,类的对象从中分配空间,堆的优势是可以动态地分配内存大小,生存期不必事先告诉编译器,但是缺点是,由于在运行时动态分配内存,存取速度慢。
栈的优势是存取速度比堆快,仅次于寄存器,缺点是栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中存放一些基本类型的变量数据和对象引用。
栈有一个重要特性,就是栈中的数据可以共享。
Int a =3;
int b =3;
编译器先处理inta =3;首先会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没有,就将3存放进来,然后将a指向3。接着处理intb =3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,a与b均指向3。如果再有语句a=4;编译器会重新搜索栈中是否有4值,如果没有,将4存放进来,并将a指向4,如果已经有了,直接将a指向这个地址。因此a的改变不会影响到b的值。
这种数据的共享与两个对象的引用同时指向一个对象的共享是不同的,因为这种情况下a的修改不会影响到b,它是由编译器完成的,有利于节省空间。而一个对象的引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
Java中,String是一个特殊的包装类数据,有两种创建方式:
String str = newString(“abc”);
String str = “abc”;
第一种用new创建对象,是存在堆中,没调用一次会创建一个新的对象。
第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池里查找有没有“abc”,如果没有,将“abc”放进字符串常量池,并让str指向“abc”,如果有,直接让str指向“abc”。
6. 运行时的数据区域
所有线程共享方法区和堆;
虚拟机栈、本地方法栈和程序计数器是线程隔离的数据区。
1) 程序计数器(ProgramCounter Register)
程序计数器是一块较小的内存空间,作用相当于当前线程所执行的字节码的行号指示器。在java虚拟机概念里,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令,很多基础功能都需要依赖这个计数器来完成,如分支,循环,跳转等。
每条线程都需要一个独立的程序计数器,并且各条线程之间的计数器互不影响,能够独立存储。
2) Java的虚拟机栈VMStack
虚拟机栈是类中方法的执行过程的内存模型,与程序计数器一样,虚拟机栈也是线程私有的,它的声明周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stackframe)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。
栈帧是虚拟机栈的栈元素。对于活动线程中栈顶的栈帧,成为当前栈帧,这个栈帧所关联的方法称为当前方法,正在执行的字节码指令都只针对当前栈帧进行操作。
通常把java内存分为堆内存(heap)和栈内存(stack),其中的栈就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
局部变量表存放了编译期可知的各种基本数据类型、对象引用、返回地址类型。
3) Java堆
堆是类实例和数组的分配空间,是多有线程共享的内存区域。堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
4) 方法区
方法区是虚拟机启动时创建,是多有线程共享的内存区域。用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
5) 运行时常量池
运行时常量池(RuntimeConstantPool)是方法区的一部分。用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
二、 Java中static、final、static final的区别
1. final:
final可以修饰:属性,方法,类,局部变量(方法中的变量)
final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。
final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。
final修饰的属性表明是一个常数(创建后不能被修改)。
final修饰的方法表示该方法在子类中不能被重写,final修饰的类表示该类不能被继承。
对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值)。
2.static:
static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)
static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。
static修饰的属性所有对象都只有一个值。
static修饰的属性强调它们只有一个。
static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等
static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。
static不可以修饰局部变量。
3.static final和final static:
static final和final static没什么区别,一般static写在前面。
4.static final:
static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用