导语
  在学习垃圾回收算法之前,首先需要了解什么是Heap、什么是Root、什么是Object、什么是Stack、什么是Pointer,这写概念都是什么,为什么要在垃圾回收算法中使用,使用这些东西有什么样的好处,为什么这样使用。带着这些问题来进入一个新的学习系列。

文章目录

  • 对象结构
    • 对象头
  • 指针
  • mutator
  • 活动对象/非活动对象
  • 分配
  • 分块
  • 总结

  首先我们知道Java是一个面向对象的编程语言,这里首先需要了解的就是对象是什么,对于对象的创建、使用、销毁又是如何实现的。了解对象之后,就知道GC操作就是围绕这些对象展开,而这些对象概括起来就是被应用程序所使用的数据集合。
  程序从开始运行就有很多的对象在内存空间中进行初始化操作,然后就是如何使用这些对象,在对象被应用程序使用完成之后,就进入到Java的垃圾回收机制,Java语言不需要使用者自己管理内存,通过自动内存管理的机制,在对象回收的过程中GC会根据这些对象的使用配置情况对对象进行移动或者销毁,所以在GC中就必须要知道这些对象都是什么。在GC中的对象主要包括两部分内容对象头header和field域。

对象结构

对象头

  作为一个对象来说首先,一个对象头主要包含以下的一些信息

  • 对象的大小
  • 对象的种类

  这里的对象头就是用来标识对象的大小和对象的种类,对象的大小就是用来管理对象在内存中存储边界,而对象的类型就是控制在应用程序中如何使用这个对象,所以说对象头,在整个的应用对象使用的过程中包括在后续的垃圾回收机制中都是非常重要的。
  由于看到在对象中有关于对象大小和种类等信息。然后在后续的GC过程中就可以根据GC算法的不同来存储不同的信息。尽可能的保证就是高效的数据组织形式,也就是是根据合适的算法安排一个合适的数据存储结构。例如在标记清除算法中,就在对象头部设置了一个flag标识位来对对象进行标记管理。

  在所有的垃圾回收算法中都需要使用的内容就是对象的大小和对象种类信息,所以说这些基础信息在对象创建的时候就会一直存在,而其他一些信息则是需要通过具体的回收算法来进行创建的。当然为什么会把这两个东西作为基础信息呢?首先先来看看一段c语言的代码

void* malloc (size_t size);int *i = malloc(type,size)

  从上面代码中,可以看到,在我们操作对象的时候如果不知道对象是什么类型、对象占用多大的内存空间就无法在堆内存中为对象开辟合适的大小空间,如果不能在堆内存中开辟合适的大小空间,那么对于后续的GC操作也是非常困难的。所以把对象大小和对象类型作为基础信息。

  在C语言中有一个概念叫做结构体,而对象中把对象可访问的部分称为"域",也就是说这个域里面才是真正存储使用的时候需要的信息。在C语言中使用结构体的时候都是以一个指针的形式存在,而Java语言中没有指针的概念,把指针称为是引用。在实际使用对象的时候就会将这个引用替换为具体的对象域值。从上面内容可以知道,在对象使用的过程中是没有办法直接去改变对象头信息,也就是说一个对象一旦被创建之后,头信息是无法直接改变的。所能变的就是域的信息。

  通过javap 命令反编译Class文件的时候会有一个单词Field,这个就表示域,也表示属性,域就是使用者可以使用对象的部分,如果将其看看做是一个结构体的话域就表示结构体中的一个成员。在使用对象的时候会响应的引用或者是替换对象的域值。从String类型的对象来看。其实域中的内容可以分为两种类型

  • 指针
  • 非指针

 &emsp对于指针来讲,它指向的内存中的一片内存区域。但是前面不是说Java是一个面向对象的编程语言么,在Java中虽然没有明确的使用指针这个概念,但是在语言的内部处理过程中是使用C语言进行处理。

  对于非指针,就可以看做是C语言中所提到的基本数据类型。

