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

<!-- /* 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 类型是必须的,比如 JDBC boolean 类型的 set get 都是通过 Boolean 封装传递的,大部分 ORM 也是用 Boolean 来封装 boolean 类型的,比如:

以下是代码片段:

ps.setBoolean("isClosed",new Boolean(true));

ps.setBoolean("isClosed",new Boolean(isClosed));

ps.setBoolean("isClosed",new Boolean(i==3));

通常这些系统中构造的 Boolean 实例的个数是相当多的,所以系统中充满了大量 Boolean 实例小对象,这是相当消耗内存的。 Boolean 类实际上只要两个实例就够了,一个 true 的实例,一个 false 的实例。

Boolean 类提供两了个静态变量:

以下是代码片段:

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(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.              别用 new Interger()

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

3.              StringBuffer 代替字符串相加

4.              过滥使用哈希表

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值