《Android虚拟机》--内存分配策略

时间:2022-12-23 18:51:46

No1:

Java在内存分配时会涉及到以下区域:

寄存器:我们在程序中无法控制

:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中

:存放用new产生的数据

静态域:存放在对象中用static定义的静态成员

常量池:存放常量

非RAM存储:硬盘等永久存储空间

No2:

栈中的数据都是以栈帧(Stack Frame)的格式存在的。栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集。

栈帧中主要保存如下3种数据:

1)本地变量(Local Variables):包括输入参数和输出参数以及方法内的变量

2)栈操作(Operand Stack):记录出栈、入栈的操作

3)栈帧数据(Frame Data):包括类文件、方法等

No3:

堆内存用来存放由关键字new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理

No4:

引用变量是普通的变量,定义时再栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它时,才变为垃圾,不能再被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是Java比较占内存的原因。实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针。

No5:

常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型和对象型(如string及数组)的常量值(final)还包含一些以文本形式出现的符号引用。

1)类和接口的全限定名 2)字段的名称和描述符 3)方法的名称和描述符

虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。

No6:

一个Java虚拟机实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:

1)Permanent Space 永久存储区

永久存储区是一个常驻内存区域,它存储的是运行环境必需的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭Java虚拟机才会释放此区域所占用的内存。

2)Young Generation Space 新生区

3)Tenure generation space 养老区

No7:

是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

No8:

的优势是存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

No9:

Java虚拟机内存模型中定义的访问操作与物理计算机处理的基本一致。Java通过多线程机制使得多个任务同时执行处理,所有的线程共享Java虚拟机内存区域main memory,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存复制到工作内存,进而交由线程处理(操作码+操作数)。

No10:

运行时的数据区域:程序计数器(Program Counter Register)、Java的虚拟机栈(VM Stack)、本地方法栈(Natvie Method Stack)、Java堆(Java Heap)、方法区、运行时常量池、直接内存

No11:

程序计数器

在Java虚拟机的概念模型里,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。为了在线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,并且各条线程之间的计数器互不影响,能够独立存储,我们称这类内存区域为线程私有的内存。

此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

No12:

Java的虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

下面列出的两种异常情况与Java栈相关

1)如果线程请求的栈深度大于虚拟机所允许的深度,则Java虚拟机将抛出*Error异常

2)如果虚拟机栈可以动态扩展,但是无法申请到足够的内存来实现扩展,或者不能得到足够的内存为一个新线程创建初始Java栈,则Java虚拟机将抛出OutOfMemorError异常

No13:

本地方法栈:

本地方法栈中执行的是非Java语言编写的代码,例如C或C++,它们通常在每个线程被创建时分配在每个线程被创建时分配在每个线程基础上的。

No14:

Java堆:

Java堆是类实例和数组的分配空间,是一块所有线程共享的内存区域。堆在虚拟机启动时创建,是Java虚拟机所管理的内存中最大的一块。Java堆内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

No15:

方法区:

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器变异后的代码等数据。

No16:

运行时常量池:

用于存放编译器生成的各种字面量和符号引用,常量池也是方法区的一部分。

No17:

访问对象的主流方式有两种:

1)使用句柄:Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

2)使用直接指针:Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。

两种访问方式各有优势

使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改

使用直接指针访问方式最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多猴也是一项非常可观的执行成本

No18:

内存泄露的分类:

1)常发性内存泄露

发生内存泄露的代码会被多次执行,每次被执行的时候都会导致一块内存泄露

2)偶发性内存泄露

发生内存泄露的代码只有在某些特定环境下或操作过程中才会发生。

3)一次性内存泄露

发生内存泄露的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅一块内存发生泄露。

4)隐式内存泄露

程序在运行过程中不停地分配内存,但是直到程序结束时才释放内存。对于一个服务器程序来说,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。

No19:

内存管理的核心就是两个部分:分配内存和回收内存。Java语言使用new操作符来分配内存

No20:

所有的对象都有一个相同的头部clazz和lock:

1)clazz:指向该对象的类对象,类对象用来描述该对象所属的类,这样可以很容易的从一个对象获取该对象所属的类的具体信息

2)lock:是一个无符号整数,用以实现对象的同步

3)data:用于存放对象数据,根据对象的不同数据区的大小是不同的

No21:

在Dalvik虚拟机实现有3个时机可以触发垃圾收集的运行

1)程序员显式的调用System.gc()

2)内存分配失败时

3)如果分配的对象大小超过384KB,运行并发标记(concurrent mark)