指针

  在GC操作的过程中,对象被保留或者被销毁,这其中起到关键作用的就是指针,也就是平时我们口中所说的引用。在C或者C++语言中被叫做指针,在GC中它是根据这个对象的引用来进行搜索其他对象。那么通过上面的概念可以知道,在域值中有指针也有非指针,那么对于这些非指针又如何处理呢?在C语言中使用动态分配内存的时候,都需要给这块内存进行一个初始化操作,有过C语言编程经验的就会知道,在C语言动态开辟内存空间过后,那块内存空间是有数据的,这是没有办法被清理掉的,所以在一般的情况下使用这块内存的时候它会被新的数据所覆盖,这个就是在C语言的使用中一定先要进行初始化操作,当然如果不初始化也可以,整个的使用过程中这些内存空间都会被新值所覆盖了,所以,也就是说这里对于非指针操作是不进行任何处理。

  既然有这样的操作,有人也许会问既然有这样的区别,那么在执行java程序的时候会不会判断指针和非指针呢?还是只是在GC操作的时候会判断。既然有指针的存在,那么这个指针描述的是对象的哪个部分呢?如果是首地址这相对比较简单,但是如果是其他地方的话就比较复杂了,在很多的情况下包括C语言中,都将指针默认指向对象的首地址。在GC中也是默认指向首地址。


  上面图中可以将B对象和C对象看作是A对象的子对象。对某个对象以及其子对象进行处理是GC操作的基本操作。可以通过递归或者遍历等操作获取到子对象的指针数组如下,通过get函数就可以将B和C两个对象依次的作为参数传入,而children()方法则是获取到A对象的子对象数组。

for(child:children(A)){get(*child)
}

mutator

  mutator 英文的直译是,突变,在GC操作的时候就是需要改变某些东西,通过上面的分析隐约的可以感觉到,这里唯一可以变化的就是参与GC操作的对象的引用关系变化。也就是说在应用程序中,真个的垃圾回收需要有一个改变的东西,这个东西就被称为是mutator。对于这个操作主要有以下的两个具体操作

  • 生成对象
  • 更新指针

  在mutator进行操作的时候,同时也会进行一些其他的处理,随着这些处理就会发生对学习啊ing引用关系的改变,而由于对象引用关系发生了改变,有些对象就会变成无用的对象,这个时候就需要被垃圾回收机制所回收,而这里负责整个的垃圾回收的机制就是GC操作。

  说到这里mutator到底是什么,它就是一个对象引用的搬用工,从创建到销毁。

  在C语言中我们知道通过malloc()函数动态创建的的内存大小是被直接创建在真实的物理内存上的,那么在Java中一个对象的创建操作,是被创建到JVM堆内存中的,也就是说堆就是一个动态的存放Java对象的内存空间。只不过这块内存空间被专门进行了设计。当mutator 收到申请需要有对象进行存放的时候,所需要的内存就是从堆内存中进行分配的。这里需要注意的一点是GC只管理已经被创建的对象,而mutator则是管理如何创建对象,如何更新对象指针。

  也就是说在mutator 开始之前,GC主要完成的操作是如何进行堆内存的分配,一旦mutator开始之后,就会按照mutator 要求的那样在堆空间中存放对象,等到堆空间被放满之后,GC就会进行清理,也就是说开始调整堆空间的的分配策略,如果不够的话就要扩展堆空间的大小,直到达到利用–mxm 参数所设置的最大值,如果超过这个值没有进行有效的策略就会出现内存溢出的情况。

  一般情况下在C语言中可以将内存看作为一块连续的空间。可以指定一块大小为HEAP_SIZE 的空间大小,这个就可以看做是堆,而要想使用它就需要一个指向这块空间的首地址的指针。

  从上图可以看到整个的三者之间的关系就如上图所展示的一样 heap_size = heap_start+HEAP_SIZE。

