好久没有写博客了,深感羞愧,今天聊一下Java的内存管理

简单介绍

Java相比传统语言(C,C++)的一个优势在于其能够自己主动管理内存。从而将开发人员管理内存任务剥离开来。

本文大体描写叙述了J2SE 5.0 release中JVM对于内存是怎样管理的。

而且为选择和配置相应的收集器,配置收集器的參数提供了一些建议和參考。

手动VS自己主动内存管理

内存管理是能够识别哪些释放的对象不再使用。释放掉这些对象所占用空间的一个过程。

在非常多编程语言中,内存管理是开发人员的责任。可是管理内存的任务具有一定的复杂性。会导致非常多错误,影响到应用的行为,而且令程序崩溃掉。结果,开发人员非常大的一部分时间都是在debug和修正这些错误。

在手动内存管理中一个常常发生的问题就是dangling references。非常有可能当在释放某个对象占用的空间时,仍然包括其它对该销毁对象的引用,在这个时候,当这些引用指向了新的对象时,运行结果是无法预期的。

还有一个常见的问题就是space leaks。产生泄露的原因在于,当内存被分配了,可是却没有引用的情况下,以后就无法再次释放掉了。举个样例。假设开发人员试着释放一个链表,可是写的程序出了点小bug,值释放了头结点。那么链表中后面的对象就无法找到了。也就再被回收掉。一旦泄露过多,整个内存就会崩溃掉。

而相对于手动管理内存。在面向对象编程语言中,通常使用是自己主动管理内存技术。也称之为垃圾收集器。自己主动的内存管理对接口进行了更高层次的抽象。

垃圾收集器攻克了dangling reference问题。由于假设一个对象还被引用的话,是不会被垃圾收集器回收掉的。同一时候,垃圾收集器也攻克了space leaks问题。由于那些泄露的空间。属于没有被引用的对象,会被垃圾收集器回收掉。

垃圾收集器的概念

垃圾收集器主要有一下一些职责:

  • 分配内存
  • 确保引用的对象仍然还在内存中
  • 释放掉那些不可达对象所占用的空间

有引用对象通常称之为存活的对象。没有引用的对象通常称之为死亡对象,也被觉得是垃圾。

检索和释放掉死亡对象的过程就称之为垃圾收集。

垃圾收集器攻克了非常多非常多的内存管理问题,可是并非全部。

当然了。开发人员能够持续不断的创建对象,而且始终保持对他们的引用,直到没有可用的内存为止。垃圾收集本身也是一个复杂的任务,须要消耗相当的时间和资源的。

关于组织对象,分配和释放空间的算法都是由垃圾收集器处理的,是被隐藏在开发人员的视线之外的。空间一般是从一个非常大的内存池来释放的。称之为

垃圾收集的调度一般是取决于垃圾收集器本身的。

通常来说,整个堆或者堆的子集会在其填充满或者到达一定占比阈值的时候进行垃圾回收。

分配的任务包括在堆中找到一块没有使用的内存,当然。这一任务并不简单。这个动态分配空间的算法基本的问题就是避免碎片,尽量保证分配空间和释放空间的高效。

令人惬意的垃圾收集特性

垃圾收集器必须既保证安全。而且充分理解代码。也就意味着,存活的数据必须不能够被错误的释放,而垃圾不应该在几个回收周期之后,仍然存活。

当然。假设垃圾收集器能够高效的运行,不会在应用正在运行的过程中,进行长时间的停顿。肯定是非常好的。然而,在绝大多数的系统中,通常都会须要在空间。时间,频率上做出权衡的。举个样例,假设堆空间非常小的话,垃圾收集的速度会非常快。可是堆会更快的充满对象,也会须要进行更为频繁的垃圾收集。相反。假设堆空间配置的较大的话,那么堆充满须要的时间会更久,垃圾收集也不会运行的非常频繁。可是单次的垃圾回收须要的时间会更久。

