java 内存分布,内存溢出,垃圾回收;GC工作流程

Java 内存区域分布

 Java虚拟机在执行Java程序过程中会将所管理的内存划分若干个区域,如下图为几个运行时数据区:


  1. 程序计数器: 它是一小块内存区域,它可以当做当前线程执行字节码行号记录器。字节码解释器通过改变重写程序计数器的值来确定下一步执行那个字节码指令。

  2. 虚拟机栈:和程序计数器一样,Java虚拟机栈也是线程私有的。 和当前线程的生命周期一致。当执行一个Java方法时候会创建一个栈帧,用来寄存方法中的局部变量,当方法执行完毕,这个栈也随方法的结束而销毁。

  3. 本地方法栈:他和虚拟机栈是类似的,不同的是:虚拟机栈是 为执行Java方法(即字节码)服务,而 本地方法栈是 为虚拟机执行native 方法服务。和虚拟机栈一样的是 ,他们都会遭遇栈溢出和内存溢出异常。

  4. 堆:是虚拟机中最大一块内存区域,是所有线程共享的一片区域,她是虚拟机启动的时候就创建的。 他的唯一目的就是用于存放 实例对象 ,几乎所有创建的实例对象分配的内存都来与此地。堆是垃圾回收器主要管理的片区,目前的虚拟机都采用分代收集算法,将对象分类为 新生代和老年代,采用相应的算法来回收垃圾内存。

  5. 方法区:和堆一样,她属于线程共享区域。但是他存储的一般是Java类加载的class字节码信息,常量,静态变量等。

内存溢出

      除过程序计数器外,其他的内存区域都可能出现内存溢出的风险。我们来看一下。

  1. 堆内存溢出: 发成位置在堆区域,由于堆用来存储对象实例,不言位于堆内存溢出是由于不断创建对象,而对象实例未被及时的垃圾回收器回收造成的。一般我们在书写代码过程中主要 死循环中创建对象,同时可以优化 堆大小, xxs和xxm 来修改。

  2. 方法区和运行时常量池内存溢出: 常量池是方法区的一部分,常量池被分配在永久代,常量池超过内存限制会导致 oom,一般通过 XX:permSize 和XX:MaxPermSize 限制方法区内存大小,从而限制常量池的大小。

垃圾回收

哪些垃圾需要回收?     

  1. 对象是死是活的判定?

          1.1  引用标记法:它的原理是:给每个对象添加一个计数器,当这个对象呗引用一次,计数加1;当引用失效,计数器减1;任何计数器为0的对象已经没有被引用了。 但是现在主流虚拟机没有采用该算法的原因,是由于不能解决对象彼此相互引用的矛盾问题,但是这个两对象并未在其他地方使用,导致该对象无法被回收,造成内存泄漏。

         1.2  可达性分析算法:这个算法的基本思想是:通过一系列的 "GC roots"  的对象作为起始点,从这个节点向下搜索,这个搜索的路径 被称为 引用链 (ref chain ),当这个对象的到 GC roots 没有任何引用链 则判定为不可达,此对象不可用。

  Java  GC roots 对象 种类: 1.2.1  虚拟机栈中引用对象   1.2.2  方法区中的 类静态属性的引用对象  1.2.3 方法区中 常量 的引用对象  1.2.4  本地方法栈(native 方法)的引用对象

        1.3  无论引用标记算法还是可达性分析算法 都是通过 判断引用来断定对象的死与活。引用的分类: 强应用  类似 Person p=new Person(), 引用一直存在,垃圾回收器永远不会回收。 软引用  那些还在用但非必须的对象,在oom之前,jvm会把它列到2次回收的范围,如果内存空间不足,那么会抛出oom异常。  弱引用 那些非必须的对象,当垃圾回收器工作时,无论内存是否充足,1次会被回收掉。虚引用 被称为幽灵引用,无法通过虚引用来取得一个实例对象,为一个对象关联虚引用只是让他被垃圾回收时收到一个通知。     

