java gc什么时候回收内存_Java的GC垃圾回收机制

一、Java虚拟机内存

JVM内存区域分为两大类:线程共享的内存区、线程私有的内存区

私有内存区伴随线程的产生而产生,一旦线程终止,私有内存区也会自动消除。

共享内存区即在运行期间,每个线程共享的内存区。

私有内存区包含有:

程序计数器:用于指示当前线程所指执行的字节码执行到了哪一行。

执行 Java 方法时,程序计数器记录正在执行的虚拟机字节码指令地址,字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令;

执行本地方法(native 关键字修饰的,由C语言编写完成)的时候,计算器值为undefined。

虚拟机栈:用于执行 Java 方法,程序执行时栈帧入栈;执行完成后栈帧出栈。

线程中的每一个方法在执行的同时,都会创建一个栈帧,栈帧中存储有:局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。

虚拟机栈中的局部变量表,里面存放了三个信息:

* 基本数据类型(boolean、byte、char、short、int、float、long、double)

* 对象引用(reference)

* returnAddress地址,用来记录自己的代码执行到哪一行,注意,程序计数器是用来指示JVM的指令执行到哪一行

本地方法栈:区别于虚拟机栈执行 Java 方法,本地方法栈式用来执行本地方法的,其他用法和虚拟机栈类似。

共享内存区:

方法区:用于存储已被虚拟机加载的类信息(包括版本、field、方法、接口等信息)、final常量、静态变量、即使编译器编译后的代码等数据。运行时常量池是方法区的一部分

注意:方法区已经被最新的 JVM 取消了。现在,被加载的类作为元数据加载到底层操作系统的本地内存区

堆空间:堆区的内存对所有线程是共享的,几乎所有的对象的实例和数组都在这个类中分配内存(现在也有栈上直接分配的)。

GC主要就是在 Java 堆中进行的。

堆空间

堆 是程序再运行期间 请求操作系统分配给自己的 向高地址扩展的数据结构,是不连续的内存区域,堆可以再程序运行时动态的申请某个大小的内存空间。

堆内存中的对象由两种状态:存活和死亡,存活的对象是应用可以访问的,不会被垃圾回收。死亡对象是应用不可以访问的,尚未被垃圾回收机制回收掉的对象。

了解完JVM内存,就来了解一下GC原理:

二、GC垃圾回收机制

1. 为什么需要 GC垃圾回收机制?

因为应用程序对资源操作需要先为资源分配内存然后初始化内存,使用完资源后需要清理资源并释放内存;

目前应用程序对资源的管理方式有三种:手动管理(C、C++)、计数管理(COM)、自动管理(Java、PHP、GO...)

手动管理和计数管理资源的方式太复杂容易出错,一使容易忘记释放内存,二是应用程序如果引用了已经释放的内存,这些都会影响系统的稳定性;

使用自动管理内存的方式可以使程序员不去考虑内存的管理,而专注于业务逻辑的实现。

2. GC垃圾回收机制的工作原理

GC的主要任务其实就是:1> 分配内存 2> 确保被引用的内存不被错误的回收 3> 回收不再被引用的对象的内存空间

这就产生了一些需要解决的问题:1> 哪些内存需要被回收? 2> 什么时候回收? 3> 如何回收?

针对这些问题 GC 都提供了解决方法。

哪些资源需要被回收?

垃圾回收机制在对堆进行回收前,先判断一下内存中哪些对象是还在被引用的,哪些对象是不可能再被使用的。

判断方法:

1)引用计数算法:

当有一个地方引用它时,计数器+1;引用失效时。计数器-1;当计数值为0时,表示对象不可能再被引用。

但是,如果存在两个对象互相引用的话,那么这两个对象就永远不会被回收,

因此,引用计数算法很难解决对象之间相互矛盾循环引用的问题

2)可达性分析算法:(有向图)

把一系列 GC 的根节点 (即:GC Roots) 作为起始点,从节点向下搜索,路径称为 引用链,