活动对象/非活动对象

  在很多场景中,将分配到内存空间中的所有对象中能通过mutator 引用的对象被称作是活动对象,也就是说在应用程序中有对应的地方在使用这个对象,同样的,如果被分配到内存中的对象不能被mutator 引用,那么这些对象就是在应用程序中已经不使用了,这样的情况被称为是非活动对象。而这些对象就应该是被垃圾回收机制所管理的对象。这里在之前的博客中有一个概念就是四种引用,强引用,弱引用,虚引用,软引用。这里的mutator不能被引用就是这四种引用类型都无法引用。而这种对象就要被垃圾回收机制所处理,因为这种状态下的对象,就算是mutator 机制想要去引用,也没有办法从现有的引用链中找到。

分配

  前面提到一个概念就是动态分配,在C语言中为了更好的使用内存,这里通过malloc()函数与free()函数进行内存的管理,而在Java中所谓的分配则是在内存空间(这里主要是指堆内存中)进行的对象的分配操作。当mutator 需要新的对象的时候,就会向分配管理器(allocator) 申请一个合适大小的空间,具体的伪代码在上面的内容中已经写过。分配器就需要在堆内存的可用空间中进行对象的分配。将最后的结果返回到mutator 中。

  当然这种操作也会遇到堆空间被占用满的时候,这个时候就算是执行了GC操作,也无法分配空间。那么就需要有两种操作

  • 1、扩展堆空间大小
  • 2、抛出异常

  根据实际的经验扩展堆空间大小是最常用的逻辑,在虚拟机参数中有一个 --Xms参数和–Xmx参数分别表示

   -Xms<size>        设置初始 Java 堆大小-Xmx<size>        设置最大 Java 堆大小

  可见这里采取的策略是继续扩大堆内存,一般这个最大内存为物理机实际内存,如果能消耗的把物理机内存都占满的话,那么这个应用程序一般都是有问题的!

分块

  在GC的操作中,分块(chunk),是为了高效利用对象而事先准备出来的一种内存空间。类似于JVM中Eden,From区,TO区等。这个与垃圾分代回收有关的内容。在初始状态下,将所有的对象都分配到Eden区,然后在这个对象变为垃圾对象,或者是出现回收操作之后,就进入到From区,To区,进一步的进行更高级的处理策略。整个的流程是

  Java编程语言是一个面向对象的编程语言,面向对象三大特点就是继承、封装、多态。我们知道在Java中所有的对象都有一个共同的父类Object,这里根root 就是指这样一个存在,它是所有对象的起点,也就是是通过mutator 的操作开始的地方

obj = new Object()obj.field = SubObject();

  首先要实现一个公共根,这个对象一定是个共享对象,然后通过对象属性将整个的调用链连接到一起。从上面的代码中可以看到在field属性被赋值为SubObject之后,其实这个引用链路就被建立起来。当获取活动对象的时候就从根开始,所有引用链路到达不了的对象,都是被称为非活动对象。

  GC会把通过上面直接或间接与根相连的对象看作是活动对象,而在整个的mutator操作的时候,而整个的调用过程都是通过栈(Stack)这种数据结构。也就是说它遵从的是先进后出,也就是通过深度优先遍历,获取到整个的调用链路。或者通过广度优先遍历获取同一节点所有的引用链。

总结

  上面介绍了关于GC算法算法的一些常见的概念。在后续的分享中也会继续使用

