引用计数和回收池 & java=null-程序员宅基地

技术标签: java  游戏  netty  

java 使用的是垃圾回收和可达性分析

oc 和 cocos2d-x 使用引用计数与回收池

netty的bytebuf由于使用的直接内存,也使用引用计数

 

 

 

交谈中提到了显式置为null,http://chenjingbo.iteye.com/blog/1980908  这篇文章有非常好的论述实践:

且文中详细论述了:https://www.cnblogs.com/silyvin/p/9106570.html  中的显示大对象gc现象

前言    

  之前看书的时候,看到了方法执行的内容,忽然就想到了这么一个有趣的东西.然后就特意开一个贴,把一些前人,大大的知识做一个汇总,做一下记录吧.

 

正文

     相信,网上很多java性能优化的帖子里都会有这么一条 

写道
尽量把不使用的对象显式得置为null.这样有助于内存回收

     可以明确的说,这个观点是基本错误的.sun jdk远比我们想象中的机智.完全能判断出对象是否已经no ref..但是,我上面用的词是"基本".也就是说,有例外的情况.这里先把这个例外情况给提出来,后续我会一点点解释.这个例外的情况是, 方法前面中有定义大的对象,然后又跟着非常耗时的操作,且没有触发JIT编译..总结这句话,就是

写道
除非在一个方法中,定义了一个非常大的对象,并且在后面又跟着一段非常耗时的操作.并且,该方法没有满足JIT编译条件,否则显式得设置 obj = null是完全没有必要的

 上面这句话有点绕,但是,上面说的每一个条件都是有意义的.这些条件分别是

写道
1 同一个方法中(不同方法已断链,有一个例外,就是后面hotspot没代码)
2 定义了一个大对象(小对象没有意义)
3 之后跟着一个非常耗时的操作. (提前断链)
4 没有满足JIT编译条件(热点函数与循环,否则会把=null优化掉)

 上面4个条件缺一不可,把obj显式设置成null才是有意义的. 下面我会一一解释上面的这些条件

 

在解释上面的条件之前,简略的说一下一些基础知识.

(1)sun jdk的内存垃圾判定,是基于根搜索算法的.也就是说,在GC root为跟,能被搜索到的,就认为是存活对象,搜索不到的,则认为是"垃圾".

(2)GC root  里和我们这篇文章有关的gc root是这一条

写道
Java Local
Local 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的,

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.   
  4.     public static void main(String[] args){  
  5.       
  6.         foo();  
  7.           
  8.         System.gc();  
  9.     }  
  10.       
  11.     public static void foo(){  
  12.         byte[] placeholder = new byte[64*1024*1024];  
  13.     }  
  14. }  

 对应的输出如下,可以看到64M的内存已经被回收

写道
D:\>java -verbose:gc Test
[GC 66798K->66120K(120960K), 0.0012225 secs]
[Full GC  66120K->481K(120960K), 0.0059647 secs]

 其实很好理解,placeholder是foo方法的局部变量,在main方法中调用的时候,其实foo方法对应的栈帧已经结束.那么placeholder指向的大对象自然被gc的时候回收了.

 

定义了一个大对象

这句话的意思也很好理解.只有定义的是大的对象,我们才需要关心他尽快被回收.如果你只是定义了一个 String str = "abc"; 后续手动设置成null让gc回收是没有任何意义的.

 

后面跟着一个非常耗时的操作

这里理解是:后面的这个耗时的可能超过了一个GC的周期.例如

Java代码   收藏代码
  1. public static void main(String[] args) throws Exception{  
  2.         byte[] placeholder = new byte[64*1024*1024];  
  3.         Thread.sleep(3000l);  
  4.         // dosomething  
  5.     }  

 在线程sleep的三秒内,可能jvm已经进行了好几次ygc.但是由于placeholder一直持有这个大对象,所以造成这个64M的大对象一直无法被回收,甚至有可能造成了满足进入old 区的条件.这个时候,在sleep之前,显式得把placeholder设置成Null是有意义的. 但是,

写道
如果没有这个耗时的操作,main方法可以非常快速的执行结束,方法返回,同时也会销毁对应的栈帧.那么就是回到第一个条件,方法已经执行结束,在下一次gc的时候,自然就会把对应的"垃圾"给回收掉.

 

