JVM GC之一找出不可达对象并回收

JAVA运行时数据区域

1、程序计数器:当前线程所执行的字节码的行号指示器。一个处理器只会执行一条线程中的指令,为了线程切换后能回复到正确的执行位置,所以每条线程都需要一个独立的计数器。各条线程之间互不影响,独立存储,属于‘线程私有’内存。

2、java虚拟机栈:描述的是JAVA方法执行的内存模型:每个方法执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法的被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。所以也是线程私有的。

3、本地方法栈:和java虚拟机栈发挥的作用类似,只不过Java虚拟机栈是为JAVA方法服务的,而本地方法栈是为Native方法服务。所以也是线程私有的。

4、JAVA堆:JAVA堆是被所有线程共享的区域。所有的对象实例及数组都要在堆上分配。

5、方法区:是各个线程共享的内存区域。主要存储被虚拟机加载的类信息,常量、静态变量、编译后的字节码数据等。有一个别名:非堆。

6、运行时常量池:方法区的一部分,当然也是线程共享的咯。除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放各种字面量和符合引用。

7、直接内存:并不是虚拟机运行时的数据区的一部分。是在NIO中基于通道和缓冲区的I/O方式,使用Native函数库直接分配堆外内存。避免了JAVA堆和Native堆中来回复制数据。和(操作系统中内存页的用户空间和系统空间的虚拟映象类似)

=======================================

垃圾收集器

1、程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此内存的分配和回收都具备确定性。因此不需要考虑回收问题,因为线程结束或者方法结束,内存自然就回收了。而Java堆和方法去不一样,一个接口中的多个实现类需要的内存不一样,一个方法中的多个分支也不一样,只有在程序处于运行时才能知道创建哪些内存,所以这部分的内存的分配和回收都是动态的。

2、引用计数算法

引用计数:每当被引用时引用计数加1,有引用断开时引用计数减1.当引用计数为0时表示该对象可以被回收。JVM并不是通过引用计数算法来进行回收的,主要原因是很难解决对象之间的相互循环引用的问题。

3、根搜索算法:GC Roots

JVM是通过根搜索算法判定对象算法存活的。算法的基本思路是:通过一系列的名为GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC
Roots没有任何引用链相连(图论说:从GC Roots到这个对象不可达)时, 证明此对象是不可用的。

4、可以作为GC Roots的对象

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中的类静态属性引用的对象

方法区中的常量引用的对象

本地方法栈JNI中的引用的对象。

===========================================

对象Life or death

1、根搜索算法中不可达的对象也并非是‘非死不可’的,暂时是‘缓刑’阶段,要真正判断一个对象死亡要经历两次标记过程:如果对象在进行根搜索后发现对象不可达,那它将会进行被第一次标记并且进行筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机掉用过,这两种情况都视为‘没有必要执行’。

如果对象被认为有必要执行finalize()方法,那么这个方法会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。这里的‘执行’也只是指虚拟机会触发这个方法,但并不承诺一定会执行。

finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC会对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()中重新与引用链上的任何一个对象建立了关联,就会被移出‘即将回收’集合,如果没有移出,那就真的离死亡不远了。

finalize()方法只会被系统自动调用一次。

============================================

回收方法区(HotSpot虚拟机中的永久代)

1、Java虚拟机规范中说过可以不要求虚拟机在方法区实行垃圾收集,在方法区进行垃圾回收的‘性价比’一般比较低。在堆中尤其是新生代中,常规的一次垃圾回收就可以回收70%--90%的空间,而永久代的垃圾收集效率远低于此。

2、永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

3、如果字符串‘abc’进入了常量池,但是当前系统中没有任何String对象引用‘abc’常量,也没有其他地方引用了这个字面量,如果此时发生内存回收,而且必要的话 这个‘abc’常量就会被回收掉。

4、判断无用的类必须同时满足三个条件:

该类的所有实例都已经被回收,即JAVA堆中不存在该类的任何实例。

加载该类的ClassLoader已经被回收

加载该类的Class对象没有任何地方引用,而且不能通过反射访问。

5、在大量使用反射、动态代理、CGLib及动态生成JSP和频繁自定义ClassLoader的场景需要虚拟机自动卸载,以防止永久代不会溢出。

===================================================

垃圾收集算法:标记--清楚算法、复制算法、标记-整理算法、分代收集算法

1、标记--清除算法

最基础的算法,分为标记和清除两个阶段:

标记:首先标记出需要清除的对象,标记的过程就是上面的对象Life or death。之所以说是基础的算法,是因为后续的算法都是基于这个算法改进的。

该算法有两个缺点:效率和空间问题(其实计算机最纠结的无外乎这两个地方:时间和空间问题)。标记和清除的效率都不高。空间问题:标记清除之后会产生大量的碎片,可能会导致,当程序在以后的运行过程中需要分配较大的对象时无法找到足够的连续内存而不得不提前触发另一次收集动作。

2、复制算法

复制算法可以解决算法1的效率问题。将可用内存按容量划分成大小相等的两块,每次只使用其中的一块。当这一块的内存用完时,就吧还活着的对象复制到另一块上面,然后再把已使用的内存一次性清楚干净。这样使得每次都是对其中的一块内存回收,内存分配时也不用考虑内存碎片问题,只要移动堆顶指针即可。但是代价是内存缩小为原来的一半。

现在的商业虚拟机都采用这中算法来回收新生代,因为IBM研究表明新生代的的对象98%都是朝生夕死的,所以不需要按照1:1的比例来划分空间。

