Java内存管理及垃圾回收总结

概述

Java和C++的一个很重要的差别在于对内存的管理。Java的自己主动内存管理及垃圾回收技术使得Java程序猿不须要释放废弃对象的内存。从而简化了编程的过程。同一时候也避免了因程序猿的疏漏而导致的内存泄露问题。

内存管理和垃圾回收是JVM很重要的一个部分。深入理解Java的内存管理和垃圾回收机制是避免及修复Java相关异常(OutOfMemoryError, StackOverflowError),理解Java对象创建过程,有效利用内存。构建高性能Java应用的前提。本文将先后介绍Java执行时内存区域,垃圾回收,对象创建过程。

Java执行时内存区域

Java执行时内存区域如图2-1所看到的,内存区域逻辑上被划分为:程序计数器,栈,本地方法栈,堆,方法区。

当中程序计数器,栈,本地方法栈都是线程私有的。堆和方法区被全部线程共享。

程序计数器用于指示当前线程运行的字节码的行号;栈用于描写叙述Java方法运行的内存模型。每当进入一个新的方法。JVM都会在栈中创建一个栈帧(存放本地变量,參数。返回地址,操作数栈)。本地方法栈是本地方法运行的内存模型。HotSpot虚拟机将栈和本地方法栈合二为一。

在提及到栈的时候。我们须要涉及两个异常:StackOverflowError和OutOfMemoryError,这两个异常的差别在于:当线程请求的栈深度超过虚拟机同意的栈深度时,会抛出StackOverflowError;当虚拟机无法为线程扩展栈分配足够的空间时,会抛出OutOfMemoryError。

设置栈深度的演示样例:-Xss128k    ==>>   设置栈的深度为128KB

图2-1

以下我们来看一下还有一块很重要的区域:堆。Java堆是用于存放Java对象实例的主要区域。通过new。clone。反序列化创建的对象都存放在堆中,为什么Java要把对象存放在堆中。而不是栈中呢?

C++因为没有垃圾回收机制,所以当定义一个变量时。其内存是在栈中分配的,仅仅有通过new显式的创建一个对象时,对象才会从堆中分配内存,而且此时须要通过delete显式的释放对象占用的内存,否则会造成内存泄露。Java中除了基本类型变量(boolean, byte, char, short, int, long, float, double),其它类型的变量基本都是通过new来创建,所以其内存都是从堆中分配,当对象废弃时。垃圾收集器会自己主动回收这部分内存。

因为堆是各个线程共享的内存区域,所以把对象存放在堆中有利于线程之间的通信(共享内存)。正如之前我们在描写叙述栈时所示,JVM会为每一个方法创建一个栈帧,所以假设对象存放在栈中,方法调用的參数将须要从调用方法的栈帧复制到被调用方法的栈帧,假设对象存放在堆中,仅仅须要拷贝指针或引用(此时。两个方法将指向同一个对象)。所以我们能够觉得Java之所以把对象存放在堆中。其一是Java具有很优秀的垃圾回收机制,其二把对象存放在堆中有利于线程之间共享数据及通信,其三是能够降低不必要的对象拷贝。提升方法调用的效率,同一时候也节约了内存。

由于不同的Java对象生命周期可能不同,所以基于Java对象不同的生命周期,堆被分成了两个不同的区域:新生代和老年代,新生代中对象的生命周期短,存活率低,老年代中对象的生命周期长。存活率高。基于不同的存活率。这两个区域的垃圾收集也採用了不同的算法。新生代一般採用复制算法,老年代一般採用标记-删除或标记-整理算法。

复制算法就是将存活下来的对象从一个区域拷贝到还有一个区域,标记删除和标记整理就是将须要回收的对象标记出来。然后清除掉,标记整理算法还会对内存进行整理。这样能够避免内存碎片。

将Java堆分成两个不同的年代并採用不同回收算法的垃圾收集方式被称为分代收集。下一节将具体介绍垃圾收集的机制以及经常使用垃圾收集器。

介绍完Java堆之后,我们来看一下方法区。方法区是用于存放类信息、常量(final, static final)、静态变量(static)、即时编译器编译后的代码的地方。HotSpot虚拟机把这部分区域称为永久代。由于HotSpot虚拟机把分代收集扩展到了方法区,或者我们能够说HotSpot虚拟机通过永久代来实现方法区;同一时候提供了參数-XX:MaxPermSize来限制方法区的最大内存。但事实上这并非一个非常好的选择,当载入的类比較多或者常量池比較大时。非常easy导致内存溢出。眼下HotSpot官方团队已经在逐步採用本地内存实现方法区,JDK1.7已经将常量池移出永久代。当该区域的内存无法满足要求时,也会导致内存溢出。

垃圾回收