没有满足JIT编译条件

  jit编译的触发条件,这里就不多阐述了.对应的测试代码和前面一样

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         byte[] placeholder = new byte[64*1024*1024];  
  5.         placeholder = null;  
  6.         //do some  time-consuming operation  
  7.         System.gc();  
  8.     }  
  9. }  

 在解释执行中,我们认为

写道
placeholder = null;

 是有助于对这个大对象的回收的.在JIT编译下,我们可以通过强制执行编译执行,然后打印出对应的 ASM码的方式查看. 安装fast_debug版本的jdk请查看 

使用-XX:+PrintAssembly打印asm代码遇到的问题

命令是

写道
D:\software\jdk6_fastdebug\jdk1.6.0_25\fastdebug\bin>java -Xcomp -XX:+PrintAssembly Test > log.txt

 

ASM 写道
Decoding compiled method 0x0267f1c8:
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'main' '([Ljava/lang/String;)V' in 'Test'
# parm0: ecx = '[Ljava/lang/String;'
# [sp+0x20] (sp of caller)
;; block B1 [0, 0]

0x0267f2d0: mov %eax,-0x8000(%esp)
0x0267f2d7: push %ebp
0x0267f2d8: sub $0x18,%esp ;*ldc ; - Test::main@0 (line 7)
;; block B0 [0, 10]

0x0267f2db: mov $0x4000000,%ebx
0x0267f2e0: mov $0x20010850,%edx ; {oop({type array byte})}
0x0267f2e5: mov %ebx,%edi
0x0267f2e7: cmp $0xffffff,%ebx
0x0267f2ed: ja 0x0267f37f
0x0267f2f3: mov $0x13,%esi
0x0267f2f8: lea (%esi,%ebx,1),%esi
0x0267f2fb: and $0xfffffff8,%esi
0x0267f2fe: mov %fs:0x0(,%eiz,1),%ecx
0x0267f306: mov -0xc(%ecx),%ecx
0x0267f309: mov 0x44(%ecx),%eax
0x0267f30c: lea (%eax,%esi,1),%esi
0x0267f30f: cmp 0x4c(%ecx),%esi
0x0267f312: ja 0x0267f37f
0x0267f318: mov %esi,0x44(%ecx)
0x0267f31b: sub %eax,%esi
0x0267f31d: movl $0x1,(%eax)
0x0267f323: mov %edx,0x4(%eax)
0x0267f326: mov %ebx,0x8(%eax)
0x0267f329: sub $0xc,%esi
0x0267f32c: je 0x0267f36f
0x0267f332: test $0x3,%esi
0x0267f338: je 0x0267f34f
0x0267f33e: push $0x844ef48 ; {external_word}
0x0267f343: call 0x0267f348
0x0267f348: pusha 
0x0267f349: call 0x0822c2e0 ; {runtime_call}
0x0267f34e: hlt 
0x0267f34f: xor %ebx,%ebx
0x0267f351: shr $0x3,%esi
0x0267f354: jae 0x0267f364
0x0267f35a: mov %ebx,0xc(%eax,%esi,8)
0x0267f35e: je 0x0267f36f
0x0267f364: mov %ebx,0x8(%eax,%esi,8)
0x0267f368: mov %ebx,0x4(%eax,%esi,8)
0x0267f36c: dec %esi
0x0267f36d: jne 0x0267f364 ;*newarray
; - Test::main@2 (line 7)
0x0267f36f: call 0x025bb450 ; OopMap{off=164}
;*invokestatic gc
; - Test::main@7 (line 10)
; {static_call}
0x0267f374: add $0x18,%esp
0x0267f377: pop %ebp
0x0267f378: test %eax,0x370100 ; {poll_return}
0x0267f37e: ret 
;; NewTypeArrayStub slow case
0x0267f37f: call 0x025f91d0 ; OopMap{off=180}
;*newarray
; - Test::main@2 (line 7)
; {runtime_call}
0x0267f384: jmp 0x0267f36f
0x0267f386: nop 
0x0267f387: nop 
;; Unwind handler
0x0267f388: mov %fs:0x0(,%eiz,1),%esi
0x0267f390: mov -0xc(%esi),%esi
0x0267f393: mov 0x198(%esi),%eax
0x0267f399: movl $0x0,0x198(%esi)
0x0267f3a3: movl $0x0,0x19c(%esi)
0x0267f3ad: add $0x18,%esp
0x0267f3b0: pop %ebp
0x0267f3b1: jmp 0x025f7be0 ; {runtime_call}
0x0267f3b6: hlt 
0x0267f3b7: hlt 
0x0267f3b8: hlt 
0x0267f3b9: hlt 
0x0267f3ba: hlt 
0x0267f3bb: hlt 
0x0267f3bc: hlt 
0x0267f3bd: hlt 
0x0267f3be: hlt 
0x0267f3bf: hlt 
[Stub Code]
0x0267f3c0: nop ; {no_reloc}
0x0267f3c1: nop 
0x0267f3c2: mov $0x0,%ebx ; {static_stub}
0x0267f3c7: jmp 0x0267f3c7 ; {runtime_call}
[Exception Handler]
0x0267f3cc: mov $0xdead,%ebx
0x0267f3d1: mov $0xdead,%ecx
0x0267f3d6: mov $0xdead,%esi
0x0267f3db: mov $0xdead,%edi
0x0267f3e0: call 0x025f9c40 ; {runtime_call}
0x0267f3e5: push $0x83c8bc0 ; {external_word}
0x0267f3ea: call 0x0267f3ef
0x0267f3ef: pusha 
0x0267f3f0: call 0x0822c2e0 ; {runtime_call}
0x0267f3f5: hlt 
[Deopt Handler Code]
0x0267f3f6: push $0x267f3f6 ; {section_word}
0x0267f3fb: jmp 0x025bbac0 ; {runtime_call}

 可以看到, placeholder = null; 这个语句被消除了! 也就是说,对于JIT编译以后的来说,压根不需要这个语句! 

