想了解JAVA内存机制以及内存泄漏情况的同行

时间:2021-09-05 20:56:09

<!-- /* Font Definitions */ @font-face{font-family:宋体;panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-alt:SimSun;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 135135232 16 0 262145 0;}@font-face{font-family:黑体;panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-alt:SimHei;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:1 135135232 16 0 262144 0;}@font-face{font-family:"/@宋体";panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 135135232 16 0 262145 0;}@font-face{font-family:"/@黑体";panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal{mso-style-parent:"";margin-top:0cm;margin-right:0cm;margin-bottom:6.0pt;margin-left:0cm;text-indent:10.0pt;mso-char-indent-count:2.0;line-height:150%;mso-pagination:none;mso-layout-grid-align:none;text-autospace:none;font-size:10.5pt;mso-bidi-font-size:10.0pt;font-family:"Times New Roman";mso-fareast-font-family:宋体;}h1{mso-style-next:正文;margin-top:17.0pt;margin-right:0cm;margin-bottom:16.5pt;margin-left:0cm;line-height:240%;mso-pagination:lines-together;page-break-after:avoid;mso-outline-level:1;mso-layout-grid-align:none;text-autospace:none;font-size:22.0pt;font-family:"Times New Roman";mso-font-kerning:22.0pt;}p.ParaCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharChar, li.ParaCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharChar, div.ParaCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharCharChar{mso-style-name:"默认段落字体 Para Char Char Char Char Char Char Char Char Char Char Char Char Char Char Char Char Char";mso-style-parent:"";mso-style-link:默认段落字体;mso-style-next:正文;margin-top:12.0pt;margin-right:0cm;margin-bottom:12.0pt;margin-left:105.0pt;text-indent:-21.0pt;mso-pagination:widow-orphan lines-together;page-break-after:avoid;mso-outline-level:8;tab-stops:list 105.0pt;font-size:10.5pt;font-family:Arial;mso-fareast-font-family:黑体;layout-grid-mode:line;} /* Page Definitions */ @page{mso-page-border-surround-header:no;mso-page-border-surround-footer:no;}@page Section1{size:612.0pt 792.0pt;margin:72.0pt 90.0pt 72.0pt 90.0pt;mso-header-margin:36.0pt;mso-footer-margin:36.0pt;mso-paper-source:0;}div.Section1{page:Section1;} /* List Definitions */ @list l0{mso-list-id:767770734;mso-list-type:hybrid;mso-list-template-ids:1101689116 325885602 67698713 67698715 67698703 67698713 67698715 67698703 67698713 67698715;}@list l0:level1{mso-level-tab-stop:39.0pt;mso-level-number-position:left;margin-left:39.0pt;text-indent:-18.0pt;}ol{margin-bottom:0cm;}ul{margin-bottom:0cm;}-->

正文

Java把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。

这也是 Java 比较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!

但是在了解JAVA的同行中,他们认为JAVA不占内存,并不比其他语言开发出来的系统占内存。但是我们问啥要说他占内存呢?简单的总结一下几点,不知道你在开发的过程中是否用到?

1.              别用 new Boolean()

在很多场景中Boolean类型是必须的,比如JDBCboolean类型的setget都是通过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类提供两了个静态变量:

以下是代码片段:

publicstatic final Boolean TRUE = new Boolean(true);

publicstatic final Boolean FALSE = new Boolean(false);

需要的时候只要取这两个变量就可以了,

比如:

以下是代码片段:ps.setBoolean("isClosed",Boolean.TRUE);

那么象23句那样要根据一个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.              别用 new Interger()

Boolean类似,java开发中使用Integer封装int的场合也非常多,并且通常用int表示的数值通常都非常小。SUN SDK中对Integer的实例化进行了优化,Integer类缓存了-128127256个状态的Integer,如果使用Integer.valueOf(inti),传入的int范围正好在此内,就返回静态实例。这样如果我们使用Integer.valueOf代替new Integer的话也将大大降低内存的占用。如果您的系统要在不同的SDK(比如IBM SDK)中使用的话,那么可以自己做了工具类封装一下,比如IntegerUtils.valueOf(),这样就可以在任何SDK中都可以使用这种特性。

3.              StringBuffer代替字符串相加

4.              过滥使用哈希表

有一定开发经验的开发人员经常会使用hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用HashMap缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如ehcacheoscache等,这些项目都实现了FIFOMRU等常见的缓存算法。

5.              避免过深的类层次结构和过深的方法调用。因为这两者都是非常占用内存的(特别是方法调用更是堆栈空间的消耗大户)。

6.              变量只有在用到它的时候才定义和实例化。

7.              尽量避免使用static变量,类内私有常量可以用final来代替

说到JAVA内存,不能不说到JAVA内存泄露。

Java的一个重要优点就是通过垃圾收集器GC GarbageCollection)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java 不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC JVM的问题。其实,这种想法是不正确的,因为Java 也存在内存泄漏,但它的表现与C++不同。如果正在开发的Java 代码要全天24 小时在服务器上运行,则内存漏洞在此处的影响就比在配置实用程序中的影响要大得多,即使最小的漏洞也会导致JVM耗尽全部可用内存。另外,在很多嵌入式系统中,内存的总量非常有限。在相反的情况下,即便程序的生存期较短,如果存在分配大量临时对象(或者若干吞噬大量内存的对象)的任何Java 代码,而且当不再需要这些对象时也没有取消对它们的引用,则仍然可能达到内存极限。

那么内存泄漏指的什么呢?

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏与许多其他问题有着相似的症状,并且通常情况下只能由那些可以获得程序源代码的程序员才可以分析出来。然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的。

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用mallocreallocnew等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用freedelete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

内存泄露大致上可以分为4

1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。

4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。