JVM内存管理:深入垃圾收集器与内存分配策略
创新互联公司服务项目包括芦山网站建设、芦山网站制作、芦山网页制作以及芦山网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,芦山网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到芦山省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
概述:说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项技术当做Java语言的伴生产物。事实上GC的历史远远比Java来得久远,在1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?怎么样回收?
把时间从1960年拨回现在,回到我们熟悉的Java语言。本文第一章中介绍了Java内存运行时区域的各个部分,其中程序计数器、VM栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的帧随着方法进入、退出而有条不紊的进行着出栈入栈操作;每一个帧中分配多少内存基本上是在Class文件生成时就已知的(可能会由JIT动态晚期编译进行一些优化,但大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收具备很高的确定性,因此在这几个区域不需要过多考虑回收的问题。而Java堆和方法区(包括运行时常量池)则不一样,我们必须等到程序实际运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,我们本文后续讨论中的“内存”分配与回收仅仅指这一部分内存。
引用计数算法(Reference Counting)
最初的想法,也是很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。
客观的说,引用计数算法实现简单,判定效率很高,在大部分情况下它都是一个不错的算法,但引用计数算法无法解决对象循环引用的问题。举个简单的例子:对象A和B分别有字段b、a,令A.b=B和B.a=A,除此之外这2个对象再无任何引用,那实际上这2个对象已经不可能再被访问,但是引用计数算法却无法回收他们。
根搜索算法(GC Roots Tracing)
在实际生产的语言中(Java、C#、甚至包括前面提到的Lisp),都是使用根搜索算法判定对象是否存活。算法基本思路就是通过一系列的称为“GC Roots”的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的。在Java语言中,GC Roots包括:
1.在VM栈(帧中的本地变量)中的引用
2.方法区中的静态引用
3.JNI(即一般说的Native方法)中的引用
生存还是死亡?
大家在进行程序系统维护的时候是否因为java编程的内存管理问题而无法快速解决导致系统出错呢?下面我们就一起来了解和学习一下,关于java编程内存管理都有哪些知识点。
程序计数器(了解)程序计数器,可以看做是当前线程所执行的字节码的行号指示器。
在虚拟机的概念模型里,字节码解释器工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。
Java虚拟机栈(了解)Java虚拟机栈也是线程私有的,它的生命周期与线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口信息等。
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表中存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。
如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈(了解)本地方法栈与虚拟机的作用相似,不同之处在于虚拟机栈为虚拟机执行的Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
有的虚拟机直接把本地方法栈和虚拟机栈合二为一。
会抛出stackOverflowError和OutOfMemoryError异常。
Java堆堆内存用来存放由new创建的对象实例和数组。
(重点)Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的目的就是存放对象实例。
Java堆是垃圾收集器管理的主要区域。
java课程培训机构发现由于现在收集器基本采用分代回收算法,所以Java堆还可细分为:新生代和老年代。
从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。
从理论上来讲java做的系统并不比其他语言开发出来的系统更占用内存,那么为什么却有这么N多理由来证明它确实占内存呢?两个字,陋习。
(1)别用newBoolean()。
在很多场景中Boolean类型是必须的,比如JDBC中boolean类型的set与get都是通过Boolean封装传递的,大部分ORM也是用Boolean来封装boolean类型的,比如:ps.setBoolean("isClosed",newBoolean(true));ps.setBoolean("isClosed",newBoolean(isClosed));ps.setBoolean("isClosed",newBoolean(i==3));通常这些系统中构造的Boolean实例的个数是相当多的,所以系统中充满了大量Boolean实例小对象,这是相当消耗内存的。
Boolean类实际上只要两个实例就够了,一个true的实例,一个false的实例。
Boolean类提供两了个静态变量:publicstaticfinalBooleanTRUE=newBoolean(true);publicstaticfinalBooleanFALSE=newBoolean(false);需要的时候只要取这两个变量就可以了,比如:ps.setBoolean("isClosed",Boolean.TRUE);那么像2、3句那样要根据一个boolean变量来创建一个Boolean怎么办呢?可以使用Boolean提供的静态方法:Boolean.valueOf()比如:ps.setBoolean("isClosed",Boolean.valueOf(isClosed));ps.setBoolean("isClosed",Boolean.valueOf(i==3));因为valueOf的内部实现是:return(b?TRUE:FALSE);所以可以节省大量内存。
相信如果Java规范直接把Boolean的构造函数规定成private,就再也不会出现这种情况了。
(2)别用newInteger.和Boolean类似,java开发中使用Integer封装int的场合也非常多,并且通常用int表示的数值通常都非常小。
SUNSDK中对Integer的实例化进行了优化,Integer类缓存了-128到127这256个状态的Integer,如果使用Integer.valueOf(inti),传入的int范围正好在此内,就返回静态实例。
这样如果我们使用Integer.valueOf代替newInteger的话也将大大降低内存的占用。
如果您的系统要在不同的SDK(比如IBMSDK)中使用的话,那么可以自己做了工具类封装一下,比如IntegerUtils.valueOf(),这样就可以在任何SDK中都可以使用这种特性。
(3)用StringBuffer代替字符串相加。
这个我就不多讲了,因为已经被人讲过N次了。
我只想将一个不是笑话的笑话,我在看国内某“著名”java开发的WEB系统的源码中,竟然发现其中大量的使用字符串相加,一个拼装SQL语句的方法中竟然最多构造了将近100个string实例。
无语中!(4)过滥使用哈希表有一定开发经验的开发人员经常会使用hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。
比如java课程认为使用HashMap缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。
其实我们可以使用
Java的内存分配有三种,
1、静态存储区:内存在程序编译时就分配好了,比如静态变量;
2、栈区:各种原始数据类型的局部变量都是在栈上创建的,当程序退出该变量的作用范围的时候,这个变量的内存会被自动释放。
3、堆区:对象(包括数组)都是在堆中创建的。程序在运行的时候用new关键字来创建对象,对象创建时会在堆中为其分配内存。
Java自有垃圾回收机制,所以Java编程不需要考虑回收问题。只要不是数组越界类似的问题,或者大规模的计算(超出计算机内存,导致内存溢出),那就没问题。
晕, 你确定你是用Java,
OK,不管Java还是C#
1. 定义你的map
HashMapString, String map = new HashMapString, String();
2. 存入你的信息, 反正你得要有个唯一标识的key,以及你想存的对象
map.put("userid", "userMessage");
....
3. 每次存入后,判断一下当前大小,java 的hashmap是size()方法,如果大于你的最大数,把原来存的前进前出删掉
if(map.size()100){
//map.remove(arg0)
....
}
更高级内容:
如果对map中的cache内容有过访问,给该内容的权重+1,每次删除的时候先将权重由小至大删除。 这就是命中率缓存,一般缓存都是按这种算法做的,只是具体算法有改进