垃圾回收假设能够有效限制分片的话,无疑也是非常好的。

当回收掉部分垃圾对象所占用的内存空间之后,空暇的空间可能以小块的形式存在于多个区域的。当出现这样的情况时,当再次为一个较大的对象申请空间的时候,可能会无法获得足够的空间。

一种消除碎片的方式叫做叫做内存紧缩。

扩展性相同是垃圾收集器所须要的。

分配操作不应该成为多进程。多线程应用的扩展性瓶颈,收集操作相同不应该成为瓶颈。

设计选择

在设计和选择垃圾回收算法的时候。通常须要作出一些抉择:

  • 选择串行回收还是并行回收。当使用串行回收的时候,每一个时间节点都仅仅会发生一件事情。

    举个样例,甚至是在多个CPU可用的情况下,也仅仅会有一个CPU来运行垃圾收集操作。而当使用并行收集的情况下。垃圾回收操作会分成不同的子模块。由不同的CPU并行运行。并行的操作会令回收操作速度更快。可是会有更高的复杂性成本以及潜在的碎片情况。

  • 并行回收VS全局暂停回收。当stop-the-world垃圾收集器运行的时候,应用的运行会在进行垃圾回收的时候全然暂停。当然,也能够同一时候并行运行垃圾回收操作和应用本身的处理。

    通常来说,并发收集器会将绝大多数任务并行运行完毕。可是有时也仍然会有较少的暂停应用的情况。

    stop-the-world垃圾收集器比并发收集器要更简单一些。由于在收集时,会将堆锁定,对象在此期间是不会发生变动的。当然。缺点是有些应用是不希望应用暂停的。相应的,使用并发收集器的话,应用暂停的时间会更短,可是收集器必须额外考虑,当应用在使用对象的时候,是否该运行更新操作。这回为并发收集带来额外的工作,在堆较大的时候,会带来一定的性能影响。

  • 压缩VS不压缩VS拷贝。当垃圾收集器决定了那些内存中的对象是存活的,那些是垃圾的时候,能够选择压缩内存,将存活的对象收集到一起,又一次利用剩余的空间。在压缩之后,能够非常easy的给新的对象分配空间。

    能够使用一个指针来跟踪分配对象的结尾。相对于压缩收集算法,非压缩收集算法会释放垃圾对象所在的位置。

    可是并不会将存活的对象压缩到一起,所以不会像压缩算法那样。能够留出较大的空间在新分配对象的时候使用。

    非压缩算法的优点是垃圾收集的速度非常快,可是内存碎片问题会比較严重。一般来说。非压缩算法的分配成本也要高于压缩算法。

    由于必须要搜索一块足够大的连续内存空间来给新的对象。

    还有一个算法就是拷贝收集。讲全部存活的对象复制到还有一块内存区域。优点在于,之前使用的内存区域就能够当成是全然全新的了。

    劣势就是须要拷贝所需的内存空间。

性能上的度量

在考虑垃圾收集器性能的时候,有下面一些方面须要考虑:

  • 吞吐:指的是不在垃圾回收上面使用的时间占比。

  • 垃圾收集负载:是吞吐的对立面。也就是垃圾回收上面的时间占比。
  • 暂停时间:当在运行垃圾回收的时候。应用停止运行的时间。

  • 收集频率:收集多久运行一次,这个值通常和应用的运行时相关的。

  • 占用的空间:对空间的占用的衡量,比方堆得大小。
  • 迅捷:当一个对象成为了垃圾对象和它占用空间可用的时间间隔。

交互式的应用须要较低的暂停时间,而总运行时间要比非交互式的应用要求要搞。而实时应用会在垃圾回收的暂停时间和垃圾回收的时间占比上都有较高的要求。而在个人计算机或者是嵌入式系统中,占用空间可能是应用更应该考虑的问题。

分代收集