所以说,如果是解释执行的情况下,显式设置成Null是没有任何必要的!

 

到这里,基本已经把文章开头说的那个论断给说明清楚了.但是,在文章的结尾,补充一下局部变量表会对内存回收有什么影响.这个例子参照<深入理解Java虚拟机:JVM高级特性与最佳实践> 一书

我们认为

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         byte[] placeholder = new byte[64*1024*1024];  
  5.         //do some  time-consuming operation  
  6.         System.gc();  
  7.     }  
  8. }  

 这样的情况下,placeholder的对象是不会被回收的.可以理解..然后我们继续修改方法体

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         {  
  5.             byte[] placeholder = new byte[64*1024*1024];  
  6.         }  
  7.         System.gc();  
  8.     }  
  9. }  

 我们运行发现

写道
d:\>java -verbose:gc Test
[GC 66798K->66072K(120960K), 0.0021019 secs]
[Full GC  66072K->66017K(120960K), 0.0069085 secs]

 垃圾收集器并不会把对象给回收..明明已经出了作用域,竟然还是不回收!. 好吧,继续修改例子

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         {  
  5.             byte[] placeholder = new byte[64*1024*1024];  
  6.         }  
  7.         int a = 0;  
  8.         System.gc();  
  9.     }  
  10. }  

 唯一的变化就是新增了一个 int a = 0; 继续看效果

写道
d:\>java -verbose:gc Test
[GC 66798K->66144K(120960K), 0.0011617 secs]
[Full GC  66144K->481K(120960K), 0.0060882 secs]

 可以看到,大对象被回收了..这是一个神奇的例子..能想到这个,我对书的作者万分佩服! 但是这个例子的解释,在书中的解释有点泛(至少我刚开始没看懂),所以这里就仔细说明一下.

要解释这个,先大概看一下  Java执行机制  里面局部变量表的部分.

写道
局部变量区用于存放方法中的局部变量和方法参数,.局部变量表用Slot为单位.jvm在实现的时候为了节省栈帧空间,做了一个简单的优化,就是slot的复用.如果当前字节码的PC计数器已经超出某些变量的作用域,那么这些变量的slot就可以给其他的复用.

上面的这段话有点抽象,后面一个个解释.其实方法的局部变量表大小在javac的时候就已经确定了.

写道
在局部变量表的slot持有的某个对象,他是无法被垃圾回收的.因为局部变量表本来就是GC Root之一

 

在class文件中,方法体对应的Code属性中就有对应的Locals属性,就是来记录局部变量表的大小的.例子如下:

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public void foo(int a,int b){  
  4.         int c = 0;  
  5.         return;  
  6.     }  
  7. }  

 通过 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: return
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 this LTest;
0 3 1 a I
0 3 2 b I
2 1 3 c I

 