依据前面对执行时内存区域的描写叙述,我们知道垃圾回收主要集中在堆和方法区。方法区能够选择性实现垃圾回收。该区域的垃圾回收主要集中在回收废弃常量和类型卸载。前面我们已经提及了复制算法和标记整理算法,那在此之前我们怎样知道哪些对象时废弃的,哪些对象时不能回收的呢?对于具备垃圾回收功能的语言。一般採用两种算法确定废弃对象:引用计数法(Python)和可达性分析算法(也被称为根搜索算法,C#,Lisp),Java採用可达性分析算法。引用计数法通过跟踪对象的引用计数器来确定对象是否被废弃,当一个新的引用指向该对象时。引用计数加1,当一个引用不在指向该对象时。引用计数减1,当引用计数为0时。对象被废弃。该算法在遇到堆中两个对象循环引用时(即对象A中有一个字段指向对象B。对象B中有一个字段指向A),会导致内存泄露,即这部分内存永远不会被回收。由于这两个对象的引用计数永远不为0。该算法出现故障的原因在于没有区分指向对象的引用ref的来源,假如ref位于栈或者方法区中,说明该对象没有废弃;但假如ref位于堆中。则不能确定,此时我们能够继续推断指向ref所在对象的引用ref2所在的内存区域。通过这样的方法不断回溯,假设终于能够到达栈,本地方法栈或者方法区中,则说明引用链中的对象都是没有废弃的,否则都是废弃的。这就是可达性分析算法。

Java除了通过可达性分析算法推断哪些对象须要回收之外。还提供了不同的引用级别用于实现更加灵活的垃圾回收。Java一共提供了四种引用级别:强引用,软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。强引用就是我们一般的引用方式。软引用指向对象在系统将要发生内存溢出时会被回收(能够用于实现缓存),弱引用指向的对象会在下一次垃圾回收时被回收,虚引用指向的对象仅仅是会在垃圾回收时收到一个系统通知,对象的生命周期全然不会受虚引用的影响。

以下我们来看一下复制算法(Copy)和标记-整理(Mark-Compact)算法的详细实现,事实上这两个算法都是从标记-清除(Mark-Sweep)算法改进而来的,标记清除算法会遇到两个问题:第一个问题是效率问题,当对象存活率非常低时,事实上把存活对象找出来并整理到一个区域,效率会更高,这就是复制算法;第二个问题是当存活率比較高时。会出现内存碎片问题。所以出现了标记-整理算法。

Java堆採用了新生代为复制算法,老年代为标记-整理或标记-清除算法的分代收集机制。

图2-2        标记 - 清除算法

图 2-3       复制算法

图2-3   标记 - 整理算法

复制算法将新生代分成Eden,From Survivor,To Survivor三块区域。每次垃圾收集Eden,From Survivor中存活的对象都会被拷贝到To Survivor中。

记住,这三个区域的划分仅仅是逻辑上的,和物理划分无关。默认Eden和Survivor的大小比例为8 : 1,比例划分这么大是为了提高内存的利用率,在这样的比例下可利用的内存事实上仅仅有90%;看到这里,我想非常多人可能会问。假如10%的空间不够存放生存下来的对象怎么办?JVM提供了一种被称为分配担保(Handle
Promotion)的机制,由老年代为To Survivor空间提供担保,假如To Survivor没有足够的空间存放生存下来的对象,这些对象直接存放到老年代,假如老年代还不够存放,就会抛出OutOfMemoryError异常。

分析了垃圾收集算法的思想之后。我们来了解一下经常使用的垃圾收集器,新生代的垃圾收集器包含 Serial,ParNew,Parallel Scavenge,老年代的垃圾收集器包含Serial Old。CMS,ParOld。

这些垃圾收集器的一个差别是单线程还是多线程。当中Serial,Serial Old是单线程的。其余是多线程的。第二个差别是垃圾收集线程和用户线程能否够并发运行。CMS收集器能够分成初始标记,并发标记。又一次标记。并发回收等过程,当中并发标记和并发回收能够与用户线程并发运行。所以它也是这些垃圾收集器中唯一真正意义上的并发收集器;Parallel
Scavenge与ParOld以提高吞吐量为目的。其它收集器以减小停顿时间(Stop The World)为目的。

创建对象及内存分配

前面提到,Java创建对象的方式包含new、clone、deserialization。在虚拟机内部,这三种创建对象的方式事实上是同样的。首先寻找或载入类信息。假设无法正常载入。则抛ClassNotFoundException,否则到java堆中分配内存,分配内存的方式依据内存是否规整(取决于垃圾回收算法。标记整理和复制算法的内存都是规整的,标记清除的内存不规整)有两种方式:指针碰撞和空暇列表。指针碰撞的方式中通过指针ptr将内存分成两个部分。ptr之前的部分都被使用。ptr之后的部分是空暇的。当对象须要的内存为size时。指针ptr
= ptr + size。空暇列表是通过将空暇的区域通过链表连接起来。对象须要内存则遍历链表,直到遇到一个具有足够空间的元素为止。内存分配完之后就将所分配的内存初始化为0,每一个对象都有一个对象头,这里保存着和对象相关的锁,对象的哈希码。对象的GC分代年龄,以及指向方法区中类型的相关引用。到此为止。对于虚拟机来说,已经成功创建了一个对象;但从Java程序来说。这才刚刚開始,接下来会运行<init>方法对全部字段进行初始化。

对于不同的对象。所分配的内存的区域是不同的。

一般来说。优先在Eden空间中分配内存;对于大对象。优先在老年代中分配内存(size大于PrenureSizeThreshold);当对象的年龄大于MaxTenuringThreshold时。对象也会被移动至老年代。假设Survivor空间中同样年龄的全部对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代。

时间: 06-23

Java内存管理及垃圾回收总结的相关文章

Java内存管理和垃圾回收

Java运行时内存区域 程序计数器,线程独占,当前线程所执行的字节码的行号指示器,每个线程需要记录下执行到哪儿了,下次调度的时候可以继续执行,这个区是唯一不会发生oom的 栈,线程独占,包含虚拟机栈或native method stack,用于存放局部变量的 堆,线程共享,用于分布对象实例的,后面说的内存管理和垃圾回收基本都是针对堆的 方法区,线程共享,用于存放被虚拟机加载的类,常量,静态变量; Java虚拟机规范,把方法区描述为堆的逻辑部分,所以也被称为“永久代”,在大量使用反射,动态代理,C

JAVA内存管理与垃圾回收

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 整个教程中已经不时的出现一些内存管理和垃圾回收的相关知识.这里进行一个小小的总结. Java是在JVM所虚拟出的内存环境中运行的.内存分为栈(stack)和堆(heap)两部分.我们将分别考察这两个区域. 栈 栈的基本概念参考纸上谈兵: 栈 (stack).许多语言利用栈数据结构来记录函数调用的次序和相关变量(参考Linux从程序到进程). 在Java中,JVM中的栈记录了线程的

JAVA内存管理和垃圾回收机制

JVM内存组成结构 JVM栈由堆.栈.本地方法栈.方法区等部分组成,结构图如下所示: 1)堆 所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制.堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图如下所示: 新生代.新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:Surv

Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确实很低,一方面,Java语言采用面向对象思想,这也决定了其必然是开发效率高,执行效率低.另一方面,Java语言对程序员做了一个美好的承诺:程序员无需去管理内存,因为JVM有垃圾回收(GC),会去自动进行垃圾回收. 其实不然: 1.垃圾回收并不会按照程序员的要求,随时进行GC. 2.垃圾回收并不会及时

