漫谈jvm垃圾收集(1)

本文探讨了JVM垃圾收集的工作原理,重点介绍了如何判断对象是否可回收,通过可达性分析和GC Roots。文章还讨论了安全点的概念,解释了在哪些位置应用程序线程会暂停以允许垃圾回收,并且提到了OopMap数据结构在收集动态变化的GC Roots中的作用。垃圾回收的时间点依赖于所使用的垃圾回收器和回收的内存区域。
摘要由CSDN通过智能技术生成

java程序员相比c++程序员最安逸的一个地方,就是不用负责对象内存的申请和释放,这部分功能都是由jvm自己负责,同时对java应用程序员来说这个过程是透明的。尽管这个过程大部分情况下不需要我们程序员去干预,但是了解这个过程并且合理的配置jvm垃圾回收策略的相关参数,能让我们在编程时更加游刃有余。

我在前一篇博客(1)JVM运行时数据分区中说了,jvm在运行时是将内存分区,不同分区存放不同的内容,其实这个分区一定程度上说是按照jvm的垃圾回收策略进行分区的。

大体上就是那些长期存在的变量、常量和类的Class信息都存在方法区,针对这个区每一次垃圾回收,可释放的空间并不是特别大,所以这个区的垃圾回去并不频繁。

而那些随着程序频繁创建和销毁的对象都放到了堆区,这部分内容存在大量“朝生夕死”的对象,所以这部分区域是垃圾回收的主战场。

而栈区内容是随着线程的而生,并随线程而死,所以这部分区域几乎不存在垃圾回收的情况。

那么,我们要思考的是哪些垃圾该被回收什么时候被回收采用什么方式回收

(1)哪些垃圾该被回收?

回收的对象肯定都是已经不再使用的对象,现在大多数商业虚拟机都是采用可达性分析(Reachability Analysis)来判断对象是否存活。大体上就是选定一些对象(这些对象被称为GC Roots)作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,就标记此对象不再使用了。

可以被选择为GC Roots对象的包括如下几种:

  • 1、虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 2、方法区中静态属性引用的对象
  • 3、方法区中常量引用的属性
  • 4、本地方栈中引用的对象


我们观察这些被选为GC Roots的对象,2、3是在jvm内存中长期驻留的对象,1、4是可以确定属于活着的线程所正在使用的对象。

随着应用的程序的不断运行,这些对象也是在动态变化的,如果每次执行垃圾回收时都要遍历所有属性去寻找GC Roots,那这效率实在也太低了。事实上现在大多数商业虚拟机都维护了一个OopMap的数据结构,来存放这些动态变化的GC Roots。当需要遍历GC Roots时,只需要从OopMap数据结构中查找就可以了。

那么现在有一个问题,就是OopMap是如何去收集动态变化的GC Roots的,可以肯定的是jvm不会在执行应用程序的每个指令期间去收集。对于2、3类型的GC Roots在类加载完成的时候,就可以准确的放入到OopMap数据结构,对于1、4类型在JIT编译过程中,也会在特定的位置记录下栈和寄存器中那些位置是引用。

 [Verified Entry Point]    
    0x026eb730: mov    %eax,-0x8000(%esp)    
    …………    
    ;; ImplicitNullCheckStub slow case    
    0x026eb7a9: call   0x026e83e0         ; OopMap{ebx=Oop [16]=Oop off=142}    
                                            ;*caload    
                                            ; - java.lang.String::hashCode@48 (line 1489)    
                                            ;   {runtime_call}    
      0x026eb7ae: push   $0x83c5c18         ;   {external_word}    
      0x026eb7b3: call   0x026eb7b8    
      0x026eb7b8: pusha      
      0x026eb7b9: call   0x0822bec0         ;   {runtime_call}    
      0x026eb7be: hlt   
上面这段代码取自周志明的《深入理解java虚拟机》一书,可以看到在 0x026eb7a9处的call指令有OopMap记录。

那么现在还有一个问题就是OopMap什么时候去收集GC Roots,我们前面说了,并不是在每条应用程序执行期间收集,实际上jvm里面有一个安全点(SafePoint)的概念,所谓安全点就是在这些位置,程序不至于长期运行下去,常用来选择为安全点的位置有

  • 1、循环的末尾
  • 2、方法临返回前 / 调用方法的call指令后
  • 3、可能抛异常的位置
程序运行到这些位置是一个相对稳定的状态的,可以来做snapshot从而保持分析对象可达性状态的一致性。

要对对象进行标记,这个过程必须要保证对象的状态不再发生变化,所以这个过程需要应用程序的线程暂停。而目前大多数虚拟机都是采用主动式中断应用程序的线程,主动中断不会直接操作应用程序的线程,jvm对编译过后的代码中会设置一个标记位,各个应用程序线程会去主动轮训这个标记位,发现标记位为真时就会自己中断。这个标记位的位置和安全点的位置是重合的,也就是说所有应用程序线程必须都跑到安全点才能挂起。

0x01b6d627: call   0x01b2b210         ; OopMap{[60]=Oop off=460}      
                                       ;*invokeinterface size      
                                       ; - Client1::main@113 (line 23)      
                                       ;   {virtual_call}      
 0x01b6d62c: nop                       ; OopMap{[60]=Oop off=461}      
                                       ;*if_icmplt      
                                       ; - Client1::main@118 (line 23)      
 0x01b6d62d: test   %eax,0x160100      ;   {poll}      
 0x01b6d633: mov    0x50(%esp),%esi      
 0x01b6d637: cmp    %eax,%esi  
以上这段编译代码我们看到虚拟机把0x160100的虚拟页面设置为不可读,线程执行的test指令时就会产生一个触发中断的信号。

(2)什么时候回收垃圾?

一次垃圾回收具体发生在什么时间,这取决于使用了什么垃圾回收器,以及垃圾回收发生的哪个内存区域。这一点我们说完了具体了垃圾回收器之后再说这个。

同时jvm并不是在代码的任何地点都可以进行垃圾回收,因为要进行垃圾回收需要先确认哪些对象需要被回收,这个过程就是标记过程,我们在上面说过,这个过程会造成应用程序线程挂起,也就是stop the world。上面我们说过,应用程序线程轮训主动中断标志位的地方和安全点时重合的,所以jvm也是进入到安全点的时候才进行垃圾回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值