a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 325|回复: 0

[专业语言] Java认证:对高性能JAVA代码之内存管理

[复制链接]
发表于 2012-8-4 12:44:44 | 显示全部楼层 |阅读模式
Java认证:对高性能JAVA代码之内存管理
3 @0 h2 x0 F5 L( _对高性能JAVA代码之内存管理
* [/ u+ y1 @; Z6 X: y/ l更甚者你写的代码,GC根本就回收不了,直接系统挂掉。GC是一段程序,不是智能,他只回收他认为的垃圾,而不是回收你认为的垃圾。
  @. y  f" S! V+ EGC垃圾回收:! l0 u# `; v4 a3 ~% \! C
Grabage Collection相信学过JAVA的人都知道这个是什么意思。但是他是如何工作的呢?
& L: E9 r# j; f+ H首先,JVM在管理内存的时候对于变量的管理总是分新对象和老对象。新对象也就是开发者new出来的对象,但是由于生命周期短,那么他占用的内存并不是马上释放,而是被标记为老对象,这个时候该对象还是要存在一段时间。然后由JVM决定他是否是垃圾对象,并进行回收。- D/ ?* [5 p: l# d  j7 S
所以我们可以知道,垃圾内存并不是用完了马上就被释放,所以就会产生内存释放不及时的现象,从而降低了内存的使用。而当程序浩大的时候。这种现象更为明显,并且GC的工作也是需要消耗资源的。所以,也就会产生内存浪费。
; q. Q! k8 o% k: F; I" wJVM中的对象生命周期里谈内存回收:
5 U6 w8 c. S, z# G  G对象的生命周期一般分为7个阶段:创建阶段,应用阶段,不可视阶段,不可到达阶段,可收集阶段,终结阶段,释放阶段。
% m( x: M% a' d; h: X2 v1 I, Q4 }, a创建阶段:首先大家看一下,如下两段代码:
1 \( F9 H8 D% p! ~+ L# itest1:
! B& q& E5 y8 l1 A9 Efor( int i=0; i《10000; i++)9 h' X! E' I, u" }
Object obj=new Object();
2 K, B# p$ }1 ^# u/ U0 \- ctest2:
1 U; z: E2 M! S0 b) XObject obj=null;, g$ c' p- @9 J* |1 u
for( int i=0; i《10000; i++)
9 M" ]7 {8 u! M8 v4 d2 Vobj=new Object();
& D' k! Z, Y$ b5 ~2 t9 W这两段代码都是相同的功能,但是显然test2的性能要比test1性能要好,内存使用率要高,这是为什么呢?原因很简单,test1每次执行for循环都要创建一个Object的临时对象,但是这些临时对象由于JVM的GC不能马上销毁,所以他们还要存在很长时间,而test2则只是在内存中保存一份对象的引用,而不必创建大量新临时变量,从而降低了内存的使用。  J4 q% R1 ?' H2 J$ {- Z; Z8 t
另外不要对同一个对象初始化多次。例如:/ r  r' i# w" l$ w  y: J( C/ q
public class A{" t& j8 m2 u) \
private Hashtable table = new Hashtable();6 N3 }; m) T6 I" _: e
public A(){ table = new Hashtable();
" O6 }9 n! p6 J9 @8 H3 O& M// 这里应该去掉,因为table已经被初始化。4 O0 H. z- C9 X3 `
}$ g! [5 K' Q. |/ q
* p6 W4 i; ]* P) _1 F7 {
5 A; R- I: }6 F9 E5 Q* l7 p% _
这样就new了两个Hashtable,但是却只使用了一个。另外一个则没有被引用。而被忽略掉。浪费了内存。并且由于进行了两次new操作。也影响了代码的执行速度。
# ^; i' U2 W  |& L" S6 P) l; h6 O应用阶段:即该对象至少有一个引用在维护他。
) f2 O9 ~3 K& m; l不可视阶段:即超出该变量的作用域。这里有一个很好的做法,因为JVM在GC的时候并不是马上进行回收,而是要判断对象是否被其他引用在维护。所以,这个时候如果我们在使用完一个对象以后对其obj=null或者obj.doSomething()操作,将其标记为空,可以帮助JVM及时发现这个垃圾对象。* X& O5 k' ~8 ^
不可到达阶段:就是在JVM中找不到对该对象的直接或者间接的引用。
% i5 d) i- d! M5 V1 V) ^0 P! u9 o可收集阶段,终结阶段,释放阶段:此为回收器发现该对象不可到达,finalize方法已经被执行,或者对象空间已被重用的时候。( @3 |' k' x! F5 d# ]" }
JAVA的析构方法:
7 O* h7 t$ E, j1 Q. B1 h可能不会有人相信,JAVA有析构函数? 是的,有。因为JAVA所有类都继承至Object类,而finalize就是Object类的一个方法,这个方法在JAVA中就是类似于C++析构函数。一般来说可以通过重载finalize方法的形式才释放类中对象。如:& e# D( `& b" O7 r5 b% P
public class A{+ x7 g) o+ x7 _) c- k0 r2 j
public Object a;
5 U; u4 i8 M( v- y1 `, m. [public A(){ a = new Object ;}0 W: g  U1 B# @) u
protected void finalize() throws java.lang.Throwable{
  X# z% J2 w/ }/ {9 m- Na = null; // 标记为空,释放对象0 E- b, o- d2 B
super.finalize(); // 递归调用超类中的finalize方法。
- O& a5 v* E7 d* `0 ]}
- C  D# v! g7 q! @4 N! ?1 B}
0 c2 Q7 l5 e9 C' a5 s# I当然,什么时候该方法被调用是由JVM来决定的。..。..。..。..。..。..。..。." w* h/ T2 {/ [% w' c: ~( }
一般来说,我们需要创建一个destory的方法来显式的调用该方法。然后在finalize也对该方法进行调用,实现双保险的做法。* e# A1 o1 u( F! G7 ~, S
由于对象的创建是递归式的,也就是先调用超级类的构造,然后依次向下递归调用构造函数,所以应该避免在类的构造函数中初始化变量,这样可以避免不必要的创建对象造成不必要的内存消耗。当然这里也就看出来接口的优势。8 Q3 m9 h$ a$ ]4 f
数组的创建:
% w3 {: U/ Q* Y1 s5 j9 J7 K由于数组需要给定一个长度,所以在不确定数据数量的时候经常会创建过大,或过小的数组的现象。造成不必要的内存浪费,所以可以通过软引用的方式来告诉JVM及时回收该内存。(软引用,具体查资料)。
% w1 X+ V3 l; M- H例如:
5 {9 b' N" a+ ^! Z* |, xObject obj = new char[10000000000000000];/ B: M& V! u8 e" ?. A2 _7 K: R
SoftReference ref = new SoftReference(obj);# N% j, z# [9 `7 }/ T( j# ]0 ?5 K
共享静态存储空间:2 r/ i" D+ A+ J$ ~
我们都知道静态变量在程序运行期间其内存是共享的,因此有时候为了节约内存工件,将一些变量声明为静态变量确实可以起到节约内存空间的作用。但是由于静态变量生命周期很长,不易被系统回收,所以使用静态变量要合理,不能盲目的使用。以免适得其反。
/ B+ {2 X4 d9 \* G因此建议在下面情况下使用:6 Z. Z+ ?- ]& H8 c5 g. y0 Z
1,变量所包含的对象体积较大,占用内存过多。% C% U2 m) W/ X, ]* W8 @% [, Y
2,变量所包含对象生命周期较长。- u' r) e+ g: g/ i2 E
3,变量所包含数据稳定。( T2 n, Q0 |; j7 l2 M( q
4,该类的对象实例有对该变量所包含的对象的共享需求。(也就是说是否需要作为全局变量)。' I: o1 L  w4 u6 J! P: P
对象重用与GC:1 _7 i/ _9 t6 J; G6 Z7 K. a- x8 p
有的时候,如数据库操作对象,一般情况下我们都需要在各个不同模块间使用,所以这样的对象需要进行重用以提高性能。也有效的避免了反复创建对象引起的性能下降。8 ^% e2 f! j' }( h: b# j
一般来说对象池是一个不错的注意。如下:) Z1 g. i9 D- ]/ s) E& h: l* J
public abstarct class ObjectPool{. I$ v& R+ @  t* L5 G
private Hashtable locked,unlocked;
, S# A# A; L& w; `6 E- c4 Eprivate long expirationTime;
1 A. R4 s4 X& C( j3 t1 Labstract Object create();
. K0 V' t# E9 O6 F2 uabstract void expire( Object o);
2 Z% K( G! o; [2 ~( R0 ?% S8 I' Uabstract void validate( Object o);1 t# R, C) e! k
synchronized Object getObject(){。..};! F9 y3 m% b5 X& n
synchronized void freeObject(Object o){。..};
$ O: w/ |. @' Q! L* j4 l) S这样我们就完成了一个对象池,我们可以将通过对应的方法来存取删除所需对象。来维护这快内存提高内存重用。, \8 J: x9 n* m' q% K4 `" o& o) v
当然也可以通过调用System.gc()强制系统进行垃圾回收操作。当然这样的代价是需要消耗一些cpu资源。- r% F( m; i% \) A" m  }
不要提前创建对象:1 t+ y9 U  R- ?2 }  l7 P/ r
尽量在需要的时候创建对象,重复的分配,构造对象可能会因为垃圾回收做额外的工作降低性能。6 f4 m% @# h4 u) n3 H9 O
JVM内存参数调优:9 H5 ?3 \0 K% ?' N3 o* a
强制内存回收对于系统自动的内存回收机制会产生负面影响,会加大系统自动回收的处理时间,所以应该尽量避免显式使用System.gc(),
$ g: o% H" J: b3 AJVM的设置可以提高系统的性能。例如:: r: W$ n6 q/ E: L; G
java -XX:NewSize=128m -XX:MaxNewSize=128m -XX:SurvivorRatio=8 -Xms512m -Xmx512m& e- _" C# m8 ^" C$ J
具体可以查看java帮助文档。我们主要介绍程序设计方面的性能提高。. Y# w2 u' @- D5 E2 u
JAVA程序设计中有关内存管理的其他经验:
# D5 C0 y7 W9 k6 \! K) K1 ?9 F( I! E根据JVM内存管理的工作原理,可以通过一些技巧和方式让JVM做GC处理时更加有效。,从而提高内存使用和缩短GC的执行时间。
0 N: |2 ~* l: s# v1,尽早释放无用对象的引用。即在不使用对象的引用后设置为空,可以加速GC的工作。(当然如果是返回值。..。.)
7 O" q% d  ]$ _1 y3 X  S* Y2,尽量少用finalize函数,此函数是JAVA给程序员提供的一个释放对象或资源的机会,但是却会加大GC工作量。5 H$ {- D8 m) B8 z
3,如果需要使用到图片,可以使用soft应用类型,它可以尽可能将图片读入内存而不引起OutOfMemory.' r5 g7 h7 _) B, m. ~
4,注意集合数据类型的数据结构,往往数据结构越复杂,GC工作量更大,处理更复杂。) e: ~( C  O, E' e
5,尽量避免在默认构造器(构造函数)中创建,初始化大量的对象。7 L4 r/ t' k1 c! `
6,尽量避免强制系统做垃圾回收。会增加系统做垃圾回收的最终时间降低性能。
8 K5 K* g1 A$ f; B5 d7,尽量避免显式申请数组,如果不得不申请数组的话,要尽量准确估算数组大小。' c( f3 K% F$ D- v$ s) g/ p
8,如果在做远程方法调用。要尽量减少传递的对象大小。或者使用瞬间值避免不必要数据的传递。4 f6 v1 f' N% a' X
9,尽量在合适的情况下使用对象池来提高系统性能减少内存开销,当然,对象池不能过于庞大,会适得其反.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-4-25 11:49 , Processed in 0.193597 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表