可以看到,局部变量表的Slot数量是4个.分别是 this,a,b,c ..这个非常好理解.那么,什么叫做Slot的复用呢,继续看例子

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public void foo(int a,int b){  
  4.         {  
  5.             int d = 0;  
  6.         }  
  7.         int c = 0;  
  8.         return;  
  9.     }  
  10. }  

 在 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 LTest;
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并没有马上消除,而是继续留着给下面的局部变量使用..按照这样理解,

 

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         {  
  5.             byte[] placeholder = new byte[64*1024*1024];  
  6.         }  
  7.         System.gc();  
  8.     }  
  9. }  

 这个例子中,在执行System.gc()的时候,虽然placeholder 的作用域已经结束,但是placeholder 对应的slot还存在,继续持有64M数组这个大对象,那么自然的,在GC的时候不会把对应的大对象给清理掉.而在

 

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         {  
  5.             byte[] placeholder = new byte[64*1024*1024];  
  6.         }  
  7.         int a = 0;  
  8.         System.gc();  
  9.     }  
  10. }  

 这个例子中,在System.gc的时候,placeholder对应的slot已经被a给占用了,那么对应的大对象就变成了无根的"垃圾",当然会被清楚.这一点,可以通过javap明显的看到

 

写道

 

public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
Stack=1, Locals=2, Args_size=1
0: ldc #2; //int 67108864
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 Signature
5 0 1 placeholder [B
0 11 0 args [Ljava/lang/String;
7 4 1 a I

Exceptions:
throws java.lang.Exception
}

 可以看到,placeholder 和 a 都对应于Slot[1].

 

这个例子说明的差不多了,在上面的基础上,再多一个例子

 

Java代码   收藏代码
  1. public class Test  
  2. {  
  3.     public static void main(String[] args) throws Exception{  
  4.         {  
  5.             int b = 0;  
  6.             byte[] placeholder = new byte[64*1024*1024];  
  7.         }  
  8.         int a = 0;  
  9.         System.gc();  
  10.     }  
  11. }  

 这个代码中,这个64M的大对象会被GC回收吗..

转载于:https://www.cnblogs.com/silyvin/p/9836797.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30617737/article/details/97562174

智能推荐

AndroidX是什么-程序员宅基地

文章浏览阅读1.8k次。AndroidX 是 Android 开发的一个重要组成部分,它是 Android Jetpack 的一部分。AndroidX 是 Android Support Library(支持库)的继承者,提供了更加模块化、更易于维护和测试的库。它包括一系列用于 Android 应用开发的库和工具,涵盖了 UI 设计、架构、数据绑定、网络通信等多个方面。_androidx

计算机科学技术专业发展分析,计算机科学与技术发展现况分析-程序员宅基地

文章浏览阅读3.1k次,点赞4次,收藏17次。摘要:在这个科技突飞猛进发展的时代,计算机网络已经家喻户晓,在日常生活中也起着不可忽视的作用,计算机的发展提高了人们的生活质量,加快了信息的传播,现如今,各个国家都比较重视计算机科学与技术的发展,使计算机科学与技术在全国综合国力竞争的作用逐渐加大。对于这种情况,加快计算机科学与技术的发展,不但有利于人们更快捷地了解如今的发展趋势和历史,且还推动计算机科学与技术的进步,方便人们的生活。本文首先阐述了..._大学计算机专业自设立以来在大时代背景下的变化

如何用ffmpeg截取视频片段&截取时间不准确的坑_ffmpeg -ss 时间不准-程序员宅基地

文章浏览阅读6.9k次,点赞8次,收藏22次。之前在工作中,有遇到需要程序化截取视频片段的场景,这里使用ffmpeg命令行就可以很容易实现,这里也记录下我们使用过程中遇到的坑,希望对大家也有所帮助。这里的参数-c:v copy指的是复用原始视频的编码格式,如果想切换视频编码也可以直接指定,比如(关于修改视频和音频编码的问题,后续会继续出一篇博客)。这里需要注意的是。_ffmpeg -ss 时间不准

vue点击按钮显示弹窗写法_vue点击按钮弹出提示框-程序员宅基地

文章浏览阅读1.1w次,点赞12次,收藏58次。源码如下<template> <div class="box"> <div> <div class="mask" v-if="showModal" @click="showModal=false"></div> <div class="pop" v-if="showModal"> <!-- 关闭 --> 内容区域 </._vue点击按钮弹出提示框

Ranger启动失败后,重新安装遇到MySQL异常_ranger连接mysql8 驱动失败-程序员宅基地

文章浏览阅读1.5k次,点赞3次,收藏2次。Ranger启动失败后,重新安装启动rangeradmin遇到MySQL异常 启动rangeradmin时报错: SQLException : SQL state: HY000 java.sql.SQLException: Operation CREATE USER failed for ‘rangeradmin’@’%’ ErrorCode: 1396 SQLException ..._ranger连接mysql8 驱动失败

记录异常InvalidKeyOrParametersException: Key length not 128/192/256 bits_key length not 128/192/256 bits.-程序员宅基地

文章浏览阅读1.1w次,点赞4次,收藏5次。Key length not 128/192/256 bits     一般是使用对称算法加密的时候出现的异常,它的意思是指 key的长度不是 128,192或者256 位,注意!!! 并不是不足,而是不是,这个意思指的是key的长度必须要是128,192或者256 位,知道了这个的话就很好去解决这个问题了     以AES 加密为例:     aes.getSecretKey().getEncoded().length 能获取到aes 的 字节长度 (注意是字节)128 位 对应的是 16_key length not 128/192/256 bits.

随便推点

python 几何计算_计算几何-凸包算法 Python实现与Matlab动画演示-程序员宅基地

文章浏览阅读152次。标签:凸包算法是计算几何中的最经典问题之一了。给定一个点集,计算其凸包。凸包是什么就不罗嗦了本文给出了《计算几何——算法与应用》中一书所列凸包算法的Python实现和Matlab实现,并给出了一个Matlab动画演示程序。啊,实现谁都会实现啦╮(╯▽╰)╭,但是演示就不一定那么好做了。算法CONVEXHULL(P)输入:平面点集P输出:由CH(P)的所有顶点沿顺时针方向组成的一个列表1.根据..._凸包算法的jpython实现

Dockerfile_env base_dir是什么意思-程序员宅基地

文章浏览阅读211次。DockerfileDockerfile简介Dockerfile使用流程Dockerfile基本语法认证流程认证流程认证流程Dockerfile简介dockerFile用来帮助我们构建自己的镜像Dockerfile解析过程:Dockerfile使用流程1.创建Dockerfile文件touch Dockerfile2.拉取centos镜像docker pull centos:centos7Dockerfile基本语法官方说明:https://docs.docker.com/eng_env base_dir是什么意思

挂载的硬盘 unraid无法格式化_Linux 系统挂载数据盘-程序员宅基地

文章浏览阅读6.7k次。适用系统:Linux(Redhat , CentOS,Debian,Ubuntu)1.查看数据盘在没有分区和格式化数据盘之前,使用 “df –h”命令,是无法看到数据盘的,可以使用“fdisk -l”命令查看。如下图:2.对数据盘进行分区执行“fdisk -S 56 /dev/sdb”命令,对数据盘进行分区;根据提示,依次输入“n”,“p”“1”,两次回车,“wq”,分区就开始了,很快就会完成。3..._unraid格式化硬盘命令

Vault实战(一)-Vault介绍_vault.cf7-1it4z.workers.dev-程序员宅基地

文章浏览阅读1k次,点赞15次,收藏17次。Vault 是一个基于身份的秘密和加密管理系统。秘密是您想要严格控制访问的任何内容,例如 API 加密密钥、密码和证书。Vault 提供由身份验证和授权方法控制的加密服务。使用 Vault 的 UI、CLI 或 HTTP API,可以安全地存储和管理、严格控制(限制)和审核对机密和其他敏感数据的访问。_vault.cf7-1it4z.workers.dev

基于STM32的BMP图片解码_stm32f103 hal fatfs bmp解码-程序员宅基地

文章浏览阅读8.9k次,点赞5次,收藏44次。1. 硬件描述单片机:STM32F407VET6 TFT-LCD控制器:RA8875 SD卡:金士顿4GB2. 第三方模块文件系统:FATFS R0.113. BMP图片基础知识BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,_stm32f103 hal fatfs bmp解码

SpringBoot 3.2.1 + SpringSecurity 3.2.1 + JWT 实现登录鉴权_springboot3.2 jwt-程序员宅基地

文章浏览阅读530次,点赞7次,收藏7次。文档已验证, 非垃圾文, 欢迎各位大佬斧正._springboot3.2 jwt

推荐文章

热门文章

相关标签