Java虚拟机之内存管理

原创 2017年09月19日 08:45:21

内存模型

一说到内存管理,首先需要了解它的内存模型。
虚拟机的内存模型在jdk1.8之后有了一些变化,我们分开来看,请看下图:

这里写图片描述

由图我们可以看出,jdk每个版本都会有新生代和老年代,唯一不同的是小于1.8的版本为永久代,而大于等于1.8的版本去掉了永久代,转为元空间(Meta Space)。

永久代也就是存储的数据区里面的方法区,如果程序在运行中发生PermSpace溢出,则说明永久代内存不够,需要调整JVM参数增加永久代内存空间。

jdk1.8以后出现了MetaSpace,它和永久代不同的是,它的内存空间是动态扩展的,当然我们也可以设置MaxMetadaSpace来设置最大元空间内存数量,也就是在1.8以后设置PermSize是无效的。

本文主要讲解jdk1.8以前得内存管理机制

垃圾回收机制

在现代编程语言中,对垃圾回收算法主要有两种方式:引用计数器和可达性分析。

引用计数器

引用计数的原理大致是这样的:为每一个创建的对象设置一个引用计数,每当对象被引用一次后,引用计数加1,当对象引用失效时,引用计数减1,当引用计数为0是说明没有任何对象引用了,即可释放该对象。

引用计数的一个弊端是,他无法解决对象间相互引用的问题,比如下面这段代码:

public class RefrenceCountingGC {

    private Object instance = null;

    public static void main(String[] args) {
        RefrenceCountingGC gc1 = new RefrenceCountingGC();
        RefrenceCountingGC gc2 = new RefrenceCountingGC();
        gc1.instance = gc2;
        gc2.instance = gc1;

        gc1 = null;
        gc2 = null;
        //假设采用引用计数算法,在这里发生GC,gc1和gc2能否被回收?
        System.gc();
    }
}

两个对象始终处于相互引用阶段,因为引用计数永远无法为0,因此就不能自动释放它。

可达性分析

为了解决引用计数出现的这些问题,可达性分析算法出现了。

在主流的编程语言中,java和c#都是通过可达性分析来判定对象是否存活的。

它的原理大致是:通过一系列的被称为“GC Roots”的对象作为起始点,然后从这些节点开始向下搜索,搜索的路径连成的一条线,我们称之为“引用链”,当前一个对象到GC Roots没有任何引用链,即我们说的这个对象不可达时,则说明该对象是可以被回收的,通过下图可以更好的理解:
这里写图片描述

当一个对象不可达时,并不能代码这个对象就能马上被回收,他会处于死缓状态,而一个对象真正要回收时,至少需要经历两次标记。第一次标记的前提条件是,看该对象是否覆盖了finalize()方法或者finalize()方法被虚拟机调用过,如果覆盖了finalize()方法并且虚拟机还没有调用过,这时会标记它,该对象还有机会存活,方法很多,比如在finalize()方法内存引用该对象。

如果进行第二次回收时,由于虚拟机已经调用过finalize()方法,就不会再调用他了,这时该对象就会真正宣告死亡了。

请看下面这段代码:

public class GCRoot {

    private static GCRoot instance = null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalized执行");
        instance = this;
    }

    public static void main(String[] args) throws Exception{
        instance = new GCRoot();
        instance = null;
        System.gc();
        //因为finalize执行优先级较低,这里等待0.5秒
        Thread.sleep(500);
        if(null != instance){
            System.out.println("对象拯救成功!");
        }else{
            System.out.println("对象被释放!");
        }

        //和上面的代码一样,不会执行finalize方法,所以拯救失败
        instance = null;
        System.gc();
        if(null != instance){
            System.out.println("对象拯救成功!");
        }else{
            System.out.println("对象被释放!");
        }
    }
}
运行结果:
finalized执行
对象拯救成功!
对象被释放!

java引用

jdk1.2之前的引用很简单,这里我们不探讨,我们主要探讨jdk1.2之后的引用。

java中将引用分为了:强引用、软引用、弱引用和虚引用。

强引用

强引用在java程序中最常见的一种引用类型,类似Object o = new Object()这类引用,只要强引用还在,垃圾回收器就永远不会回收它。

软引用

软引用通常用来描述一些可以用但非必须的对象,在内存溢出之前会先回收掉软引用相关联的对象,如果回收后内存依然不够,则才会抛出内存溢出异常。