当一个对象到 GC Roots 没有任何引用链相连,即不可达的时候,说明这个对象就不可用

在Java中,可以当做GC Root的对象有以下几种:

1)虚拟机栈中的引用的对象,即当前所有正在被调用的方法的引用类型的参数、局部变量、临时值

2)方法区中的类静态属性引用的对象(Java中的静态变量)

3)方法区中的常量引用的对象(主要指声明为final的常量值、String常量池中的引用)

4)本地方法栈中JNI(即一般说的native方法)的引用的对象

什么时候回收?

当被判断为不可达的对象的时候,需要进行筛选,判断一下当前对象有没有覆盖finalize()方法,如果没有覆盖,则回收这个对象;如果覆盖了finalize()方法那么在这次检测中,GC就不会回收这块内存,但是下一次检测的时候就会被回收(finalize()方法只能被调用一次),也就是说,覆盖了finalize方法的对象需要经过两个GC周期才能被清除。

补充:finalize()方法是Object类的方法,子类可以覆盖这个方法,来做一些系统资源的释放或者数据的清理,有时候,我们可以在finalize()方法中再次被引用,避免被GC回收

如何回收?

追踪回收算法(tracing collector)

从根结点开始遍历对象的应用图,同时标记遍历到的对象,遍历完成后,没有被标记的对象就是目前未被引用,可以被回收。

这种算法一个效率不高,另一个会产生大量不连续的内存碎片。

压缩回收算法(Compacting Collector)

把堆中活动的对象集中移动到堆的一端,就会在堆的另一端流出很大的空闲区域。

这种处理简化了消除碎片的工作,但可能带来性能的损失。

复制回收算法(Coping Collector)

把堆均分成两个大小相同的区域,只使用其中的一个区域,直到该区域消耗完。

此时垃圾回收器终端程序的执行,通过遍历把所有活动的对象复制到另一个区域,

复制过程中它们是紧挨着布置的,这样也可以达到消除内存碎片的目的。

复制结束后程序会继续运行,直到该区域被用完。

但是,这种方法有两个缺陷: 1)对于指定大小的堆,需要两倍大小的内存空间,2)需要中断正在执行的程序,降低了执行效率

按代回收算法(Generational Collector)

不同对象生命周期不同,但每次回收都要遍历所有存活对象,这样就造成大量时间浪费,为了提高效率使用按代回收算法。

主要思路是:把堆分成若干个子堆,每个子堆视为一代,算法在运行的过程中优先收集“年幼”的对象,如果某个对象经过多次回收仍然“存活”,就移动到高一级的堆,减少对其扫描次数。

根据按代回收算法的思想,我们将堆内存分为两个堆,一个堆是新生代,一个是老年代;

新生代内存中又分为一块Eden(占堆新生代堆内存的80%) 和 两块 survivor(占20%);

刚新建出来的对象,大部分存放在新生代中的 Eden区,当Eden内存不够,就进行Minor GC 释放掉不活跃的对象;

然后将部分活跃对象复制到一个Survivor中,同时清空Eden区;

(两个Survivor大小空间相同,同一时刻只能有一个为空,一个正在使用)

重复多次后(默认是15次)Survivor中没有被清理的对象就会被复制到老年代中;

当老年代中的内存占用率达到一定比例,则会触发Major GC 释放老年代;

当老年代满了的时候,触发 Full GC 一次完整的垃圾回收;

如果内存还是不够,JVM 会抛出异常 Out of Memory,内存泄漏

GC

特点

Minor GC

发生在新生代,用于清理Eden区的内存,频率高,速度快(大部分对象活不过Minor GC)

Major GC

发生在老年代,用于清理老年代内存,速度慢

Full GC

用于清理整个堆空间,成本较高,会对系统性能产生影响。

开发中容易造成内存泄漏的操作:

创建大量无用对象:比如需要大量连接字符串时,使用String而不是StringBulider/StringBuffer/线程池。

静态集合类的使用:HashMap,Vector,List

各种连接对象未及时释放关闭

监听器的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值