垃圾回收算法与实现系列-学习GC之前的准备工作相关推荐

  1. 垃圾回收算法与实现系列-GC 标记-清除算法

    导语   在GC 中最重要的算法就是GC标记-清除算法(Mark-Sweep GC).在很多的场景下都还是在使用这个算法来进行垃圾回收操作.就如如同它的名字一样先标记,然后清除.下面就来看看标记清除算 ...

  2. 垃圾回收算法与实现系列-String在虚拟机中的实现

    导语   String 字符串一直作为各种编程语言的核心内容存在.作为动态字符的一种是实现方案,应用很广泛.每一种计算机语言对于这种数据结构都进行了特殊的优化和实现.在Java中,String作为引用 ...

  3. 垃圾回收算法与实现系列-Java堆内存溢出原因

    导语   内存一直是所有开发人员探索的一片天地,再JVM中,内存往往会被分为几块,了解不同的内存区域对编写出优质的代码有很大的帮助.堆内存作为JVM中比较重要的区域,有很多值得我们探索的地方.下面就来 ...

  4. 垃圾回收算法与实现系列-JVM无锁实现

    导语   为了确保多线程场景下数据安全,使用锁机制一直是一种优秀的解决方案,但是再高并发场景下,对锁的竞争可能成为性能瓶颈.为此,有出现了一种新的解决方案,被称为是非阻塞同步的方案.这种实现方式不需要 ...

  5. 垃圾回收算法与实现系列-锁在应用层的优化思路

    导语   之前的分享中主要介绍了虚拟机内部的对锁机制的优化与具体实现,在实际的开发过程中,还可以通过在应用层的合理优化,达到保证性能的目的,那么下面就学习介绍一下在应用层中如何进行锁的优化. 文章目录 ...

  6. 垃圾回收算法与实现系列-Java的Class文件详解

    导语   对于JVM来说,Class文件作为虚拟机的一个重要接口.无论使用什么样的语言,进行软件开发,只要能将源码编译为Class文件,并放到正确的路径下,那么这种语言就可以被JVM所执行.可以说Cl ...

  7. 垃圾回收算法与实现系列-锁在Java虚拟机中的实现和优化

    导语   上篇分享中提到了对象头Mark Word 的基本概念之后,接下来就可以深入到虚拟机内部了.在多线程程序中,线程之间的竞争是不可避免的,并且这是一种多线程程序的常态.那么如何高效的处理多线程的 ...

  8. 垃圾回收算法与实现系列-线程安全与锁简介

    导语   锁是多线程软件开发的必要工具,它的基本作用是保护临界区资源不被多个线程同时访问进而受到破坏.如果由于多线程访问造成数据不一致,那么系统将会得到一个错误的结果.通过锁可以让多个线程排队一个一个 ...

  9. java虚拟机学习-JVM调优总结-新一代的垃圾回收算法(11)

    java虚拟机学习-深入理解JVM(1) java虚拟机学习-慢慢琢磨JVM(2) java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制 java虚拟机学习-JVM内存管理:深 ...

最新文章

  1. java中next的用法_关于java iterator的next()方法的用法
  2. 设计模式学习笔记清单
  3. 创建相似对象,就交给『工厂模式』吧
  4. Android OOM案例分析
  5. C++学习之路 | PTA乙级—— 1076 Wifi密码 (15 分)(精简)
  6. c语言输入字符时控制符%c前加空格的原因解释
  7. python f string slash_python-django中的APPEND_SLASH实现
  8. ubuntu的磁盘扩容
  9. 简书bug(已修复):简友圈含英文时误删空格
  10. 没有基础的人可以学python吗-今天就来告诉你,没有编程基础的人适不适合学python...
  11. Keras AlexNet 网络实现 Kaggle 猫狗大战
  12. MOOC|Coursera课程批量下载(保持资源原目录结构)
  13. android listview删除刷新,如何刷新Android ListView?
  14. 【三维点云滤波】对三维点云空间数据进行滤波的matlab仿真
  15. 2017计算机办公自动化试题,【2017年整理】计算机办公自动化试题.doc
  16. 高盛发布区块链报告:从理论到实践(中文版)三
  17. bilibili 弹幕协议分析,golang 还原代码
  18. UVC 摄像头驱动(二)描述符分析
  19. 上传本地图片到数据库
  20. 【Python】Decision on buying cars COROLLA or LEVIN(数据分析技术实现过程之·2 data_analysis①)

热门文章

  1. layui引入php项目,怎么将layui引入开发框架中
  2. vue中既可以选择又可以手动输入的文本框类型_在PPT中制作一个胖乎乎的可爱圆环图...
  3. 北京开源人linux运维实战
  4. linux命令-tar命令
  5. C#关于自带滚动条控件的滚动条跳动问题
  6. 车道线识别/Opencv/传统方法
  7. Java中的ASCII码与Unicode码
  8. git 分支的创建和切换
  9. Notes:DOM的事件模拟
  10. html 空链接 href=#与href=javascript:void(0)的区别