前言:Java与C++之间有一堵高墙,主要是有内存动态分配和垃圾收集技术组成的。墙外的人想要进来,墙内的人想要出去。

一、概述

  每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的。内存的分配和回收都具有确定性。

二、对象已死?

  垃圾收集器在对堆进行回收之前,不能确定哪些“对象”活着,哪些“对象”死去。

  1、引用计数算法

    在对象中,添加一个引用计数器,当有一个地方引用它时,计数器的值加一;引用失效的时候,计数器的值减一;任何时刻,计数器为零的对象不能被再次使用。这样就存在一个问题,如果两个对象之间相互引用,是否永远不能够被回收。代码如下所示:

package com.example.dayevery;
public class ReferenceCountingGC{
public Object instance = null;
private static final int _1MB = 1024*1024;
// 测试 是否被回收
private byte[] bigSize = new byte[2 * _1MB];
public static void TestGc (){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 测试 objA和objB是否被系统回收
System.gc();
}
public static void main(String[] args) {
// 提交执行
TestGc();
System.out.println("1");
}
}

  通过结果发现,虚拟机最终并没有因为两个对象之间互相引用就放弃回收它们,也就是说Java虚拟机并不是通过引用技术算法来判断对象是否存活。

  2、可达性分析算法

    当前主流算法(Java.C#)的内存管理子系统,采用可达性分析算法来判断对象是否存活。

    算法思路:通过一系列称之为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称之为“引用链”,如果某一个对象和GC Roots集合之间没有任何的引用链相连,则证明此对象之间不能被再次使用。

    GC Roots:除了固定的集合之外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可能会有其他的“对象”临时的加入,从而构成了完整的GC Roots集合。

三、生存还是死亡?

  当在可达性分析算法中,被标记为“死亡”的时候,该对象并不是真正的“死亡”,除了这一次的标记之外,还有第二次自救,那就是采用finalize()方法,从而让自己跟某一个起始节点集合挂上联系。

  finalize()方法:能且只能执行一次,如果失败,那么就会被回收。这是一个被可以在Java语言中被遗忘的方法。

四、回收方法区

  方法区回收的垃圾主要有两部分:废弃的常量和不再使用的类型。其中废弃的常量,举个例子,一个字符串“java”进入常量池中,没有任何的地方引用这个字符串,这属于废弃的常量。

  不再使用的类型要满足以下几点。1、该类的所有实例被回收。2、该类的类加载器已经被全部回收。3、该类对应的java.lang.Class类对象没有在任何地方引用。

五、垃圾收集算法

  垃圾收集算法可以划分为“引用计数式垃圾收集”和“追踪式垃圾收集”。本文主要介绍追踪式垃圾收集。

  首先要介绍分代收集理论,它建立在两个分代假说之上:1、弱分代假说。2、强分代假说。此后,还延伸出来“跨代引用假说”:存在相互引用的两个对象应该倾向于同时生存或者同时消亡的。

  针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法:1、标记 - 复制算法。2、标记 - 消除算法。3、标记 - 整理算法。

  1、标记 - 消除算法

    标记 - 消除算法是最基础的算法,主要分为“标记”和“清除”两部分,标记过程就是对象是否属于垃圾的判定过程。

    算法流程:首先标记出所有需要回收的对象,标记完成之后,统一回收掉所有被标记的对象。同时也存在两个缺点:1、执行效率不太稳定。2、内存空间的碎片化问题。

  2、标记 - 复制算法

    为了解决标记 - 清除算法,面对可回收对象时执行效率低的问题,提出采用“半区复制”,将内存划分为大小相等的两块,每次只是使用其中的一块。当其中一块内存用完之后,就将还存活的对象复制到另一块的上面,然后把已经使用过的内存空间一次性的清理到。最明显的缺点:可用内存缩小为原来的一半。

    标记 - 复制算法中的“半区复制”在89年发生了变化,针对“新生代”朝生夕灭的特点,采用了一种更加优化的半区复制分代策略,称为Appel式回收:将新生代的内存分为一块较大(80%)的Eden空间和两块较小(10%)的Survivor空间。

    每次使用Eden空间和一块Survivor空间,在垃圾回收的时候,将仍然存活的对象直接复制未被使用的Survivor空间中,当所需空间不能满足存活对象要求,采用“逃生门”的安全设计,依赖其他内存区域(老年代)进行分配担保。PS:有点像是银行借贷这种。

  3、标记 - 整理算法

    算法过程:标记 - 整理算法和标记 - 清除算法的第一步是相同的,但是标记 - 整理算法不能够直接对可回收对象进行清理,而是让所有的存活对象都往内存空间的一端移动,直接清理掉边界以外的内存。

六、HotSpot虚拟机的算法细节实现

    见下一篇博客。

Java虚拟机(JVM):第三幕:自动内存管理 - 垃圾收集器与内存分配策略的更多相关文章

  1. 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略

    1.  前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...

  2. Java虚拟机的内存管理----垃圾收集器

    1.Serial收集器 优点,是简单而高效,单线程避免了线程交互的开销. 缺点,进行垃圾回收时需要Stop the world(暂停所有用户线程). 2.ParNew收集器 它是Serial收集器的多 ...

  3. JVM内存管理---垃圾收集器

    说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态 ...

  4. 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...

  5. 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略

    第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收.   3.2 对象已死吗 ...

  6. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  7. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

  8. java虚拟机 jvm 出入java栈 栈空间内存分配

    java栈空间是一块线程私有的内存空间,java堆和程序数据密切相关,那么java栈就是和线程执行密切相关.线程最基本的执行行为就是函数的调用.每次函数调用其实是通过java栈传递数据的. 数据结构中 ...

  9. 如何设置Java虚拟机JVM启动内存参数

    Tomcat默认的Java虚拟机JVM启动内存参数大约只有64MB或者128MB,非常小,远远没有利用现在服务器的强大内存,所以要设置Java虚拟机JVM启动内存参数.具体设置方法为: Tomcat修 ...

  10. 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.3.垃圾收集器与内存分配策略

    1.学习目的 当需要排查各种内存溢出. 内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节. Java内存运行时区域的各个部分,其中程序计数 ...

随机推荐

  1. ModifyAjaxResponse,修改ajax请求返回值,前后端调试之利器

    一.概要 先看图 京豆多的离谱,你的第一想法肯定是:按F12修改了网页元素 没那么简单,你看支持刷新的 肯定还是假的,通过 Fiddler 或 Wireshark 等抓包工具修改了响应包:或者干脆改了 ...

  2. 一行命令使用 Docker 编译 Latex 文件,简单优雅

    使用 Docker 编译 LaTeX 文章 LaTeX 是一种常用的排版系统,它可以帮助用户创建漂亮.专业的文档.但是,安装和配置 LaTeX 比较麻烦,特别是对于初学者而言. Docker 是一个开 ...

  3. Github入门教程(新版)

    GitHub 的介绍与使用 GitHub 注册一个账号 直接在首页注册即可啦 要注意的是 第一项 username 别人是可见的 后面修改也会比较麻烦,所以起个好名字很重要 个人主页介绍 刚注册好的页 ...

  4. 4.10 x64dbg 反汇编功能的封装

    LyScript 插件提供的反汇编系列函数虽然能够实现基本的反汇编功能,但在实际使用中,可能会遇到一些更为复杂的需求,此时就需要根据自身需要进行二次开发,以实现更加高级的功能.本章将继续深入探索反汇编 ...

  5. 包管理工具npm和Yarn的区别,我们该如何选择?

    好家伙,学习新工具    1.为什么我们需要包管理器? 关于npm我们已经知道了,这是我们项目的包管理器, 我们现在用的无比顺手的工具,都是在无数的竞争中杀出来的,他们淘汰了无数的产品   首先,倘若 ...

  6. 自定义gradle插件并且发布到本地仓库

    转载请标明出处,维权必究:http://77blogs.com/?p=189 一.在工程上新建一个Module. 二.删除其余不必要的文件,最终目录结构如下: 注意:由于我用的是kotlin语言,所以 ...

  7. javascript中的垃圾回收机制的一些知识记录

    调用栈中的数据是如何回收的 原始类型的数据会分配到栈中 引用类型的数据会被分配到堆中 在执行代码的过程中,如果遇到了一个函数,js引擎会创建该函数的执行上下文,并将该函数的上下文压入调用栈中,与此同时 ...

  8. jsp中的一些问题

    jsp里的${pageContext.request.contextPath} 在JSP中,${pageContext.request.contextPath}是一个EL表达式(Expression ...

  9. Go语言中指针详解

    指针在 Go 语言中是一个重要的特性,它允许你引用和操作变量的内存地址.下面是指针的主要作用和相关示例代码: 1. 引用传递 在 Go 中,所有的变量传递都是值传递,即复制变量的值.如果你想在一个函数 ...

  10. Linux 标准目录结构 FHS ——原文链接https://www.cnblogs.com/woider/p/6618295.html

    因为利用 Linux 来开发产品或 distribution 的团队实在太多了,如果每个人都用自己的想法来配置文件放置的目录,那么将可能造成很多管理上的困扰.所以,后来就有了 Filesystem H ...