当使用了分代收集技术的时候,内存是分成不同的代的,也就是将不同年纪的对象分放到不同的对象池中。举个样例。Java中最常使用的配置有两个不同的年代:年轻代,老年代,分别用来存放年轻的对象和年老的对象。

在每一个不同的代中,能够使用不同的垃圾回收算法。而每一个算法能够在其自己的年代中依据该年代的特性进行优化。

每一代的垃圾收集器都有例如以下的一种假设,称之为weak generational hypothesis,觉得多数语言中实现的应用(包括Java),有例如以下特点:

  • 大多数分配的对象都不会存活非常长的时间。
  • 少数存活非常久的对象会一直存在着。

如图所看到的:

年轻代进行的垃圾回收相对来说。会相对更频繁,而且运行也更迅速,由于年轻代对象通常较小,而且会引用非常多生命周期非常短的对象。

而一些对象在几次年轻代回收都没有回收掉的话,就会晋升成为老年代对象。例如以下图:老年代通常比年轻代要大,其占用的增长速度会变慢。

所以,老年代垃圾回收不会非常频繁。可是回收的时间要更久一些。

为年轻代选择的垃圾回收算法一般会优先考虑速度,由于年轻代的回收通常来说是更频繁。还有一方面。老年代考虑的算法一般是更考虑空间的有效性。由于老年代会占用很多其它的堆内空间,老年代算法须要更好的处理低密度垃圾回收。

J2SE JVM中的垃圾回收器

J2SE JVM中包括四种垃圾收集器。全部的垃圾收集器都是分代的。本节描写叙述了回收的分代和类型,以及讨论为何对象的空间分配一般是高效和迅速的。然后为每种垃圾收集器提供了具体的信息。

HotSpot分代

在JVM中,内存被分成三代来管理的。各自是前面提到的年轻代,老年代以及永久代。绝大多数对象都是被初始化到年轻代的。而老年代中包括的对象一般是多次回收都没有回收掉的年轻代对象,以及部分非常大的对象,这些对象是直接分配到老年代的。

永久代中包括一些对JVM方便进行垃圾收集管理的信息。比方描写叙述类和方法的对象,还有类和方法本身。

年轻代包括一个叫做Eden的区域和两个稍小的survivor区域。例如以下图。

大多数对象都是直接初始化在Eden区域的。

(前面提到过,少数非常大的对象可能直接分配到老年代的)survivor空间持有那些至少一次从年轻代垃圾回收下存活的对象。

垃圾收集器会给这些对象再进入老年代之前一些机会,让他们在进入老年代之前仍然在年轻代中,能够被回收掉。在不论什么给定的时间,一个Survivor的空间(从图中标记为From)持有这样的对象。而还有一个直到下一次垃圾回收之前都是空的。

垃圾回收类型

当年轻代对象空间慢了,年轻代的垃圾收集就開始了(有的时候,也称之为minorGC)。当老年代或者永久代对象空间慢了,运行的垃圾回收称之为majorGC。通常来说,年轻代是优先收集的,使用的回收算法也是依据其年代的特点来特别设计的。由于通常年轻代对垃圾的识别和回收对效率要求更高。老年代的回收算法是同一时候运行在老年代和永久代的。一旦发生内存压缩。每一代都是分别进行内存压缩的。

有的时候,老年代已经空间不足,无法继续接受年轻代的对象了。在这样的情况下。除了CMS收集器。全部的手机都不会运行,年轻代的回收算法也不会运行。相反。会在整个堆上使用老年代回收算法。(CMS老年代算法属于特殊情况,由于它不会对年轻代进行收集)

高速分配

在非常多情况下,内存中都有非常大的连续空间用来给对象使用。

这些内存块的空间分配是配合简单的bump-the-pointer技术是十分高效的。bump-the-pointer技术就是通过一个指针来跟踪上一次释放对象空间的结尾。当新的分配请求过来的时候。JVM仅仅须要推断指针和当前代结尾之间的空间是否足够就能够了。假设能够的话,就挪动指针,而且初始化对象。