而是把内存分成一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor空间。当回收时,把Eden和Survivor中还活着的对象一次性拷贝到Survivor空间中,最后清理掉Eden和Survivor空间。HotSpot默认Eden和Survivor空间的大小是8:1,这样可以保证每次可用内存是90%(80%+10),只有10%是浪费的。

当Survivor空间不足时就要依赖老年代进行分配担保(就是说 有大头(老年代)在后面,才放心只留出10%来存放Eden和Survivor中活着的对象,万一Survivor不足时还有老年代在后面撑腰)。

3、标记-整理算法

复制算法在对象存活率较高时就要执行交多的复制操作,效率会变低。重点是如果不想浪费掉50%的空间就需要有额外的空间进行担保,以应对被使用的内存中有100%存活的极端情况,所以老年代一般不能直接使用这一算法。因为老年代没有其他的内存可担保了。

根据老年代的特定,提出了 ‘标记--整理’算法。原理是:标记过程仍然与‘标记--清除’算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有活着的对象都向一端移动,然后直接清理边界以外的内存。

4、分代算法

当前商业虚拟机都采用这种算法回收。没有什么新的思想,只是根据对象的存活周期把内存划分成几块,一般是新生代和老生代。这样可以根据各个年代的特定采用合适的算法进行垃圾收集。

在新生代中,对象的存活周期较短,朝生夕死,采用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。

在老生代中,对象的存活率较高,没有额外的空间对它进行分配担保,必须使用‘标记--清除’或‘标记--整理’算法进行回收。

时间: 2024-12-29 10:06:55

JVM GC之一找出不可达对象并回收的相关文章

递归找出数组中对象属性为某个值的选项,不改变原数组

arr数组每一项为一个对象,并且每一项可能有children属性,其值为同它本身一样的数组,要求找到数组中checked==true的值(前提:如果其子元素checked==true,那么父级的checked一定为true) 这里的逻辑跟该自定义组件相似:http://www.cnblogs.com/XHappyness/p/7451611.html findValue(arr) { let newArr= [].concat(arr);//把值赋值给一个新数组,而不是赋值引用:如果直接 let

找出一个JSON对象修改前后的属性和值

工作了这么久,第一次记录心得,有些汗颜,因为某些原因,觉得还是记录下比较好,话不多说,现在开始.这个需求的背景是我有一个表单,表单对象有array,obj,str等,我现在对这个表单数据某些表单项做了修改,保存的时候需要对比修改前后的表单,列出哪些修改项,来触发某些操作.下面是方法: 1 // 对比JSON数据 2 export function equalsWithA (object1, object2) { 3 var changeField = [] 4 for (var propName

02 JVM 从入门到实战 | 什么样的对象需要被 GC

引言 上一篇文章 JVM 基本介绍 我们了解了一些基本的 JVM 知识,本篇开始逐步学习垃圾回收,我们都知道既然叫垃圾回收,那回收的就应该是垃圾,可是我们怎么知道哪些对象是垃圾呢? 哪些对象需要被回收? 什么时候需要回收呢? 判断算法 引用计数算法 给每个对象设置一个计数器,每当该对象被引用时引用计数器加 1,有引用断开时引用计数减 1.当引用计数为 0 时表示该对象可以被回收. 这个可以用数据算法中的图形表示,对象 A-对象 B-对象 C 都有引用,所以不会被回收,对象 B 由于没有被引用,没

JVM调优之jstack找出最耗cpu的线程并定位代码

一.jstack使用总结 分析java进程,cpu占用高的问题 { 1.找到cpu占用高的进程pid 在top中,按组合键: shift + h ,会按cpu使用从高到低排序2.找到cpu占用高的线程pid top -Hp cpu高的进程pid, shift +h 查找最高线程,显示线程3.jstack 进程的pid | grep -A 线程pid的十六进制 #分析Java应用程序线程堆栈dump出来 printf "%x\n" cpu高的线程pid ==> 得到十六进制 pyt

jvm gc回收机制

jstat -gcutil jstat -gccause pid 1 每格1毫秒输出结果jstat -gccause pid 2000 每格2秒输出结果不断的在屏幕打印出结果 S0S1EOPYGCYGCTFGCFGCTGCTLGCCGCC 87.710.0094.7159.4559.0320832 1961.08912174.676 2035.765 Allocation FailureNo GC 87.710.0094.7159.4559.0320832 1961.08912174.676 2

【转载】Java性能优化之JVM GC(垃圾回收机制)

章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行.当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成.GC优化很多时候就是减少stop-

Java性能优化之JVM GC(垃圾回收机制)

Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行.当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成.GC优化很多时候就是减少stop-the-world 的发生. JVM GC回收哪个区域内的垃圾? 需要注意的是,JV

JVM GC杂谈之理论入门

GC杂谈之理论入门 JVM堆布局介绍 ? JVM堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old ).新生代 ( Young ) 又被划分为三个区域:Eden.From Survivor.To Survivor,其中两个Survivor区的大小一致. ? \(堆新生代老年代老年代java堆 = 新生代(Young) + 老年代(Old) = 老年代(Old) + Eden + From Survivor + To Survivor\) ? 特殊参数说明: JVM参数 默认

JVM虚拟机笔记(二)--HotSpot对象的创建

对象的创建过程: 当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作: 1.检查常量池中是否有即将要创建的这个对象所属类的引用: (1)若常量池中有没有这个类的符号引用,说明这个类还没有被定义!抛出ClassNotFoundException: (2)若常量池中有这个类的符号引用,则进行下一步操作: 2.进而检查这个符号引用所代表的类是否已经被JVM加载: (1)若该类没有被加载,就找该类的class文件,并加载进方法区: (2)如该类已经被JVM加载,则准备为对象分配内存: 3.根