引用计数和回收池 & 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

智能推荐

全卷积网络 Fully Convolutional Networks-程序员宅基地

文章浏览阅读1.1w次,点赞9次,收藏45次。CNN能够对图片进行分类,可是怎么样才能识别图片中特定部分的物体,在2015年之前还是一个世界难题。神经网络大神Jonathan Long发表了《Fully Convolutional Networks for Semantic Segmentation》在图像语义分割挖了一个坑,于是无穷无尽的人往坑里面跳。全卷积网络 Fully Convolutional Networks_fully convolutional networks

签证上的mult是什么意思_申根签证中mult是什么意思-程序员宅基地

文章浏览阅读789次。展开全部申根签证中mult是是多次的意思,指可以在有效期内多次往返申根国家。类型申根签证分62616964757a686964616fe78988e69d8331333431373939为入境和过境两类。1.入境签证有一次入境和多次入境两种。签证持有者分别可一次连续停留90天或每半年多次累计不超过3个月。如需长期停留,可向某一成员国申请只在该国使用的国别签证;2.过境签证指过境前往协定国以外国家的..._mult是什么意思?

webpack 配置_webpack设置 require-程序员宅基地

文章浏览阅读602次。corejs处理,在项目根目录下的 babel.config.js 文件配置。webpack.config.js文件。babel.config.js文件。记录学习 webpack 的过程。.eslintrc.js 文件。_webpack设置 require

Vue组件详解-程序员宅基地

文章浏览阅读454次。文章目录什么是组件?模块化与组件化组件定义命名规则创建组件的方式方式一方式二方式三组件的唯一性什么是组件?什么是组件:组件的出现,就是为了拆分vue实例的代码里的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可模块化与组件化名称概念模块化是从代码逻辑角度进行划分的;方便代码的分层开发,保证每个功能模块的职能单一组件化是从UI界面的角度进行划分的;前端的组件化,方便UI组件重用组件定义命名规则推荐全小写,然后

图像分类篇——使用pytorch搭建ResNet网络_resnet实战:使用resnet实现图像分类(pytorch)-程序员宅基地

文章浏览阅读2.7k次,点赞6次,收藏40次。目录1. ResNet网络详解1.1 ResNet网络概述1.2 Batch Normalization1.3 residual结构1.4 ResNet结构和详细参数1.5 迁移学习2. Pytorch搭建本文为学习记录和备忘录,对代码进行了详细注释,以供学习。内容来源:★github:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing★b站:https://space.bilibili.com/18161609/chan_resnet实战:使用resnet实现图像分类(pytorch)

Beautiful Soup之find()和find_all()的基本使用_soup.find_all-程序员宅基地

文章浏览阅读1w次,点赞17次,收藏66次。1.HTML文本这里以官方文档提供的html代码来演示Beautiful Soup中find_all()和find()的基本使用。<html><head><title>The Dormouse's story</title></head><body><p class="title"><b>The Dormouse's story</b></p><p class="stor_soup.find_all

随便推点

python heapq 优先队列数组内部不单调递增,但是单独一个个出队列单调递增_python heapq 维护单调-程序员宅基地

文章浏览阅读65次。在使用heapq的优先队列时,发现了标题中的现象,一度怀疑优先队列出错了。总之,使用heapq访问q[0]后面的内容时要注意。具体原因没有深究,有知道的大佬可以指点指点。_python heapq 维护单调

phpStudy环境安装SSL证书教程(apache)-程序员宅基地

文章浏览阅读85次。https://cloud.tencent.com/product/ssl此链接是检测域名 证书的可以检测一下下面是证书配置 小白呢亲测作为PHP程序员,我们一定要学会使用phpStudy环境集成包,PHPstudy用起来方便,快捷,对于刚入门的PHP初学者来说phpStudy是个好东西,我本文我们就和大家分享一下phpStudy环境如何安装SSL证书。第一步:修改apache目录..._d:/phpstudy/apache/conf/ssl/ca.key do not match

Python之小词典应用-程序员宅基地

文章浏览阅读5.9k次,点赞5次,收藏41次。Python之小词典应用这个学期专业开了python课,最后老师布置了一个作业:用python制作一个英语小词典的应用,遂做了一下。题目要求:制作英文学习词典。编写程序制作英文学习词典,词典有三个基本功能:添加、查询、和退出。程序读取源文件路径下的txt格式词典文件,若没有就创建一个。词典文件存储格式为“英文单词 中文单词”,每行仅有一对中英释义。程序会根据用户的选择进入相应的功能...

【轮式平衡机器人】——TMS320F28069片内外设之Timer_IT(补:CCS程序烧录方法)_机器人人烧录程序调试过程-程序员宅基地

文章浏览阅读1.1k次,点赞27次,收藏24次。TMS320F28069 的定时器中断功能。在微控制器或数字信号控制器中,定时器是一个非常重要的外设,它可以用来产生固定时间间隔的中断,或者用来精确计算时间。_机器人人烧录程序调试过程

计算机输入法无法启动,Win7系统开机后输入法总是消失如何解决-程序员宅基地

文章浏览阅读1.2k次。输入法是我们在使用电脑的时候经常会用来输入文字的工具,一般在开机的时候都会自动启动并在任务栏右下角显示,可是有不少win7系统用户却遇到开机后输入法总是消失的情况,要怎么解决呢?现在为大家带来Win7系统开机后输入法总是消失的详细解决步骤。1、在win7系统中点击“开始”--“运行”输入--regedit,打开“注册表编辑器”,找到“HKEY_USERS\.DEFAULT\ControlPanel..._开机输入法消失

手把手教你利用ORB_SLAM2制作自己的数据集(手机拍摄即可,不需要ROS)_orbslam2 自己的视频-程序员宅基地

文章浏览阅读633次,点赞7次,收藏14次。利用ORB_SLAM2运行自己数据集_orbslam2 自己的视频

推荐文章

热门文章

相关标签