对于多线程应用来说。分配操作是必须保证线程安全的。假设使用全局锁来保证分配操作是线程安全的,那么分配操作进入某一代将会成为一个性能上的瓶颈。

相反。JVM使用了一个技术叫做Thread-Local Allocation Buffer技术(TLABs).该技术会将分配操作先写入线程本身的缓冲区中,来提高多线程分配操作的吞吐量。由于,一旦每一个线程将分配操作写入到自己的缓冲区的话。就能够使用bump-the-pointer技术实现高速分配,而且全程是不须要锁来进行堵塞操作的。当然,偶然的情况下,当线程内部的缓冲区已经填满了,无法写入很多其它的对象的时候,就必须使用同步操作来保证分配的线程安全性了。当然,使用TLABs同一时候也有一些降低空间浪费的技术。

TLABs的空间的浪费平均不到Eden区的1%。

使用TLABs技术和bump-the-pointer技术令分配操作性能非常高。大概仅仅须要10个本地指令的时间。

串行收集器

当使用串行收集器的时候,不管是年轻代还有老年代的手机,都是串行收集的(使用一个CPU),收集的过程中。会停止应用的一切运行。

串行收集——年轻代

下图展示了年轻代使用串行收集器收集的一些操作。存货的对象从Eden复制到空的survivor空间,也就是图中的TO区域,当然。假设对象太大是不会进入到To区域的,而是直接进入老年代。

在survivor中From区域的对象中。仍然相对年轻的对象复制到To空间。而比較老的对象会进入到老年代。注意,假设To空间满了,没有复制到To区域的Eden和From区域的对象将直接进入老年代,而不会管这些对象究竟经过了多少次年轻代的回收。而其它没有拷贝的EdenFrom区域的对象,就不再是存活的对象了。

在一次年轻代收集完毕之后,不管是Eden还是survivor的From区域,就都是空的了,仅仅有survivor的To区域还有存活的对象。这个时候FromTo两者的职责会调换过来,參考下图:

串行收集——老年代

老年代使用串行收集器收集的算法是mark-sweep-compact收集算法,在mark阶段,收集器识别出全部的存活的对象。而在sweep阶段,会清除垃圾。

收集器会运行滑动压缩,将存活的对象依次向老年代空间的起始位置滑动(永久代也一样)。而在老年代的末尾处留出较大的连续空间。当回收完毕以后。老年代仍然支持bump-the-pointer技术来实现高速分配。參考下图:

何时使用串行收集器

串行收集器一般来说仅仅有运行在Client端的应用,而且这些应用对于应用暂停时长没有太多的需求的情况下会使用。以今天的设备来说。串行收集器能够在不到半秒的时间内,收集64M的堆空间。

J2SE5.0的公布时间为2005年的6月。以上測试结果以当时的硬件性能为准。

串行收集器的选择

在J2SE 5.0中。在非server的JVM中,默认的收集器就是串行收集器。假设使用其它的JVM的话,能够通过例如以下參数来指定使用串行收集器:

-XX:+UseSerialGC