java Vamei快速教程22 内存管理和垃圾回收

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 整个教程中已经不时的出现一些内存管理和垃圾回收的相关知识.这里进行一个小小的总结. Java是在JVM所虚拟出的内存环境中运行的.内存分为栈(stack)和堆(heap)两部分.我们将分别考察这两个区域. 栈 栈的基本概念参考纸上谈兵: 栈 (stack).许多语言利用栈数据结构来记录函数调用的次序和相关变量(参考Linux从程序到进程). 在Java中,JVM中的栈记录了线程的

JAVA当中内存管理与垃圾回收!

很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确实很低,一方面,Java语言采用面向对象思想,这也决定了其必然是开发效率高,执行效率低.另一方面,Java语言对程序员做了一个美好的承诺:程序员无需去管理内存,因为JVM有垃圾回收(GC),会去自动进行垃圾回收. 其实不然: 1.垃圾回收并不会按照程序员的要求,随时进行GC. 2.垃圾回收并不会及时

Java性能剖析]Sun JVM内存管理和垃圾回收

内存管理和垃圾回收是JVM非常关键的点,对Java性能的剖析而言,了解内存管理和垃圾回收的基本策略非常重要.本篇对Sun JVM 6.0的内存管理和垃圾回收做大概的描述. 1.内存管理      在程序运行过程当中,会创建大量的对象,这些对象,大部分是短周期的对象,小部分是长周期的对象,对于短周期的对象,需要频繁地进行垃圾回收以保证无用对象尽早被释放掉,对于长周期对象,则不需要频率垃圾回收以确保无谓地垃圾扫描检测.为解决这种矛盾,Sun JVM的内存管理采用分代的策略.      1)年轻代(Y

JVM内存管理和垃圾回收机制介绍

http://backend.blog.163.com/blog/static/20229412620128233285220/ 内存管理和垃圾回收机制是JVM最核心的两个组成部分,对其内部实现的掌握是Java开发人员开发出高质量的Java系统的必备条件.最近整理了一些关于JVM内存管理和垃圾回收方面的知识,这里梳理一下,分享给大家,希望能够对Java虚拟机有更深入的了解. 1. JVM内存管理 首先,JVM将内存组织为主内存和工作内存两个部分.主内存中主要包括本地方法区和堆.每个线程都有一个工

详解JVM内存管理与垃圾回收机制 (上)

Java应用程序是运行在JVM上的,得益于JVM的内存管理和垃圾收集机制,开发人员的效率得到了显著提升,也不容易出现内存溢出和泄漏问题.但正是因为开发人员把内存的控制权交给了JVM,一旦出现内存方面的问题,如果不了解JVM的工作原理,将很难排查错误.本文将从理论角度介绍虚拟机的内存管理和垃圾回收机制,算是入门级的文章,希望对大家的日常开发有所助益. 一.内存管理 也许大家都有过这样的经历,在启动时通过-Xmx或者-XX:MaxPermSize这样的参数来显式的设置应用的堆(Heap)和永久代(P