在项目和一些群讨论中,经常看到对内存的处理,但是,自己确是一知半解的,基于此,就把这部分的知识简单的整理了下,知识点来源于网络博文,也一一标明出处,谢谢。
package com.love.malinda.utils; /** * 关于 堆与栈 * Date 2017-1-13 * @author Aaron * */ public class StackAndHeadStudy { /* * ####相关博文介绍: * -- http://blog.csdn.net/shimiso/article/details/8595564 --讲解比较详细 * *-- java内存分配研究:http://www.blogjava.net/Jack2007/archive/2008/05/21/202018.html * *-- Java常量池详解之一道比较蛋疼的面试题:http://www.cnblogs.com/DreamSea/archive/2011/11/20/2256396.html * *-- jvm常量池:http://www.cnblogs.com/wenfeng762/archive/2011/08/14/2137820.html * *-- 深入Java核心 Java内存分配原理精讲:http://developer.51cto.com/art/201009/225071.htm * * ####一个完整的java程序运行时涉及的主要内存区域: * 寄存器:jvm内部虚拟寄存器,存取速度非常快,程序不可控制。 * * 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,也就是 堆 中对象的引用(指针)。也可以用来保存加载方法时的帧。 * * 堆:用来存放动态产生的数据,比如 new 出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。 * 因为同一个类的对象拥有各自的成员变量,存储在各自的 堆 中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。 * * 常量池:jvm为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型、String)和对其他类型、方法、字段的符号引用。 * 池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在java的动态链接中起了核心的作用。 * 常量池存在于 堆 中。 * * 代码段:用来存放从硬盘上读取的源程序代码。 * * 数据段:用来存放static定义的静态成员。 * * 相关:1.无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。 * 只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向 堆 的指针,通过这个指针,就可以找到这个实例在堆中对应的对象。 * 因此,普通类型的变量只在 栈 中占用一块内存,而引用类型的变量要在 堆 和 栈 中各占一块内存。作为参数时,基本类型就直接传值,引用类型传指针。 * * 2.分清 对象 和 实例。Class a = new Class(); 此时 a 叫 实例,而不能直接说是对象。实例在栈中,对象在 堆 中,操作实例实际上是通过实例的指针间接的操作对象。 * 多个实例可以指向同一个对象。 * * 3.栈 和 堆 中的数据销毁并不是同步的。方法结束,栈中局部变量立即销毁,但是 堆 中的对象不一定销毁。 * 因为可能有其他的变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且不是马上销毁,要等垃圾回收扫描时才可以被销毁。 * * 4.以上的栈 、堆、代码段、数据段等都是相对于应用程序而言。每一个应用程序都是对应唯一的一个jvm实例,每一个jvm实例都有自己的内存区域,互不影响。 * 并且这些内存区域是所有线程共享的。这里提到的 堆 和 栈 都是整体的概念,这些 堆 和 栈 还可以细分。 * * 5.类的成员变量在不同的对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而 类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候, * 该方法才被压入 栈,方法不使用,则不占用内存。 * * 6.其他。常量池,维护了一个已加载类的常量。 * * 基本类型 和 基本类型的包装类。两者区别:基本类型体现在程序中是普通变量,基本类型的包装类是 类,体现在程序中是引用变量。因此两者在内存中的存储位置不同。 * 基本类型在栈中,而其包装类是在堆中。 * 注意:byte,short,char,int,long,boolean 的包装类都实现了常量池技术。String 类型也实现了常量池技术。但是 ,两种浮点类型的包装类(Float,Double)则没有实现。 * */ public static void main(String[] args) { //关于常量池对于基本类型的测试,这其实是一道面试题 int i = 40; int i0 = 40; Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = new Integer(40); Integer i5 = new Integer(40); Integer i6 = new Integer(0); Double d1 = 1.0; Double d2 = 1.0; System.out.println("i=i0 " + (i == i0)); System.out.println("i1=i2 " + (i1 == i2)); //这种情况时候,需要注意一个问题 数字段 只能在 -128~127之间。具体说明参见下面的解释。 System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); System.out.println("i4=i5 " + (i4 == i5)); System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("d1=d2 " + (d1 == d2)); //补充 Integer 的一个知识点。 Integer a = 1 ; //自动装箱。没有自动装箱,就应该是 Integer a = new Integer(1); int b = a ;//自动拆箱。没有自动拆箱,就应该是 int b = a.intValue(); //运行结果: // i=i0 true // i1=i2 true // i1=i2+i3 true // i4=i5 false // i4=i5+i6 true // d1=d2 false /* * 相关分析: * 1. i 和 i0 都是普通类型 int 的变量,所以数据直接存储在栈中。而 栈 有个很重要的特性:栈中的数据可以共享。当我们定义了一个 int i = 40;这时如果再定义一个 * int i0 = 40; 这时就会自动检测是否已经有 40 这个数据,如果有,i0 会直接指向 i 的40,不会再添加新的40; * * 2. i1 和 i2 都是integer这个包装类在栈中的引用(索引,类的实例在栈中存储)。Integer 实现了常量池技术,i1 和 i2 都是从常量池获取的,均指向同一个地址。因此相等。 * 但是但是但是,先说三遍。这里有个瑕疵(也不算瑕疵了,反正是不能一用百用,需要注意):如果是 Integer ii1 = 129; Integer ii2 = 129; 再比较时候,就有问题了,返回 false 。 * * 注意: * java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。 * 看 valueOf() 的源码中,( return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];),(取值范围是 -128~127) * 两个都是大于 128 的,new Integer(xx)的对象都是在堆中。内存地址不对,就是false;小于128 的还是在常量池中,就是true。 * * 3. java的数学运算都是在栈中进行的。 * * 4. i4 和 i5 都是引用。是存储在栈中的指针,由于是new 出来的,对应于 在堆中不同的内存地址,对象不同。也就是返回false了。 * 5. 同上。 * 6. d1 和d2 都是引用类型,存储于栈中。但是 两个 浮点类型 都没有实现 常量池 技术,就相当于 Double d2 = new Double(1.0); 。内存地址不一样,返回 false 。 * * 补充: * 1. 常量池技术,维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。 * 比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。 * * 2. String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。 * * 3. 内存分区的一些对比:待补充 * * 4. 缓存 概念及相关涉及点:(根据网络上的资料简单整理) * a. 缓存位于应用程序和物理数据源之间,用于临时存放复制数据的内存区域,目的是为了减少应用程序对物理数据源范围的次数,从而提高应用程序的运行性能。 * b. 缓存是CPU的一部分,它存在于CPU中。 * c. 缓存只是内存中少部分数据的复制品,所以CPU到缓存中找数据,可能会出现找不到的情况,这时它就会到内存中去找,不过CPU会把这部分数据复制到缓存中,方便下次查找。 * d. 随着时间变化,缓存中的数据可能不会被频繁的被访问了,或者又有新的数据要被频繁访问,所以啊,需要经常通过一定的算法来更新需要的数据。 * e. 了解一级缓存、二级缓存: * RAM (random access memory)随机存储记忆体 ,断电后数据信息消失, 相当于 电脑的 移动存储设备 * 分为 静态RAM(SRAM) ,存储速度较快,现在使用的缓存一般为此, * 动态RAM(DRAM) ,存储速度比DRAM慢,现在使用的内存一般为此。 * * 由于静态RAM的使用成本较高(存储体积更大,价格更高),但为了提高系统性能和运行速度,可以通过增加 告诉动态RAM作为缓存。 * * 运行速度对比: 静态RAM > 高速动态RAM > 常规动态RAM * 查找数据的顺序:》》》一级缓存 》》》二级缓存》》》内存 * 其中,静态RAM 称为 一级缓存,高速动态RAM 称为 二级缓存 * 一、二级缓存中的内容 都是内存中访问频率较高的内容的复制品(映射),存在的目的是为了减少 高速CPU对慢速内存的访问。 * */ } }
这只是从网络上整理的一部分,还需要不断的学习总结。