- 浏览: 259001 次
- 性别:
- 来自: 深圳
文章分类
- 全部博客 (88)
- JAVA / base (26)
- JAVA / web (12)
- JAVA / Lib-tools (5)
- SERVER / tomcat (4)
- DB / mysql (4)
- DB / mongodb (2)
- DB / memcached (2)
- DB / redis (2)
- WEB / Front-end (3)
- WEB / security (4)
- WEB / css (2)
- WEB / js (4)
- OS / linux (3)
- IT / Architecture (4)
- IT / other (2)
- Android (9)
- Go (1)
- Other (1)
- OS / Mac (2)
最新评论
-
Zero2Max:
哈哈,马士兵老师也发现了。
java实现接口的bug -
xly1981:
能像CSRF攻击一样带个图就更棒了
XSS跨站攻击 -
xmong:
df274119386 写道在javascript中看到下面的 ...
CSRF攻击与防御策略 -
df274119386:
在javascript中看到下面的语句 e.value = t ...
CSRF攻击与防御策略 -
xmong:
yzxqml 写道xmong 写道yzxqml 写道tomca ...
Tomcat集群
java内存管理
1 Java内存
1.1 Java有几种存储区域
寄存器:位于CPU内部,是最快的存储器,开发人员不能通过代码来控制寄存器的分配,由编译器来管理。
栈:位于通用RAM(随机访问存储器)中,是一块连续的内存的区域,通过栈指针可以从处理器那里获得支持,指针想下移动,则分配新内存;向上移动,则释放内存。在java中用于存放基本数据类型和对象的引用。
堆:一种通用的内存池(也位于RAM中),是不连续的内存区域。在java中用于存放所有的java对象。
常量存储区与静态存储区:常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中,静态存储区用来存放static类型的变量。
非RAM存储:如果数据完全活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。如对象流,持久化对象。
1.2 Java内存分配方式
每个java虚拟机都有一个类装载器子系统,他根据给定的全限定名来装入类型(类或接口)。同样每个java虚拟机都一个执行引擎,他负责执行那些包还在被装载类的方法中的指令。当java虚拟机运行程序时,需要分配内存来存储许多东西,如创建的对象,传递方法的参数,返回值等。这时候需要对内存进行相应的分配,内存的分配可以通过三种方式:
静态分配:这是最简单的内存分配策略,将程序中的所有名字在编译时绑定在某个存储位置上,这种绑定不会在运行时改变。
常数池,源代码中的命名常量、String常量和static 变量保存在方法区。
栈分配:块结构语言通过在栈上分配内存,克服了静态分配的一些限制。每次过程调用时,一个活动记录或是帧被压入系统栈,并在返回时弹出。
最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的
堆分配:堆中的数据结构能够以任意次序分配和释放。意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。因而活动记录和动态数据结构可能比创建他们的过程更长寿。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。
1.3 Java数据类型的内存分配
-- 基础数据类型直接在栈空间分配;
-- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
-- 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
-- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
-- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;
-- 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间释放;
-- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;
-- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小
2 实例分析
2.1 常量池
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
常量池在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。
例:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );//false
System.out.println( s1+" "+s2 );// kvill kvill
System.out.println( s2==s1.intern() );//true
这个类中事先没有声名”kvill”常量,所以常量池中一开始是没有”kvill”的,当调用s1.intern()后就在常量池中新添加了一个 ”kvill”常量,原来的不在常量池中的”kvill”仍然存在。s1==s1.intern()为false说明原来的“kvill”仍然存在;s2 现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。
2.2 String常量池问题
(1) 字符串常量的"+"号连接,在编译期字符串常量的值就确定下来, 拿"a" + 1来说,编译器优化后在class中就已经是a1。
String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true
(2) 对于含有字符串引用的"+"连接,无法被编译器优化。
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
由于引用的值在程序编译期是无法确定的,即"a" + bb,只有在运行期来动态分配并将连接后的新地址赋给b。
(3) 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝并存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
(4) jvm对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b。
String a = "ab";
final String bb = getbb();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static string getbb() {
return "b";
}
(5) String 变量采用连接运算符(+)效率低下。
String s = "a" + "b" + "c"; 就等价于String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
这个就不一样了,最终结果等于:
Stringbuffer temp = new Stringbuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
(6) Integer、Double等包装类和String有着同样的特性:不变类。
String str = "abc"的内部工作机制很有代表性,以Boolean为例,说明同样的问题。
不变类的属性一般定义为final,一旦构造完毕就不能再改变了。
Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。 Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!由于Boolean.TRUE是类变量 (static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源 代码是:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等价于Boolean b1 = Boolean.valueOf(5>3); //优于Boolean b1 = new Boolean (5>3);
static void foo(){
boolean isTrue = 5>3; //基本类型
Boolean b1 = Boolean.TRUE; //静态变量创建的对象
Boolean b2 = Boolean.valueOf(isTrue);//静态工厂
Boolean b3 = 5>3;//自动装箱(autoboxing)
System.out.println("b1 == b2 ?" +(b1 == b2));
System.out.println("b1 == b3 ?" +(b1 == b3));
Boolean b4 = new Boolean(isTrue);////不宜使用
System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销
} //这里b1、b2、b3指向同一个Boolean对象。
(7) 如果问你:String x ="abc";创建了几个对象?
准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。
如果问你:String str1 = new String("abc"); 创建了几个对象?
准确的答案是:1或者2个。(至少1个在heap中)
(8) 对于int a = 3; int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处 理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
2.3 堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”
可以看出JVM主要管理两种类型的内存:堆和非堆。
简单来说堆就是Java代码可及的内存,是留给开发人员使用的;
非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;
JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
例子
-Xms256m
-Xmx1024m
---------------------------------------
参考:
《thinking in java》
java内存管理机制文档
1 Java内存
1.1 Java有几种存储区域
寄存器:位于CPU内部,是最快的存储器,开发人员不能通过代码来控制寄存器的分配,由编译器来管理。
栈:位于通用RAM(随机访问存储器)中,是一块连续的内存的区域,通过栈指针可以从处理器那里获得支持,指针想下移动,则分配新内存;向上移动,则释放内存。在java中用于存放基本数据类型和对象的引用。
堆:一种通用的内存池(也位于RAM中),是不连续的内存区域。在java中用于存放所有的java对象。
常量存储区与静态存储区:常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中,静态存储区用来存放static类型的变量。
非RAM存储:如果数据完全活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。如对象流,持久化对象。
1.2 Java内存分配方式
每个java虚拟机都有一个类装载器子系统,他根据给定的全限定名来装入类型(类或接口)。同样每个java虚拟机都一个执行引擎,他负责执行那些包还在被装载类的方法中的指令。当java虚拟机运行程序时,需要分配内存来存储许多东西,如创建的对象,传递方法的参数,返回值等。这时候需要对内存进行相应的分配,内存的分配可以通过三种方式:
静态分配:这是最简单的内存分配策略,将程序中的所有名字在编译时绑定在某个存储位置上,这种绑定不会在运行时改变。
常数池,源代码中的命名常量、String常量和static 变量保存在方法区。
栈分配:块结构语言通过在栈上分配内存,克服了静态分配的一些限制。每次过程调用时,一个活动记录或是帧被压入系统栈,并在返回时弹出。
最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的
堆分配:堆中的数据结构能够以任意次序分配和释放。意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。因而活动记录和动态数据结构可能比创建他们的过程更长寿。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。
1.3 Java数据类型的内存分配
-- 基础数据类型直接在栈空间分配;
-- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
-- 引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
-- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
-- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收;
-- 方法调用时传入的 literal 参数,先在栈空间分配,在方法调用完成后从栈空间释放;
-- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;
-- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小
2 实例分析
2.1 常量池
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
常量池在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。
例:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );//false
System.out.println( s1+" "+s2 );// kvill kvill
System.out.println( s2==s1.intern() );//true
这个类中事先没有声名”kvill”常量,所以常量池中一开始是没有”kvill”的,当调用s1.intern()后就在常量池中新添加了一个 ”kvill”常量,原来的不在常量池中的”kvill”仍然存在。s1==s1.intern()为false说明原来的“kvill”仍然存在;s2 现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。
2.2 String常量池问题
(1) 字符串常量的"+"号连接,在编译期字符串常量的值就确定下来, 拿"a" + 1来说,编译器优化后在class中就已经是a1。
String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true
(2) 对于含有字符串引用的"+"连接,无法被编译器优化。
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
由于引用的值在程序编译期是无法确定的,即"a" + bb,只有在运行期来动态分配并将连接后的新地址赋给b。
(3) 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝并存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
(4) jvm对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b。
String a = "ab";
final String bb = getbb();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static string getbb() {
return "b";
}
(5) String 变量采用连接运算符(+)效率低下。
String s = "a" + "b" + "c"; 就等价于String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
这个就不一样了,最终结果等于:
Stringbuffer temp = new Stringbuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
(6) Integer、Double等包装类和String有着同样的特性:不变类。
String str = "abc"的内部工作机制很有代表性,以Boolean为例,说明同样的问题。
不变类的属性一般定义为final,一旦构造完毕就不能再改变了。
Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。 Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!由于Boolean.TRUE是类变量 (static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源 代码是:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等价于Boolean b1 = Boolean.valueOf(5>3); //优于Boolean b1 = new Boolean (5>3);
static void foo(){
boolean isTrue = 5>3; //基本类型
Boolean b1 = Boolean.TRUE; //静态变量创建的对象
Boolean b2 = Boolean.valueOf(isTrue);//静态工厂
Boolean b3 = 5>3;//自动装箱(autoboxing)
System.out.println("b1 == b2 ?" +(b1 == b2));
System.out.println("b1 == b3 ?" +(b1 == b3));
Boolean b4 = new Boolean(isTrue);////不宜使用
System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销
} //这里b1、b2、b3指向同一个Boolean对象。
(7) 如果问你:String x ="abc";创建了几个对象?
准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。
如果问你:String str1 = new String("abc"); 创建了几个对象?
准确的答案是:1或者2个。(至少1个在heap中)
(8) 对于int a = 3; int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处 理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
2.3 堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”
可以看出JVM主要管理两种类型的内存:堆和非堆。
简单来说堆就是Java代码可及的内存,是留给开发人员使用的;
非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;
JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
例子
-Xms256m
-Xmx1024m
---------------------------------------
参考:
《thinking in java》
java内存管理机制文档
发表评论
-
Java validation(java验证器实现)
2014-03-18 11:45 3636Java validation 1. java验证器 在 ... -
Memo class备注类信息
2014-03-18 09:52 810Memo Class 1. 什么是Memo Class Mem ... -
java annotation注解
2014-01-24 18:01 9101. Annotation的声明方式 An ... -
Java RMI
2013-03-28 15:12 1647Java Rmi 目录 1 JAVA RMI 1 ... -
java内部类
2013-03-19 16:25 997Java内部类 目录 1 JAVA ... -
java多线程设计模式之订单模式
2013-03-11 14:00 2614Java多线程实现订单模式: 客户端线程向服务端发起请求后, ... -
java多线程设计模式之线程池处理请求
2013-03-08 17:50 1784Java实现线程池处理请求: 客户端线程发出请求,请求存入请 ... -
java多线程设计模式之异步处理请求
2013-03-08 12:36 4486Java实现多线程异步处理请求: Java实现多线程异步处理 ... -
java多线程设计模式之读写文件模式
2013-03-07 17:56 1543Java实现多线程读写数据 ... -
java多线程设计模式之生产者与消费者
2013-03-07 11:34 998Java实现多线程生产者与消费者: 生产者线程负责生产产品 ... -
java多线程设计模式之文件保存
2013-03-06 16:16 1548Java实现多线程保存文件:两线程去保存文件,一个保存线程定时 ... -
java多线程设计模式之队列通信
2013-03-06 13:51 2438Java实现多线程处理队列请求通信:客户端线程向请求队列中不断 ... -
Java读linux系统文件文件名乱码
2012-12-06 17:01 90631,问题描述 web应用想通过Java读取linux系统文件显 ... -
Java安全加密
2012-11-28 10:24 1921安全加密 目录 1 加密安全 1 1.1 应用的安全 1 ... -
图着色问题
2012-11-27 13:05 3062图着色问题 目录 1 图 ... -
JDK6新特性
2012-07-03 23:24 2863JDK6的新特性 JDK6的新特性之一_Desktop类 ... -
JDK7新特性
2012-07-03 15:39 3470JDK7新特性 一 JDK7新特性简介 准备 JDK7下载 ... -
JDK5新特性
2012-07-03 10:23 73JDK5.0新特性 1.自动封箱和自动解封(简单类型和封装类 ... -
java多线程
2012-06-15 15:12 1513Java多线程 目录 1 线 ... -
代理模式
2012-06-13 14:12 1318代理模式 目录 1 代理 ...
相关推荐
资源名称:Java内存管理机制相关资料汇总资源目录:【】java内存回收机制及预防【】java内存管理机制【】java内存管理白皮书【】Java虚拟机内存管理_对象和引用_空指针【】深入理解java虚拟机jvm高级行与最佳实践...
1、JAVA 内存管理总结 2、Java的内存管理实例 3、垃圾回收机制:
java内存管理精彩概述 java内存管理精彩概述 java内存管理精彩概述
本文档主要讲述的是java内存管理详细介绍;对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象...
java内存管理
java 内存管理 详细介绍了java内存管理各个方面的内容
主要关于java虚拟机的运行时数据区域,参考了周志明的深入理解java虚拟机,还涉及到了native方法、垃圾回收机制等等。
One strength of the Java™ 2 Platform, Standard Edition (J2SE™) is that it performs automatic memory management, thereby shielding the developer from the complexity of explicit memory management.
JAVA 内存管理总结 1. java是如何管理内存的 2. 什么叫java的内存泄露 3. JVM的内存区域组成 4.Java中数据在内存中是如何存储的 5. Java的内存管理实例
内存管理简介 内存管理的职责为分配内存,回收内存。 没有自动内存管理的语言/平台容易发生错误。 典型的问题包括悬挂指针问题,一个指针引用了一个已经被回收的内存地址,导致程序的运行完全不可知。 另一个...
java内存管理详细介绍,增强自己对虚拟机和内存进一步认识!
java内存管理深入讲解 基本是理解java程序内存的直接能用的资料了
Java的内存管理机制分析 让你了解java的内存管理 以及如何去分析它
java内存结构,垃圾收集,并发收集,虚拟机参数
java内存管理与垃圾回收
JAVA内存管理机制的思维导图
JAVA内存管理模式研究