什么时候回收?

 可达性分析算法,当对象为关联到 GC roots 引用链,认为对象不可达,先进行第一次搜素标记筛选,筛选的标准是 该对象是否必要执行 finalize() 方法,如果该对象有必要执行finalize() 方法,会把对象放到一个 F-queue 对列中,随后GC会对F-queue进行第二次扫描标记,如果在finalize()方法时候该对象重新建立ref chain ,那么它在第二次标记是被移除了“即将回收”的集合,该对象逃脱被回收的宿命,这个时候还没逃脱,那么对象要被真真回收了。 对于一个对象 finalize() 只会被系统调用一次。

如何回收?

  1.       垃圾收集算法   

              1.1  标记-清理算法: 分为 标记,清理两部分,标记 :引用不可达的对象进行清除标记, 接下来清理掉一标记对象。缺点: 存在大量不连续的内存空间,浪费资源 。图示:

           1.2 复制算法:  将可用内存分为2部分,每次使用其中1块,当使用完毕,将存活的对象复制到另一块区域,最后将原来的1块内存一次清理。 缺点: 将原有的内存利用率降到一半,代价太大。这种算法适用于新生代对象,因为复制的存活对象量较小。图示如下:

            1.3  标记--整理算法: 复制算法在对象存活率较高的情况下要进行大量的复制操作,效率低下。 标记--整理,是先标记 不可达对象,将存活对象向一端移动,然后清理掉边界端以外的内存。图示如下:

  1.4  分代收集算法: 目前商用虚拟机都采用 这种分代收集算法。 对于 新生代对象, 采用复制算法; 对于老年代 对象采用 标记--清理 或者 标记--整理 算法。

2  垃圾回收器

      2.1   serial  收集器: 是一个单线程的收集器,这里的单线程不是只有一个cpu 或者一个线程来工作 , 而是 当开始垃圾回收工作时候,其他线程的工作任务要暂停工作 ,直到垃圾回收完毕。jdk 1.3 开始使用 ,工作图示如下:

      2.2  parNew 收集器: 其实是 serial 收集器的多线程版本,其他工作原理和serial 完全一致。 运行图示如下:

     2.3  Parallel 收集器 , serial  old 收集器,  parallel 收集器, CMS 收集器, G1 收集器 等可自行了解。值得一提的是G1 收集器是目前最前沿的收集器,可并行并发,分代收集,空间整理,可预测停顿的特点。

内存分配和回收策略

                Full GC 还是 Minor GC?   

              1.1  大多数情况下,对象被分配在新生代(Eden)区,当 Eden区内存没有足够的内存分配时,虚拟机将触发一次 minor GC ,minor GC 执行 要比 Full GC 频繁的多。而对于 老年代 内存不足分配,会触发一次 Full GC ,Full GC 要比 minor GC 慢很多,速度慢10倍以上。

 

一个完整GC的流程? 新生代晋升老年代的条件?

流程:

1. 每次分配内存都只会使用 1个 Eden区和 1个 survivor。对象默认存储在 Eden 区,但是如果对象太大,Eden 区放不下,直接进入老年代。

2. 将 survivor 区域分为 s0,s1;当进行GC时候,将Eden,s0的存活对象复制到 s1,这样循环,每次GC保证其中一块 survivor是空闲的,当存活的对象量过大,有老年代担保,放到老年代。

3. 当一个对象经历了 15次GC,那么直接放置到 老年代。

4. 如果survivor空间中,年龄相仿的对象超过survivor区域的一半,那么直接进入老年代。

对象新生代晋升到老年代的4个条件:

1. Eden 对象发不下了,直接放置到老年代。

2. 存放 存活对象的 survivor区太小,不足以放下存活对象。

3. 对象经历了15次GC。

4. survivor 空间中年龄相仿的对象大于 survivor 区域的一半,这些对象放入 老年代

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值