Java GC机制 仅作为自己的学习笔记 条理知识

Java gc 概况

Java GC(Garbage Collection,垃圾回收)机制,是Java相对于C/C++来讲我觉得简单暴力的一个地方,Java开发这不需要专门写内存回收,垃圾清理的代码,free和release啥的就不用过多的去注意了,Java虚拟机存在自动的内存管理和垃圾回收机制,对JVM中的内存进行标记,never stop,防止出现内存泄露和溢出问题

但Java中有finalize()  他的作用主要是清理那些并非使用new 来获得内存区域的对象, 垃圾回收器只知道释放那些new出来的内存(理解有限,不足请指正),所以这时候就可以用finalize(),垃圾回收器准备好释放对象占用的内存空间,首先会调用finalize()的方法,然后在下一次垃圾回收动作发生时,才会真正回收对象占用的内存(这种情况发生在使用本地方法上,java中可能调用了非java方法,可能调用了C中的malloc()来分配内存,而且没有free()函数,这时候不就蛋疼了)

只说HotSpot的GC机制

Java GC机制需要做3件事:确定哪些内存需要回收,确定什么时候执行GC,如何执行GC

1 java内存是如何分配的


程序计数器:程序计数器占比较小的内存区域,用来指示当先所执行的字节码到第几行了,通过改变计数器的值来读取下一行语句指令,程序计数器是 线程 私有的

如果现在在执行的是一个本地方法(native c编写的)计数器值为undefined   

虚拟机栈:一个线程的每个方法在执行的时候,都会创建一个栈帧,栈帧中有局部变量表,操作数栈,动态链接,方法出口,用于存放此次方法调用过程中的临时变量,参数,和中间结果,每一个方法的执行都完成一次栈帧压栈 到 弹栈的过程


局部变量表存放方法相关局部变量,包括基本数据类型,对象引用,返回地址

虚拟机栈里有两种异常,如果栈深度大于虚拟机允许的最大深度,StatckOverFlowError,不过一般虚拟机都允许动态拓展栈的大小,所以线程一直申请栈,知道内存不足  OutOfMemoryError(都是Error级别)

虚拟机栈是 线程 私有的  

本地方法栈:执行本地方法呗  线程 私有

堆区:最牛逼的地方了  在jvm管理中堆区是最大的一块,是GC机制管理的主要区域, 所有 线程 共享 ,在虚拟机启动时创建,为了存放对象实例

堆内存的逻辑上是连续的(物理上不需要),大小可固定,可不固定,如果在GC后依然没有足够的内存分配,会抛出OutOfMemoryError:Java heap sapce异常

方法区:在java虚拟机中,将方法区作为堆的一个逻辑部分来对待,但方法区并不是堆,但我觉得理论上他还是用堆来实现的


方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息,final常量,静态变量

方法区可以选择是否执行GC操作,一般这里的执行垃圾回收很少,所以才被称为“永久代”

String s = "abc";

运行时常量池是方法区的一部分,这个“abc”是存放在方法区里的,常量池在方法区中,但理论上方法区用堆区实现,所以好多人说常量池在堆里,纠正过来,常量池在方法区,这里也可以存放运行时产生的常量(比如String的intern()方法,作用就是String维护里一个常量池,如果调用的字符“cde”在池子里,那就返回字符串地址,没有的话新建一个,返回地址)

Java对象访问方式


refernce指向句柄   然后再去……慢


reference直接指向对象在堆中的实际地址,快,hotspot用的就是这个

2 Java内存分配机制

一般对象的内存分配实在堆上进行,Java内存分配和回收的机制就是分代分配,分代回收,根据对象存活时间分为:年轻带,年老代,永久代(方法区)



年轻代:对象创建时,首先发生在年轻代(大对象可以创建在年老代),大部分很快就不会再用了,很快变得不可达,就被年轻代的GC清理掉了

年轻代的内存分为3个区域:Eden区,两个个存活区(Srivivor0,S1)

垃圾回收分类:

Minor GC

Full GC(Major GC)

1大多数对象分配会在Eden上,Eden连续,所以分配速度快

2 Eden区满了,执行Minor GC,将清理后存活的对象复制到一个存活区,两个存活区保证其中一个是空白的

3 当Survivor0已满了,将依然活着的对象复制到S1去,S0清空,以后Eden执行minor GC后将剩余对象添加到S1中

4当连个存活区切换几次后,hotspot默认15次,依然存活的对象复制到年老代

经过一次GC和复制,一个survivor中保存依然活着的对象,其他的都清空,这次GC时S0,S1再角色切换,这就是“停止-复制”清理法,年老代不采用这种方式!!

年老代:年老代的内存空间一般比年轻代要大,所以发生GC的次数也相对较少,当年老代内存不足后,将执行Major GC--Full GC

如果对象较大,年轻代内存不足,大对象可以直接分配到年老区

当年老区存在指向年轻区的引用时,年老区中有一个512byte的“card table” 存放引用记录,不然年老区整个遍历查询   累死

3 Java GC机制

年轻代:Eden区和S0 S1 占80% 10% 10&  采用“停止-复制”算法进行清理,当Survivor+Eden中存活的对象超过10% 则需要将一部分对象分配到年老代

年老代:对象很多,很大,不易采用“停止-复制”,一般用“标记--整理”算法:标记处依然存活的对象(存在引用的),将所有存活的对象 向一端移动,保证内存连续

在发生minor GC时 虚拟机会检查每次进入年老区的对象是否大于剩余空间,如果没地方了,则执行Full GC 

永久代:常量池中的常量,无用的类信息,   没有引用了直接就回收了,对于无用的类:

1类的所有实例已经回收了

2加载类的ClassLoader已经回收了

3 类对象的Class对象没被引用(没有通过反射引用该类的地方)

4  垃圾回收如何工作

“引用计数”   每个对象都有一个引用计数器,当有引用连接是,计数器+1,解除连接或引用置为null时,计数器-1;

import java.util.ArrayList;

/**
 * Created by albert.bai on 2014/10/31.
 */
public class TestGC {
    public static void main(String[] args) {
        int i = 0;
        ArrayList<Object> list = new ArrayList<Object>();
        while(i<10) {
            Object s = new Object();
            list.add(s);
            s=null;
        }
    }
}
这时候所有Object都没有释放,因为s虽然置null了,但list有对它的引用,依然没法收回

还有一种如果对象间存在循环引用,但这时计数器永远不会是0;也就没法收回!  放弃这种吧

更快的模式:对任何“活”着的对象,一定能最终追溯到其存活在堆栈或静态变量区(方法区)的引用,通常,GC采用有向图的方式记录和管理堆中的所有对象,通过这种方式来确定是否“可达”,这个引用链条可以穿过多个对象层次,从堆栈和静态变量区开始遍历所有引用(我觉得这张有向图开始时是以栈中的引用为起点开始搭建的),对于每一个发现的引用你必须追踪他所有的引用对象,然后是次对象包含的所有引用,一次反复,所以采用的是“有向图”,知道引用的网络全部都被访问到为止,你访问的对象必须是“活的‘,这就解决了存在交互引用的整体对象的问题!



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值