弱引用

弱引用用来描述一些非必须的对象,但是它的强度比软引用还有弱一些。被弱引用关联的对象只能存活到下次垃圾回收器工作之前,当垃圾回收器开始工作时,无论当前内存是否足够,都会回收掉被弱引用关联的对象。

虚引用

虚引用是最弱的一种引用类型,为对像设置虚引用关系的唯一目的就是能在这个对象被垃圾回收之时收到一个系统通知。

垃圾收集算法

1、标记-清除算法

这是最基础的一种垃圾收集算法,后续所有的算法都是在这个算法的基础上进行扩展。

通过算法的名字大致能够看出,该算法分为了“标记”和“清除”两个阶段:首先需要标记出需要回收的所有对象,待标记完成后清除掉所有标记过的对象。这个算法的不足主要有两个:一是性能问题,标记和清除两个阶段的性能都不高,二是标记清除后会产品不连续的大量内存碎片,内存碎片太多会导致下一次在需要分配占用大量内存的对象时,无法找到足够的连续碎片而不得不再一次触发垃圾收集动作。

2、复制算法

为了解决效率问题,复制算法出现了。这种算法会将可用内存区域划分为大小相同的两块,当需要垃圾回收时,会先将可用的对象复制到另一个内存区域,从而将当前区域一次性清除。这样做的好处是每次都将一整块内存区域清除掉,从而避免了大量的内存碎片出现。

目前主流的商用虚拟机大多是采用复制算法来回收新生代。

3、标记-整理算法

当对象的存活率较高时采用复制算法,效率就会很低,因此对于老年代一般不采用复制算法。

鉴于这种问题,一种称之为“标记-整理”算法的思路出现了。它和“标记-清除”算法一样,都需要先进行标记,但是它不会简单一次性清除标记的对象,而是将所有存活对象都移动到另一端,然后清除掉边界外的对象。

4、分代收集算法

分代收集其实就是将内存划分为几块,比如将java堆划分为新生代和老年代,根据不用年代采取最适合的算法,所以目前主流商用虚拟机都是采用这种方式。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Docker_入门?只要这篇就够了!(纯干货适合0基础小白)

与sgy一起开启你的Docker之路 关键词: Docker; mac; Docker中使用gdb无法进入断点,无法调试; 写在前面 这篇博客适合谁? 对于Docker并不了解,只是有一点模糊的...

想要清肠排毒,就喝汁己青汁!

保持肠胃畅通,对于我们的身体是非常重要的。积累毒素会影响我们气色和皮肤。只有身体排毒好了,才能让身体更加轻盈人也更加精神。 日常多加注意一些小细节可帮助你减少毒素积累。 多喝水 早晨最好空腹喝水...

史上最简单的 MySQL 教程(三)「 MySQL 数据库」

MySQL 数据库MySQL 数据库是一种C\S结构的软件,即分为:客户端和服务端。若想访问服务器,必须通过客户端;服务器应该一直运行,客户端则在需要使用的时候运行。

Node.js开发入门—使用对话框ngDialog

做网站经常会遇到弹出对话框获取用户输入或弹出对话框让用户确认某个操作之类的情景,基于AngularJS的扩展模块ngDialog可以帮我们优雅地完成这类事情。
  • foruok
  • foruok
  • 2015-09-06 07:15
  • 13266

ACM竞赛路上亲爱的坑们

写在前边:这些梗都是敝人自己做题和比赛时曾经坑过自己的地方,特别在这里记录一下,所有的链接都是本博客中的题解链接(有大致题意说明和代码),原题请到OJ上自行寻找。目的是提升自身姿势。欢迎大佬们给我提出...

经验分享-前端与后端的接口、HTML分离

在WEB项目中 前后端不分离多人开放效率还不及一个人开发效率来的高,今天分享一个概念

java实现二维码([带]logo)的绘制和解析(zxing by google)

二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础...

Java基础之(三十七)Java多线程编程<二>

控制线程join线程Thread提供了一个线程等待另一个线程完成的方法:join方法。当在某个程序执行流中调用其他线程的join方法,调用join方法的那个线程将被阻塞,直到被join方法加入的joi...

std::map 如何使用结构体作为自定义键值

在使用map时,有时候我们需要自定义键值,才能符合程序的需要。 比如我们需要使用自定义的结构体来作为map的键值.
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)