在java里当一个对象O被创建时,它就被放在堆里,当GC运行时,当发现没有任何引用指向O,O就会被回收以腾出空间
一个对象被回收:
a.没有任何引用指向该对象
b.GC被运行
在java中,对于简单的对象,当调用它的方法执行完毕后,指向它的引用会被从栈中popup,所以它就能在下一次GC的时候被回收
堆栈
从堆和栈的功能和作用来通俗的比较, 堆主要用来存放对象的,栈主要是用来执行程序的 .
栈
在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
在C、C++中,所有的方法调用都是通过栈中进行的,所有的局部变量,形式参数都是从栈中分配内存空间的,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁。
为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程 序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时
这样的模式速度最快
堆
而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低
优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性
面向对象的多态性,堆内存分布是不可少的,多态变量所需的存储空间,只有在运行时创建了对象之后才确定的
JVM中的堆和栈
JVM(Java Virtual Machine)是基于堆栈的虚拟机,JVM为每个新创建的线程都分配一个堆栈,对于java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位来保存线程的状态,JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作
某个线程正在执行的方法为此线程的当前方法,当前方法使用的帧称为当前帧,当线程激活一个java方法,JVM就会在线程的java堆栈里新压入一个帧,这个帧就成了当前帧,在此方法执行期间,这个帧用来保存参数,局部变量,中间计算过程和其他数据
java中的堆栈:堆栈(stack)是操作系统在建立某个进程或线程时(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性
每一个java应用都唯一对应一个jvm实例,每一个实例唯一对应一个堆,应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享
java中分配堆内存是自动初始化的,所有的对象的存储空间都是在堆中分配的,但对象的引用却是在堆栈中分配,建立一个对象时,从两个地方都分配内存,在堆中分配的内存实际建立这个对象,在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)。
JAVA内存分配
java程序运行时的结构分成:方法区、栈内存、堆内存、本地方法栈
方法区存放装载的类数据信息:
.基本信息:每个类的全限定名(就是类名全称,带包路径的用点隔开,例如: java.lang.String。)、每个类的直接超类的全限定名、该类是类还是接口、该类型的访问修饰符、直接超类的全限定名的有序列表
.每个类已装载的详细信息:运行时常量池、字段信息、方法信息、静态变量、到类classloader的引用、到类class的引用
栈内存:
Java栈内存由局部变量区、操作数栈、帧数据区组成,以帧的形式存放本地方法的调用状态(包括方法调用的参数、局部变量、中间结果……)
堆内存:
堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理
本地方法栈内存:
Java通过Java本地接口JNI(Java Native Interface)来调用其它语言编写的程序,在Java里面用native修饰符来描述一个方法是本地方法
String的内存分配:
String是一个特殊的包装类数据,由于String类的值不可变性,当String变量需要经常变换其值时,应该考虑使用StringBuffer或StringBuilder类,以提高程序效率
java中运行时的内存结构
1.方法区
方法区是系统分配的一个内存逻辑区域,是JVM在装载类文件时用于存储类型信息的(类的描述信息)
存放的信息包括:
(1).类的基本信息:每个类的全限定名称、每个类的直接超类的全限定名称、该类是类还是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表
(2).已装载类的详细信息:
a.2运行时常量池:在方法区中,每个类型都对应一个常量池,存放该类型所用到的所有常量,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用)
b.字段信息:存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符。字段名称指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串,如private A a=null;则a为字段名,A为描述符,private为修饰符
c.方法信息:类中声明的每一个方法信息,包括方法名,返回值类型,参数类型,修饰符,异常,方法的字节码(在编译的时候,就已经将方法的局部变量、操作数栈大小等确定并存放在字节码中,在装载的时候,随着类一起装入方法区。)
在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。 |
d.静态变量:就是类变量,类的所有实例都共享,在方法区有个静态区,静态区专门存放静态变量和静态块
e.到类classloader的引用:到该类的类装载器的引用
f.到类class的引用:虚拟机为每一个被装载的类型创建一个class实例,用来代表这个被装载的类
2.java栈
JVM栈是程序运行时单位,决定了程序如何执行,数据如何处理
每个线程都会有与之对应的JVM栈
对方法的调用
java栈内存以帧的形式存放本地方法的调用状态,包括方法调用的参数,局部变量,中间结果(方法都是以方法帧的形式存放在方法区),每调用一个方法,就将该方法的方法帧压入java栈,称为当前方法帧,当调用结束时,就弹出该帧。
在方法中定义一些基本类型的变量和引用常量都在方法的栈内存中分配,当一段代码块定义一个变量时,java就会在栈中为这个变量分配内存空间,当超过变量的作用域后(方法执行结束),java会自动释放掉为该变量分配的内存空间,该空间可以立即被另作他用,同时,因为变量被释放,该变量对应的对象也就失去了引用,变成了可以被GC对象回收的垃圾
由此成员变量和局部变量的区别:
局部变量在方法内部声明,当方法执行结束,内存立即被释放
成员变量,只要该对象还在,某一个方法执行完毕,还是存在
从系统的角度说,声明局部变量有利于内存空间的更高效利用,方法运行完就被回收
成员变量可以用于各个方法间数据共享
java栈内存的组成:局部变量区,操作数栈,帧数据区
(1).局部变量区以一个字长为单位,每个数组元素对应一个局部变量的值,调用方法时,将方法的局部变量组成一个数组,通过索引来访问,非静态方法,加入一个隐含的引用参数this,该参数执行调用这个方法的对象,静态方法没有this参数,对象无法调用静态方法
方法设计为静态和非静态:
静态方法适合那些工具类中的工具方法,这些类只是用来实现一些功能,也不需要产生对象,通过设置对象的属性来得到各个不同的个体
(2).操作数栈也是一个数组,但是通过栈操作来访问,操作数是那些被指令操作的数据,当需要对参数操作时如a=b+c,就将即将被操作的参数压栈,如将b 和c 压栈,然后由操作指令将它们弹出,并执行操作。虚拟机将操作数栈作为工作区
(3).帧数据区:处理常量池解析,异常处理
3.java堆
java堆是一个运行时的数据区,用了存储数据的单元,存放通过new关键字新建的对象和数组,对象从中分配内存
在堆中声明的对象时不能直接访问的,必须通过在栈中声明的指向该引用的变量调用,引用变量相当于为该对象或数组起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组和对象
引用类型变量和对象的区别:
声明的对象是在堆中初始化的,用来存储数据,不能直接访问
引用类型变量是保存在栈中的,用来引用堆中对象的符号(指针)
堆和栈的比较
(1).存放数据
栈中存放的是基本数据类型的变量或者引用类型的变量
堆中存放的是对象或者数组对象
在栈中引用变量的大小为32位,基本类型为1-8个字节
但对象的大小和数组的大小是动态的,这也决定了堆中数据的动态性,它是在运行时动态分配内存的,生存周期也不必在编译时确定,java的垃圾回收器会自动回收不再使用的数据
(2).数据共享
在单个线程中,栈中的数据可以共享
int a=3;
int b = 3;
编译器处理int a = 3;首先会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3放进来,然后a指向3,接着处理int b = 3,在创建完b的引用变量后,在栈中已经有了3,所以直接将b指向3,这样就出现了a和b同时指向3的情况
integer a = new Integer(3);integer b = new Integer(3)
这时,先在栈中创建一个a引用变量,然后在堆内存中实例化一个对象,并将变量a指向这个实例化的对象,然后再在栈中创建变量b的引用,然后再堆内存中实例化一个对象,降变量b指向这个实例化的对象
(2).在进程的各个线程之间,数据的共享通过堆来实现
例:那么,在多线程开发中,我们的数据共享又是怎么实现的呢?
如图所示,堆中的数据是所有线程栈所共享的,我们可以通过参数传递,将一个堆中的数据传入各个栈的工作内存中,从而实现多个线程间的数据共享
(多个进程间的数据共享则需要通过网络传输了。)
(3).程序设计的角度
从软件设计的角度看,JVM栈代表了处理逻辑,而JVM堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。
(4).值传递和引用传递的真相
a.程序永远都是在jvm栈中进行,参数传递时,只存在传递基本类型和对象引用,不会直接传递对象本身
传引用的错觉如何产生
在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的
但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象
如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了
从某种意义上来说对象都是由基本类型组成的。
可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。 |
其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别
面向对象的引入,只是改变了我们对待问题的思考方式,而更接近于自然方式的思考
当我们把对象拆开,其实对象的属性就是数据,存放在JVM堆中;而对象的行为(方法),就是运行逻辑,放在JVM栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。