【Java问题:】如下代码,java面向对象代码实例b最早在哪个选项前被垃圾回收?

2014年5月 Java大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。之前看书的时候,看到了方法执行的内容,忽然就想到了这么一个有趣的东西.然后就特意开一个贴,把一些前人,大大的知识做一个汇总,做一下记录吧.
相信,网上很多java性能优化的帖子里都会有这么一条
尽量把不使用的对象显式得置为null.这样有助于内存回收
可以明确的说,这个观点是基本错误的.sun jdk远比我们想象中的机智.完全能判断出对象是否已经no ref..但是,我上面用的词是"基本".也就是说,有例外的情况.这里先把这个例外情况给提出来,后续我会一点点解释.这个例外的情况是, 方法前面中有定义大的对象,然后又跟着非常耗时的操作,且没有触发JIT编译..总结这句话,就是
除非在一个方法中,定义了一个非常大的对象,并且在后面又跟着一段非常耗时的操作.并且,该方法没有满足JIT编译条件,否则显式得设置 obj = null是完全没有必要的
上面这句话有点绕,但是,上面说的每一个条件都是有意义的.这些条件分别是
1 同一个方法中2 定义了一个大对象(小对象没有意义)3 之后跟着一个非常耗时的操作.4 没有满足JIT编译条件
上面4个条件缺一不可,把obj显式设置成null才是有意义的. 下面我会一一解释上面的这些条件
在解释上面的条件之前,简略的说一下一些基础知识.
(1)sun jdk的内存垃圾判定,是基于根搜索算法的.也就是说,在GC root为跟,能被搜索到的,就认为是存活对象,搜索不到的,则认为是"垃圾".
(2) 里和我们这篇文章有关的gc root是这一条
Java LocalLocal variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
这句话直接翻译就是说是"本地变量,例如方法的参数或者方法中创建的局部变量".如果换一种说法是,
Java 方法栈(Java Method Stack)的局部变量表(Local Variable Table)中引用的对象。
下面开始说四大条件. 我们测试是否被垃圾回收的方法是,申请一个64M的byte数组(作为大对象),然后调用System.gc();.运行的时候用 -verbose:gc 观察回收情况来判定是否会回收.
同一个方法中
这个条件是最容易理解的,如果大对象定义在其他方法中,那么是不需要设置成Null的,
public class Test
public static void main(String[] args){
System.gc();
public static void foo(){
byte[] placeholder = new byte[64*];
对应的输出如下,可以看到64M的内存已经被回收
D:\&java -verbose:gc Test[GC 66798K-&6K), 0.0012225 secs][Full GC 66120K-&481K(120960K), 0.0059647 secs]
其实很好理解,placeholder是foo方法的局部变量,在main方法中调用的时候,其实foo方法对应的栈帧已经结束.那么placeholder指向的大对象自然被gc的时候回收了.
定义了一个大对象
这句话的意思也很好理解.只有定义的是大的对象,我们才需要关心他尽快被回收.如果你只是定义了一个 String str = "abc"; 后续手动设置成null让gc回收是没有任何意义的.
后面跟着一个非常耗时的操作
这里理解是:后面的这个耗时的可能超过了一个GC的周期.例如
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
Thread.sleep(3000l);
// dosomething
在线程sleep的三秒内,可能jvm已经进行了好几次ygc.但是由于placeholder一直持有这个大对象,所以造成这个64M的大对象一直无法被回收,甚至有可能造成了满足进入old 区的条件.这个时候,在sleep之前,显式得把placeholder设置成Null是有意义的. 但是,
如果没有这个耗时的操作,main方法可以非常快速的执行结束,方法返回,同时也会销毁对应的栈帧.那么就是回到第一个条件,方法已经执行结束,在下一次gc的时候,自然就会把对应的"垃圾"给回收掉.
没有满足JIT编译条件
jit编译的触发条件,这里就不多阐述了.对应的测试代码和前面一样
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
placeholder =
time-consuming operation
System.gc();
在解释执行中,我们认为
placeholder =
是有助于对这个大对象的回收的.在JIT编译下,我们可以通过强制执行编译执行,然后打印出对应的 ASM码的方式查看. 安装fast_debug版本的jdk请查看
D:\software\jdk6_fastdebug\jdk1.6.0_25\fastdebug\bin&java -Xcomp -XX:+PrintAssembly Test & log.txt
Decoding compiled method 0x:Code:[Disassembling for mach='i386'][Entry Point][Verified Entry Point][Constants] # {method} 'main' '([Ljava/lang/S)V' in 'Test' # parm0: ecx = '[Ljava/lang/S' # [sp+0x20] (sp of caller) ;; block B1 [0, 0] 0x: mov %eax,-0x8000(%esp) 0x: push %ebp 0x: sub $0x18,%* - Test::main@0 (line 7) ;; block B0 [0, 10] 0x0267f2db: mov $0x4000000,%ebx 0x: mov $0x,% {oop({type array byte})} 0x: mov %ebx,%edi 0x: cmp $0xffffff,%ebx 0x0267f2ed: ja 0x0267f37f 0x: mov $0x13,%esi 0x: lea (%esi,%ebx,1),%esi 0x0267f2fb: and $0xfffffff8,%esi 0x0267f2fe: mov %fs:0x0(,%eiz,1),%ecx 0x: mov -0xc(%ecx),%ecx 0x: mov 0x44(%ecx),%eax 0x0267f30c: lea (%eax,%esi,1),%esi 0x0267f30f: cmp 0x4c(%ecx),%esi 0x: ja 0x0267f37f 0x: mov %esi,0x44(%ecx) 0x0267f31b: sub %eax,%esi 0x0267f31d: movl $0x1,(%eax) 0x: mov %edx,0x4(%eax) 0x: mov %ebx,0x8(%eax) 0x: sub $0xc,%esi 0x0267f32c: je 0x0267f36f 0x: test $0x3,%esi 0x: je 0x0267f34f 0x0267f33e: push $0x844ef48 ; {external_word} 0x: call 0x 0x: pusha
0x: call 0x ; {runtime_call} 0x0267f34e: hlt
0x0267f34f: xor %ebx,%ebx 0x: shr $0x3,%esi 0x: jae 0x 0x0267f35a: mov %ebx,0xc(%eax,%esi,8) 0x0267f35e: je 0x0267f36f 0x: mov %ebx,0x8(%eax,%esi,8) 0x: mov %ebx,0x4(%eax,%esi,8) 0x0267f36c: dec %esi 0x0267f36d: jne 0x ;*newarray ; - Test::main@2 (line 7) 0x0267f36f: call 0x025bb450 ; OopMap{off=164} ;*invokestatic gc ; - Test::main@7 (line 10) ; {static_call} 0x: add $0x18,%esp 0x: pop %ebp 0x: test %eax,0x370100 ; {poll_return} 0x0267f37e: ret
;; NewTypeArrayStub slow case 0x0267f37f: call 0x025f91d0 ; OopMap{off=180} ;*newarray ; - Test::main@2 (line 7) ; {runtime_call} 0x: jmp 0x0267f36f 0x: nop
;; Unwind handler 0x: mov %fs:0x0(,%eiz,1),%esi 0x: mov -0xc(%esi),%esi 0x: mov 0x198(%esi),%eax 0x: movl $0x0,0x198(%esi) 0x: movl $0x0,0x19c(%esi) 0x0267f3ad: add $0x18,%esp 0x: pop %ebp 0x: jmp 0x025f7be0 ; {runtime_call} 0x: hlt
0x0267f3ba: hlt
0x0267f3bb: hlt
0x0267f3bc: hlt
0x0267f3bd: hlt
0x0267f3be: hlt
0x0267f3bf: hlt [Stub Code] 0x: {no_reloc} 0x: nop
0x: mov $0x0,% {static_stub} 0x: jmp 0x ; {runtime_call}[Exception Handler] 0x0267f3cc: mov $0xdead,%ebx 0x: mov $0xdead,%ecx 0x: mov $0xdead,%esi 0x0267f3db: mov $0xdead,%edi 0x: call 0x025f9c40 ; {runtime_call} 0x: push $0x83c8bc0 ; {external_word} 0x0267f3ea: call 0x0267f3ef 0x0267f3ef: pusha
0x: call 0x ; {runtime_call} 0x: hlt [Deopt Handler Code] 0x: push $0x267f3f6 ; {section_word} 0x0267f3fb: jmp 0x025bbac0 ; {runtime_call}
可以看到, placeholder = 这个语句被消除了! 也就是说,对于JIT编译以后的来说,压根不需要这个语句!
所以说,如果是解释执行的情况下,显式设置成Null是没有任何必要的!
到这里,基本已经把文章开头说的那个论断给说明清楚了.但是,在文章的结尾,补充一下局部变量表会对内存回收有什么影响.这个例子参照&深入理解Java虚拟机:JVM高级特性与最佳实践& 一书
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
time-consuming operation
System.gc();
这样的情况下,placeholder的对象是不会被回收的.可以理解..然后我们继续修改方法体
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
System.gc();
我们运行发现
d:\&java -verbose:gc Test[GC 66798K-&6K), 0.0021019 secs][Full GC 66072K-&66017K(120960K), 0.0069085 secs]
垃圾收集器并不会把对象给回收..明明已经出了作用域,竟然还是不回收!. 好吧,继续修改例子
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
int a = 0;
System.gc();
唯一的变化就是新增了一个 int a = 0; 继续看效果
d:\&java -verbose:gc Test[GC 66798K-&6K), 0.0011617 secs][Full GC 66144K-&481K(120960K), 0.0060882 secs]
可以看到,大对象被回收了..这是一个神奇的例子..能想到这个,我对书的作者万分佩服! 但是这个例子的解释,在书中的解释有点泛(至少我刚开始没看懂),所以这里就仔细说明一下.
要解释这个,先大概看一下
里面局部变量表的部分.
局部变量区用于存放方法中的局部变量和方法参数,.局部变量表用Slot为单位.jvm在实现的时候为了节省栈帧空间,做了一个简单的优化,就是slot的复用.如果当前字节码的PC计数器已经超出某些变量的作用域,那么这些变量的slot就可以给其他的复用.
上面的这段话有点抽象,后面一个个解释.其实方法的局部变量表大小在javac的时候就已经确定了.
在局部变量表的slot持有的某个对象,他是无法被垃圾回收的.因为局部变量表本来就是GC Root之一
在class文件中,方法体对应的Code属性中就有对应的Locals属性,就是来记录局部变量表的大小的.例子如下:
public class Test
public void foo(int a,int b){
int c = 0;
通过 javac -g:vars Test 编译,然后,通过javap -verbose 查看
public void foo(int, int); Code: Stack=1, Locals=4, Args_size=3 0: iconst_0 1: istore_3 2: returnLocalVariableTable: Start Length Slot Name Signature 0 3 0 this LT 0 3 1 a I 0 3 2 b I 2 1 3 c I
可以看到,局部变量表的Slot数量是4个.分别是 this,a,b,c ..这个非常好理解.那么,什么叫做Slot的复用呢,继续看例子
public class Test
public void foo(int a,int b){
int d = 0;
int c = 0;
在 int c = 0;之前新增一个作用域,里面定义了一个局部变量.如果没有slot复用机制,那么,理论上说,这个方法中局部变量表的slot个数应该是5个,但是,看具体的javap 输出
public void foo(int, int); Code: Stack=1, Locals=4, Args_size=3 0: iconst_0 1: istore_3 2: iconst_0 3: istore_3 4: return LocalVariableTable:Start Length Slot Name Signature 2 0 3 d I 0 5 0 this LT 0 5 1 a I 0 5 2 b I 4 1 3 c I
可以看到,对应的locals=4 ,也就是对应的slot个数还是4个. 通过查看对应的LocalVariableTable属性,可以看到,局部变量d和c都是在Slot[3]中. 这就是上面说的,在某个作用域结束以后,里面的对应的slot并没有马上消除,而是继续留着给下面的局部变量使用..按照这样理解,
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
System.gc();
这个例子中,在执行System.gc()的时候,虽然placeholder 的作用域已经结束,但是placeholder 对应的slot还存在,继续持有64M数组这个大对象,那么自然的,在GC的时候不会把对应的大对象给清理掉.而在
public class Test
public static void main(String[] args) throws Exception{
byte[] placeholder = new byte[64*];
int a = 0;
System.gc();
这个例子中,在System.gc的时候,placeholder对应的slot已经被a给占用了,那么对应的大对象就变成了无根的"垃圾",当然会被清楚.这一点,可以通过javap明显的看到
public static void main(java.lang.String[]) throws java.lang.E Code: Stack=1, Locals=2, Args_size=1 0: ldc #2; //int
2: newarray byte 4: astore_1 5: iconst_0 6: istore_1 7: invokestatic #3; //Method java/lang/System.gc:()V 10: return LocalVariableTable: Start Length Slot Name Signature5 0 1 placeholder [B 0 11 0 args [Ljava/lang/S7 4 1 a I Exceptions: throws java.lang.Exception}
可以看到,placeholder 和 a 都对应于Slot[1].
这个例子说明的差不多了,在上面的基础上,再多一个例子
public class Test
public static void main(String[] args) throws Exception{
int b = 0;
byte[] placeholder = new byte[64*];
int a = 0;
System.gc();
这个代码中,这个64M的大对象会被GC回收吗..
&深入理解Java虚拟机:JVM高级特性与最佳实践& 周志明的书
chenjingbo
浏览: 291998 次
来自: 杭州
发布式Java应用基础和实践 -& 分布式Java应用基 ...
同一机器不同线程都会获取锁,有并发问题
我只能说你实现的有严重的并发问题,并没有考虑并发的情况。
若多个客户端同时调用tryLock去获取锁,最开始不存在锁的节 ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'当前位置: →
→ 浅析JAVA之垃圾回收机制
浅析JAVA之垃圾回收机制
& 作者及来源: 我是IT民工 - 博客园 &
&收藏到→_→:
摘要: 浅析JAVA之垃圾回收机制
"浅析JAVA之垃圾回收机制"::
对于java编程和很多类似c、c++语言有一个巨大区别就是内存不需要自己去free或者delete,而是由jvm垃圾回收机制去完成的。对于这个过程很多人一直比较茫然或者觉得很智能,使得在写程序的过程不太考虑它的感受,其实知道一些内在的原理,帮助我们编写更加优秀的代码是非常有必要的。
本文从以下几个方面进行阐述:1、finalize()方法2、system.gc()方法及一些实用方法3、java如何申请内存,和c、c++有何区别
4、jvm如何寻找到需要回收的内存5、jvm如何回收内存的(回收算法分解详述)6、 部署及常用参数设置7、扩展话题jit(及时)与lazy evaluation(惰性评估)
1、finalize()方法:&&&& 为了说明jvm回收,不得不先说明一个问题就是关于finalize()方法,所有实体对象都会有这个方法,因为这个object类定义的,这个可能会被认为是垃圾回收的方法或者叫做析构函数,其实并非如此。finalize在jvm内存回收前会被调用(但并非绝对),而即使不调用它,jvm回收机制通过后面所述的一些算法就可以定位哪些是垃圾内存,那么这个拿来干什么用呢?&&finalize()其实是要做一些特殊的内存回收操作,如果对java研究稍微多一点,大家会发现java中有一种jni的机制,即:java native interface,这种属于java本地接口调用,即调用本地的其他语言信息,java底层掉调用也是这样实现的,这部分调用中可能存在一些对c、c++语言的操作,在c和c++内部通过new、malloc、realloc等创建的对象垃圾回收机制是无能为力的,因为这不是它要管理的范围,而平时这些对象可能被java对应的实体所调用,那么需要在对应java对象放弃时(并不代表回收,只是程序中不使用它了)去调用对应的c、c++提供的本地接口去释放这段内存信息,他们的释放同样需要通过free或delete去释放,所以我们一般情况下不要滥用finalize(),可能你会联想到另一类某些特殊引用对象的释放,如层数引用太多,java有些时候不知道这一线的对象是否都可能被回收那么,你可以自己将finalize()重写,并将内置对象的句柄先释放掉,这样也是没有问题的,不过一般不要滥用而已。
2、system.gc()或者runtime.getruntime().gc();
&&&这个可以被认为是强制垃圾回收的一种机制,但是并非强制回收,只是向jvm建议可以进行垃圾回收,而且垃圾回收的地方和多少是不能像c语言一样控制,这是jvm垃圾回收机去控制的。程序中尽量不要是去使用这些东西,除自己开发一些管理代码除外,一般由jvm自己管理即可。
这里顺便提及几个查看当前jvm内存的几个方法(在同一个集群下的多个server,即使在同一个机器上通过下面方法只能查看到当前server下的内存情况):
2.1.设置的最大内存:-xmx等值:
(runtime.getruntime().maxmemory()/ (1024 * 1024)) + "mb"
2.2.当前jvm可使用的内存,这个值初始化和-xms等值,若加载东西超过这个值,那么以下值会跟着变大,不过上限为-xmx,由于变动过程中需要由向申请新的内存,所以存在不连续内存以及影响开销,很多时候服务器提供商(如bea)推荐是-xms等价于-xmx的值:
(runtime.getruntime().totalmemory()/ (1024 * 1024)) + "mb"
2.3.剩余内存,在当前可使用内存基础上,此文来自: 马开东博客
转载请注明出处 网址:
剩余内存等价于其剪掉使用了的内存容量:
(runtime.getruntime().freememory()/ (1024 * 1024)) + "mb"
&同理如果要查看使用了多少内存或者百分比。可以通过上述几个参数进行运算查看到。。。。
顺便在这里提供几个实用方法和类,这部分可能和jvm回收关系不大,不过只是相关推敲,扩展知识面,而且也较为实用的东西:
2.4.获取java中的所有系统级属性值(包含版本、、字符集等等信息):
&system.setproperty("aaa", "123445");
&properties properties = system.getproperties();&&enumeration&object& e = properties.keys();&&while (e.hasmoreelements()) {&&&string key = (string) e.nextelement();&&&system.out.println(key + " = " + properties.getproperty(key));&}&
2.5.获取系统中所有的环境变量信息:
map&string, string& env = system.getenv();&&for (iterator&string& iterator = env.keyset().iterator(); iterator.hasnext();) {&&&string key = iterator.next();&&&system.out.println(key + " = " + env.get(key));}
system.out.println(system.getenv("classpath"));
2.6.在win环境下,打开一个记事本和一个 文档:
& runtime.getruntime().exec("notepad");
& runtime.getruntime().exec("cmd /c start win ");
}catch(exception e) {
& e.printstacktrace();
2.7.查询当前server下所有的线程信息列表情况(这里需要提供两个步骤,首先要根据任意一个线程获取到顶级线程组的句柄(有关线程的说明,后面专门会有一篇文章说明),然后通过顶级线程组得到其存在线程信息,进行一份拷贝,给与遍历):
2.7.1.这里通过当前线程得到顶级线程组信息:
public static threadgroup getheadthreadgroup() {&&thread t = thread.currentthread();&&threadgroup group = t.getthreadgroup();&&while(group.getparent() != null) {&&&group = group.getparent();&&}&&}
2.7.2.通过得到的顶级线程组,遍历存在的子元素信息(仅此文来自: 马开东博客
转载请注明出处 网址:
仅遍历常用属性):
public static void disallthread(threadgroup threadgroup) {&&thread list[] = new thread[threadgroup.activecount()];&&threadgroup.enumerate(list);&&for(thread thread:list) {&&&system.out.println(thread.getid()+"\t"+thread.getname()
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&+"\t"+thread.getthreadgroup()+"\t"
&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +thread.getstate()+"\t"+thread.isalive());}}
2.7.3.测试方法如:
类名.disallthread(getheadthreadgroup());即可完成,第一个方法带有不断向上查询的过程,这个过程可能在一般情况下也不会太慢,不过我们最好将其记录在一个地方,方便我们提供管理类来进行直接管理,而不需要每次去获取,对外调用都是封装的运行过程而已。
好,回到话题,继续说明jvm垃圾回收机制的信息,下面开始说明java申请内存、回收内存的机制了。
3、java如何申请内存,和c、c++有何区别。
&&&在上一次缩写的关于java集合类文章中其实已经有部分说明,可以大致看到java内部是按照句柄指向实体的过程,不过这是从java的角度去理解,如果我们需要更加细致的问一个问题是:jvm垃圾回收机制是如何知道哪些内存是垃圾内存的?jvm为什么不在平时就去回收内存,而是要等到内存不够用的时候才会去回收内存?不得不让我进一步去探讨java是如何细节的申请内存的。
从编程思想的角度来说,c、c++new申请的内存也是通过指针指向完成,不过你可以看成是一个地球板块图,在这些板块中,他们去new的过程中,就是好比是找一个版块,因为c、c++在申请内存的过程中,是不断的free和delete操作,所以会产生很多内存的碎片操作,而java不是,java只有内存不够用的时候才会去回收(回收细节讲会在文章后面介绍),也就是说,可以保证内存在一定程度上是连续的。从某种意义上将,只要下一块申请的内存不会到头,就可以继续在上一块申请内存的后面紧跟着去申请内存,那么从某种意义上讲,其申请的开销可能可以和c++媲美。那么java在回收内存后,内存还能是连续的嘛。。。。我们姑且这样去理解,在第五节会说明。。继续深入话题:
在启动weblogic的时候,如果打开,可以马上发现,内存被占用了最少-xms的大小,一个说明现象就是jvm首先将内存先占用了,然后再分配给其对象的,也就是说我们所谓此文来自: 马开东博客
转载请注明出处 网址:
的new可以理解为在堆上做了一个标记,所以在一定程度上做连续分配内存是可以实现的,只是你会发现若要真正实现连续,必然导致一定程度上的序列化,所以new的开销一般还是蛮大的,即使在后面说的jvm会将内存分成几个大块来完成操作,但是也避免不了序列化的过程。
在这里一个小推敲就是,一个server的管理内存范围一般不要太大(一般在1~2g一个server),推荐也不要太大,因数去考虑:
1、java回收内存是在不够用的时候再去回收,这个不够用何以说明,很多时候因为计算上的失误导致内存溢出。
2、如果一个主机只有2g左右内存,很少的cpu,那么一个jvm也好,但是如果主机很好,如32g内存,那么这样做未必有点过,第一发挥不出来,一个jvm管这么大块内存好像有点过,还有内存不够用去回收这么大块内存(回收内存时一般需暂停服务),需要花时间,第二举个很现实的例子,一个学校如果只有20~30人,一个人可以既当校长又当老师,如果一个学校有几百上千人,我想这个人再大的能力忙死也管不过来,而且会出乱子,此时它要请班主任来管了。
3、对于大内存来说,使用多个server完成负载均衡,一个暂停服务回收内存,另一个还可以运行嘛。
4、jvm如何寻找到需要回收的内存:
4.1、引用计数算法:在早期的java中,他们采用思维上最为常用的算法,就是计数器,在对象被使用&=&给与句柄的过程中(在集合类内部虽然外部调用通过add或者put完成,不过内部仍然是这样),它同时会告诉java,这个对象被增加一次引用(注意一个句柄如果已经存在指向的实体,此时指向另一个实体,它也会同时告诉java以前指向那个实体少了一个引用,不然这就乱了),当你使用句柄=null的时候,它会告诉java这个指向的实体少了一个引用,这个计数器并不是记录在实体本省,而是被jvm私有化管理起来,这部分也是jvm垃圾回收机的基础信息(私有化管理部分为jvm中的永久域,为所有静态常量的管理池,如:public class的代码段、static代码段、static变量、string常量数组的一份拷贝等等),jvm回收内存的时候,就会去找计数器中位0的元素,将其回收(回收过程,在第五节说明),如果有级联引用的,如果父亲级别的引用被回收后,子对象的引用数会自动减少1。
问题出来了:
对于a对象有b对象的引用、b对象有a对象的引用,如果两个引用都没有去手动去=null操作,那么实用引用计数算法,将永远计算不出来他们是需要被回收的内存,这就是一种在使用引用计数器中的java内存泄露问题(对于这类内存泄露属于引用计数上,使用树结构遍历是没有问题的,同样在引用计数算法上,存在多层集合类级联引用的问题可能不会被回收到;另一类是流的内存泄露(连接的内存泄露也属于其中),对于这类对象,进行特殊的管理,内部有一个管理器,如连接数据有一个drivermanager,他们永远都保存着对连接的引用,如果直接使用jdbc操作,使用完然后做close操作,相当于告诉连接管理器断开并可以释放连接对象,反过来说,如果不做close操作,系统永远不知道这块资源信息是垃圾内存,永远不会回收它,直到资源耗尽内存溢出为止,直接将句柄=null,对于连接对象是无效)。
这个引用计数需要遍历整块jvm才知道哪些需要回收,哪些不需要回收,太慢了。。。。
4.2.树结构遍历算法:其实为什么java可以管理其区域下的内存,因为我们是在jvm内部去创建内存,所以可以理解为打一个tag,那么它必然有一个根引用(静态区)通过分类的管理机制遍历到所使用的每一个堆空间中,此时这棵很大的树上,进行遍历下来得到的全部是活着的结点。那么没有被遍历到的全部是没有活着的结点,对于大量内存需要回收的情况(很多情况下是的,因为业务级的请求用完这块内存就没用了),我们很快可以知道哪些内存是活着的,在后面回收时对应其快速复制的过程,另一个区域就作为自动作为垃圾内存了,在jdk1.4.2后开始逐步提出并行回收算法(包含了并行遍历算法,内部包含了后面说的需要将内存分块管理,而并非完全是一个整体)。
&&&&&现在的jvm根据实际情况会采用一种自适应的算法去寻找垃圾内存,它会按照上述两种算法进行分别管理,当发现树结构的开销较大的时候(大部分是不需要回收的内存,由于树的遍历要么通过递归消耗代码段并在时间开销上很大以外或者利用一个缓冲区来遍历,比顺序遍历要慢很多,这种情况其实很少,如果真是这样,内存很容易溢出),所以此时它会自适应的去采用引用计数算法去找需要回收的内存部分。
&5、jvm如何回收内存的(回收算法分解详述):
首先了解几个其他的概念:
5.1.平时所说的jdk,其实是java的意思,安装java会产生两个jre目录,jre目录为java运行时环境的意思,两个jre目录的区别是其中在jdk所在的jre目录下没有server和client文件夹(jdk1.5自动安装包会自动将其复制到jdk下面一份),jre为运行时环境,提供对jvm操作的api,jvm内部通过动态链接库(就是配置path的路径下),通过它作为主动态链接库寻找到其它的动态链接库,动态链接库为何os绑定的参数,即代码最终要通过这些东西转换为x86指令集进行运行,另一个核心工具为jit(java即时编译工具),用于将代码转换为对应的运行指令集合的过程,不过其与惰性评估形成对比,后面会专门介绍。
&5.1.jvm首先将大致分为:jvm指令集、jvm、jvm内存(堆栈区域部分)、jvm垃圾回收区域;jvm的堆部分又一般分为:新域、旧域、永久域(很多时候不会认为段是堆的一部分,因为它是永远不会被回收的,它一般包含class的定义信息、static定义的方法、static匿名块代码段、常量信息(较为典型的就是string常量),不过这块内存也是可以被配置的);新域此文来自: 马开东博客
转载请注明出处 网址:
内部又可以分为eden和两个救助区域,这几个对象在jvm内部有一定的默认值,但是也是可以被设置的。
&&&&当新申请的对象的时候,会放入eden区中(这个区域一般不会太大),当对象在一定时间内还在使用的时候,它会逐步的进入旧域(此时是一个内存复制的过程,旧区域按照顺序,其引用的句柄也会被修改指向的位置),jvm回收中会先将eden里面的内存和一个救助区域的内存就会被赋值到另一个救助区域,然后对这两块内存进行回收,同理,旧区域也有一个差不多大小的内存区域进行被复制,这个复制的过程肯定就会在一定程度上将内存连续的排列起来;另外可以想到java提供内存复制最快的就是system.arraycopy方法,那么这个肯定是按照内存数组进行拷贝(jvm起始就是一个大内存,本身就可以成是几个大数组组成的,而这个拷贝方法,默认拷贝多长呢,其实数组最长可以达到多少,通过数组的length返回的是int类型数据就可以清楚发现,为int类型的上限1&&30的长度(理想情况,因为有可能因为的其他进程导致jvm内存本身就不是连续的),即在2g*单元内存长度,所以也在一定程度上说明我们的一个jvm设置内存不要太大,不然复制内存的过程开销是很大的)。
&&&&其实上述描述的是一种停止-复制回收算法,在这个过程中形成了几个大的内存来回倒,这必然是很恶心的事情,那么继续将其切片为几个大的板块,有些大的对象会出现一两个对象占用一个版块的现象,这些大对象基本不会怎么移动(被回收就是另一回事,因为会清空这个版块),板块之间有一些对应关系,在回收时先将一些版块的小对象,向另一个还未装满的大板块内部转移,复制的粒度变小了,另外管理上可以发挥多线程的优势所在,好比是将一块大的田地,分成很多小田地,每块田地种植不同档次的秧苗,将其划分等级,我们假如秧苗经常会随机的死掉一些(这块是垃圾),在清理一些很普通的秧苗田地的时候,可能会将其中一块或几块田地的(活着的秧苗)种植到另一块田地中,但是他们不可以将高档次的秧苗移植到低档次的田地中,因为高档次的秧苗造价太高(内存太大),移植过程中代价太大,需要使用非普通秧苗的手段要移动他们,所以基本不移动他们,除非丰收他们的时候(他们也成为垃圾内存的时候),才会被拔出,腾出田地来。在转移秧苗的过程中,他们需要整理出顺序便于管理,在很多书籍上把这个过程叫做压缩,因为这样使得保证在只要内存不溢出的情况下,申请的对象都有足够的控件可以存放,不然零碎的空间中间的缝隙未必可以存放下一个较大的对象。将内存分块管理就是另一个停止复制收集器的进一步升级:增量收集思想。
&5.2.回收过程,垃圾回收机制一般分为:标记&清除、标记&压缩、停止&复制、增量收集、分代收集、并发收集和并行收集。
&标记清除收集器,一般依赖于第一种寻找垃圾的算法去寻找,当寻找到需要使用的内存,会打一个tag,此时未标记的对象,就会被该收集器回收,一般先停止外部服务,并且是单线程的去寻找并清除。
&标记压缩收集器,和上述收集器唯一区别就是多一步压缩操作,压缩操作删除前或删除后去操作是将标记为正在使用的对象复制到一块新的内存中,也就是大致上是按照顺序排列的。
&停止复制收集器、增量收集器上述描述的较多,这里就不再多说了,总之是来回倒这些内存,为了介于内存,在其基础上按照一定规则进行内存分块操作。
&并发收集器,这里一定要明确并发和并行的概念不同之处,并发收集器是可以再内存回收的过程中不暂停服务,也就是不影响运行,类似上述的收集器,要进行压缩空间等操作不得不暂停服务保证系统的正常运行;可以思考到它基本会建立在首先将内存分块的基础上,可能会更细,它独立的运行并和同时运行,回收的方式也是和上面一样,只是它复制的时候只是较小部分内存的复制,所以其他业务系统运行照常,粒度很小,最好的情况就是这块内存99%是需要回收的,它正在回收这块内存,关于剩下的1%的内存应用服务被暂停,其余其它块的内存照常运行不受到影响。
&并行收集器是使用上述某类收集方法,但是使用多线程算法进行回收,在服务器应用中,利用多cpu进行回收可以显著提 ,此时需要进行相关的配置,在第六节中有详细的说明。
&对于上述了解后,可能对于不同的 有不同的jvm垃圾查找算法和回收算法,但是大致不离其中,根据实际情况,进行服务器的相关调试就可以在一定程度上提高服务器的运行性能,第六节就详细说明下。
6、 部署及常用参数设置:
说到jvm的配置,最常用的两个配置就是:
-xms512m &xmx1024m
-xms设置jvm的初始化内存大小,-xmx为最大内存大小,当突破这个值,将会报内存溢出,导致的原因有很多,主要是的回收问题以及上的内存泄露问题;由于在超过-xms时会产生页面申请的开销,所以一般很多 会推荐-xms和-xmx是等值的;最大值一般不保持在主机内存的75%的内存左右(多个server是加起来的内存),当jvm绝大部分时间处于回收状态,并且内存长时间处于非常长少的状态就会报:java.lang.outofmemoryerror:java heap space的错误。
&上面提及到jvm很多的知识面,很显然你想去设置一下其它的参数,其实对于jvm设置的参数有上百个,这里就说一些较为常用配置即可。
&jvm内存配置分两大类:
1、-x开头的参数信息:一般每个版本变化不大。
2、-xx开头的参数信息:版本升级变化较大,如果没有太大必要保持默认即可。
3、另外还有一个特殊的选项就是-server还是-client,他们在默认配置内存上有一些细微的区别,直接用jdk运行程序默认是-client, 生产模式一般只会用-server。
&这些命令其实就是在运行java命令或者javaw等相关命令后可以配置的参数,如果不配置,他们有相应的默认值配置。
&1、-x开头的常用配置信息:
-xnoclassgc& 禁用垃圾回收,一般不适用这个参数
-xincgc&&&&&& 启用增量垃圾回收
-xmn1024k&& eden区初始化java堆的尺寸,默认值640k
-xms512m&&& java堆初始化尺寸,默认是32m
-xmx512m&&& java堆最大尺寸,默认64m,一般不超过2g,在64位机上,使用64位的jvm,需要进行unlimited方可设置到2g以上。
&2、-xx开头常用内存配置信息:
-xx:-disableexplicitgc& 将会忽略手动调用gc的代码,如:system.gc(),将-disableexplicitgc,&& 改成+disableexplicitgc即为启用,默认为启用,什么也不写,默认是加号,但是系统内部默认的并不是什么都启用。
-xx:+useparallelgc&&&& 将会自动启用并行回收,多余多cpu主机有效,默认是不启用。
-xx:+useparnewgc &&&&启用并行收集(不是回收),也是多cpu有效。
-xx:newsize=128m&&&& 新域的初始化尺寸。
-xx:maxnewsize=128m新创建的对象都是在eden中,其属于新域,在-client中默认为640k,而-server中默认是2m,为减少频繁的对新域进行回收,可以适当调大这个值。
-xx:persize=64m &&&&&&&设置永久域的初始化大小,在weblogic中默认的尺寸应该是48m,一般够用,可以根据实际情况作相应条调整。
-xx:maxpersize=64m&&& 设置永久域的最大尺寸。
另外还可以设置按照区域的比例进行设置操作,以及设置线程、缓存、页面大小等等操作。
&3、-xx开头的几个监控信息:
-xx:+gitime&&&&&&&&&&&&& 显示有多少时间花在编译代码代码上,这部分为运行时编译为对应机器码时间。
-xx:+printgc&&&&&&&&&&&&& 打印垃圾回收的基本信息
-xx:+printgctimestamps 打印垃圾回收时间戳信息
-xx:+printgcdetails&&&&&& 打印垃圾回收的详细信息
-xx:+traceclassloading&&& 跟踪类的加载
-xx:+traceclassresolution 跟踪常量池
-xx:+traceclassunloading 跟踪类卸载
等等。。。。。。
编写一个简单的java类:
public class hello {
&&&& public static void main(string []args) {
&&&&&&&& byte []a1 = new byte[4*];
&&&&&&&& system.out.println("第一次申请");
&&&&&&&& byte []a2 = new byte[4*];
&&&&&&&& system.out.println("第二次申请");
&&&&&&&& byte []a3 = new byte[4*];
&&&&&&&& system.out.println("第三次申请");
&&&&&&&& byte []a4 = new byte[4*];
&&&&&&&& system.out.println("第四次申请");
&&&&&&&& byte []a5 = new byte[4*];
&&&&&&&& system.out.println("第五次申请");
&&&&&&&& byte []a6 = new byte[4*];
&&&&&&&& system.out.println("第六次申请");
此时运行程序,这样调试一下:
c:\&java -xmn4m -xms16m -xmx16m hello
第一次申请
第二次申请
exception in thread "main" java.lang.outofmemoryerror: java heap space
&此时内存溢出,因为这些空间都有释放,16m空间正好一半8m就溢出了,和我们的理论较吻合的就是一半在使用一半的大小就会崩掉,显示的使用值会成倍增加。
&那么我们将程序修改一下再看效果:
public class hello {
&&&& public static void main(string []args) {
&&&&&&&& byte []a1 = new byte[4*];
&&&&&&&& system.out.println("第一次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第二次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第三次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第四次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第五次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第六次申请");
运行程序如下:
c:\&javac hello.java
&c:\&java -xmn4m -xms16m -xmx16m hello
第一次申请
第二次申请
第三次申请
第四次申请
第五次申请
第六次申请
程序正常下来了,说明中途进行了垃圾回收的动作,我们想看下垃圾回收的整个过程,如何看,把上面的参数搬下来:
e:\&java -xmn4m -xms16m -xmx16m -xx:+printgcdetails hello
第一次申请
第二次申请
[gc [defnew: 189k-&133k(3712k), 0.0014622 secs][tenured: 8192k-&k), 0.0089967 secs] 8381k-&k), 0.0110011 secs]
第三次申请
[gc [defnew: 0k-&0k(3712k), 0.0004749 secs][tenured: 8325k-&k), 0.0083114 secs] 8325k-&k), 0.0092936 secs]
第四次申请
[gc [defnew: 0k-&0k(3712k), 0.0003168 secs][tenured: 8325k-&k), 0.0081516 secs] 8325k-&k), 0.0089735 secs]
第五次申请
[gc [defnew: 0k-&0k(3712k), 0.0003179 secs][tenured: 8325k-&k), 0.0080368 secs] 8325k-&k), 0.0088335 secs]
第六次申请
&从上面的结果中看到第一次回收是从两个对象申请后,开始回收,后面是每申请一个就回收一次,那是因为,程序代码中始终用一个句柄指向了一个空间,第一次回收的时候只回收了一个对象,还有一个对象没有回收,而当后面申请对象过程中,没申请一个对象以前那个对象就是垃圾,而且内存又满了,所以内存就会在每申请一个对象的时候被回收。
&把程序稍微修改一下,增加两个申请得较大的内存,因为新区域的回收从上一个试验中看不出来:
public class hello {
&&&& public static void main(string []args) {
&&&&&&&& byte []a1 = new byte[4*];
&&&&&&&& system.out.println("第一次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第二次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第三次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第四次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第五次申请");
&&&&&&&& a1 = new byte[4*];
&&&&&&&& system.out.println("第六次申请");
&&&&&&&& a1 = new byte[12*];
&&&&&&&& system.out.println("第七次申请(大三倍的内存)");
&&&&&&&& a1 = new byte[12*];
&&&&&&&& system.out.println("第八次申请(大三倍的内存)");
e:\&javac hello.java
--先将新区域设置为4m看下结果:
e:\&java -xmn4m -xms32m -xmx32m -xx:+printgcdetails hello
第一次申请
第二次申请
第三次申请
第四次申请
第五次申请
第六次申请
[gc [defnew: 189k-&133k(3712k), 0.0015527 secs][tenured: 24576k-&k), 0.0091076 secs] 24765k-&k), 0.0111978 secs]
第七次申请(大三倍的内存)
[gc [defnew: 0k-&0k(3712k), 0.0005428 secs][tenured: 16517k-&1k), 0.0131774 secs] 16517k-&1k), 0.0142610 secs]
第八次申请(大三倍的内存)
&--再将新域的大小扩大一倍:
e:\&java -xmn8m -xms32m -xmx32m -xx:+printgcdetails hello
第一次申请
[gc [defnew: 4362k-&133k(7424k), 0.0049861 secs] 4362k-&k), 0.0053091 secs]
第二次申请
[gc [defnew: 4229k-&133k(7424k), 0.0043679 secs] 8325k-&k), 0.0047056 secs]
第三次申请
[gc [defnew: 4229k-&133k(7424k), 0.0044629 secs] 12421k-&1k), 0.0047791 secs]
第四次申请
[gc [defnew: 4229k-&133k(7424k), 0.0044263 secs] 16517k-&1k), 0.0047646 secs]
第五次申请
[gc [defnew: 4229k-&133k(7424k), 0.0045198 secs] 20613k-&2k), 0.0048372 secs]
第六次申请
[gc [defnew: 4229k-&k), 0.0001604 secs][tenured: 20480k-&k), 0.0092190 secs] 24709k-&k), 0.0098943 secs]
第七次申请(大三倍的内存)
[gc [defnew: 0k-&0k(7424k), 0.0005219 secs][tenured: 16517k-&1k), 0.0131838 secs] 16517k-&1k), 0.0142096 secs]
[full gc [tenured: 12421k-&1k), 0.0115076 secs] 12421k-&1k), [perm : 338k-&337k(8192k)], 0.0123320 secs]
exception in thread "main" java.lang.outofmemoryerror: java heap space
&&&尽然内存溢出了,怎么扩大了内存溢出了?难道新域不能设置得太大?那么为了论证,我们将新域的大小再放大一倍:
e:\&java -xmn16m -xms32m -xmx32m -xx:+printgcdetails hello
第一次申请
第二次申请
第三次申请
[gc [defnew: 12551k-&133k(14784k), 0.0052593 secs] 12551k-&k), 0.0055915 secs]
第四次申请
第五次申请
第六次申请
[gc [defnew: 12556k-&1k), 0.0001953 secs][tenured: 4096k-&k), 0.0091847 secs] 16652k-&k), 0.0098906 secs]
第七次申请(大三倍的内存)
[gc [defnew: 12288k-&1k), 0.0001972 secs][tenured: 4229k-&1k), 0.0148896 secs] 16517k-&1k),搜索此文相关文章:此文来自: 马开东博客
网址: 站长QQ
浅析JAVA之垃圾回收机制_博客园相关文章
博客园_总排行榜
博客园_最新
博客园_月排行榜
博客园_周排行榜
博客园_日排行榜

我要回帖

更多关于 缺少对象 代码0 的文章

 

随机推荐