Java内存管理(一)的更多相关文章

  1. Java 内存管理

    java 内存管理机制 JAVA 内存管理总结 java 是如何管理内存的 Java 的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字 new 为 ...

  2. 简单的例子 关于Java内存管理的讲解

    我想做的是,逐行读取文件,然后用该行的电影名去获取电影信息.因为源文件较大,readlines()不能完全读取所有电影名,所以我们逐行读取. 就这段代码,我想要在位置二处使用base64,然后结果呢? ...

  3. java内存管理机制

    JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 ( ...

  4. Java内存管理的9个小技巧

    Java内存管理的9个小技巧很多人都说“Java完了,只等着衰亡吧!”,为什么呢?最简单的的例子就是Java做的系统时非常占内存!一听到这样的话,一定会有不少人站出来为Java辩护,并举出一堆的性能测 ...

  5. 关于Java内存管理的几个小技巧

    这里将介绍几则Java内存管理的小技巧,让你让你从Java入门开始告别陋习,为Java程序提速.有不少人都说"Java完了,只等着衰亡吧!",为什么呢?最简单的的例子就是Java做 ...

  6. java内存管理(堆、栈、方法区)

    java内存管理 简介 首先我们要了解我们为什么要学习java虚拟机的内存管理,不是java的gc垃圾回收机制都帮我们释放了内存了吗?但是在写程序的过程中却也往往因为不懂内存管理而造成了一些不容易察觉 ...

  7. Java内存管理及对Java对象管理

    Java内存管理及对Java对象管理 1Java内存管理 1.1Java中的堆和栈 通常来说,人们会将Java内存氛围栈内存(Stack)和堆内存(Heap). 栈内存用来保存基本类型的变量和对象的引 ...

  8. 揭开Java内存管理的面纱

    前言 相对于C.C++这些高性能语言,Java有着让此类程序员羡慕的功能:内存自动管理.似乎这样,Java程序员不用再关心内存,也不用去了解相关知识.但结果真的是这样吗?特别对于我们这种Android ...

  9. java基础(一):谈谈java内存管理与垃圾回收机制

    看了很多java内存管理的文章或者博客,写的要么笼统,要么划分的不正确,且很多文章都千篇一律.例如部分地方将jvm笼统的分为堆.栈.程序计数器,这么分太过于笼统,无法清晰的阐述java的内存管理模型: ...

  10. Java内存管理的进一步理解-模拟过程图解

    Java内存管理的进一步理解-模拟过程图解--转载 java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 /* 1:方法区      方法区存放装载的类数据信息包括:      ...

随机推荐

  1. jQuery选择器(可见性选择器)第五节

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...

  2. SQL Server 2008 R2 企业版 MSDN原版

    经网友建议,提供常用试验用资源.以下软件或系统仅为完成本博客内的各种实验而提供下载. 所有软件.系统均为该软件发布方提供的原版文件,未经任何修改.破解等操作.使用目的仅限于学习.测试及实验,符合国家相 ...

  3. 高阶函数,柯里化,sort排序

    高阶函数概念 first class object:     函数在python中时一等公民.     函数也是对象,可调用的对象.     函数可以作为普通变量,参数,返回值等等. 高阶函数:    ...

  4. javascript 中遍历数组的简单方法

    在Javascript中有自带方便遍历数组的方法(此方法非彼方法不要误会哦): 1 .利用for( index in array ){}; 2.利用 array.forEach( function(e ...

  5. GPIO复位功能说明

    根据STM32L0x3参考手册RM0367,每个General-Purpose IO口,有以下寄存器: 1.4个32位的控制寄存器,分别为GPIOx_MODER,GPIOx_OTYPER,GPIOx_ ...

  6. JS中!=、==、!==、===的用法和区别

    1.对于string,number等基础类型,==和===是有区别的 1)不同类型间比较,==之比较"转化成同一类型后的值"看"值"是否相等,===如果类型不同 ...

  7. Nodejs前端服务器压缩图片

    Nodejs作为前端服务器,自然能承担处理图片的能力, 使用GM for nodejs 作为图片处理器,调用ImageMagick处理图片 使用ImageMagick var imageMagick ...

  8. ssh相关原理学习与常见错误总结

    欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...

  9. JAVA构造函数简析

    构造函数是java新建对象的一种手段 构造函数可以重载 如果一个类中有多个域,那么就可能需要多个构造函数.这时候,使用重载就可以了 构造函数中this和super的使用 this:(1)this用于本 ...

  10. CSS样式命名整理(非原创)

    非原创,具体出自哪里忘了,如果侵害您的利益,请联系我. CSS样式命名整理 页面结构 容器: container/wrap 整体宽度:wrapper 页头:header 内容:content 页面主体 ...