peteryuanpan / notebook

喜欢的,值得留念的,就记下来,总会有用的。
73 stars 43 forks source link

鲁班学院 第三期第八节 精讲垃圾回收算法(一) #115

Closed peteryuanpan closed 3 years ago

peteryuanpan commented 4 years ago

简介

本节课内容 1、垃圾判断算法 2、源码及讲解内存池 3、源码及讲解标记清除、标记整理、分代复制算法 4、三色标记、读写屏障、原始快照、增量更新 5、答疑环节

总结

peteryuanpan commented 4 years ago

录播笔记 00:06:00 - 00:41:00

"子牙" 是1个String,1个char new String("真帅");是2个String,2个char 那么合并起来是4个String,3个char(Ubuntu internal jdk8环境下) 至于为什么windows下JDK8跑出来是3个String,3个char,老师不知道,直接跳过了...

数组长度对应起来是一个字节码指令:arraylength 因此来看字节码解释器:https://github.com/peteryuanpan/openjdk-8u40-source-code-mirror/blob/master/hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp#L1794

      CASE(_arraylength):
      {
          // 从栈中获取到arrayOop
          arrayOop ary = (arrayOop) STACK_OBJECT(-1);
          // 检查oop不为Null
          CHECK_NULL(ary);
          // 获取arrayOop的length,并往栈中设置值
          SET_STACK_INT(ary->length(), -1);
          // 更新程序计数器
          UPDATE_PC_AND_CONTINUE(1);
      }

来到 https://github.com/peteryuanpan/openjdk-8u40-source-code-mirror/blob/master/hotspot/src/share/vm/oops/arrayOop.hpp#L87

  // Accessors for instance variable which is not a C++ declared nonstatic
  // field.
  int length() const {
    // 取对象的首地址 + 数组长度在内存中的偏移,转为int指针,取值
    return *(int*)(((intptr_t)this) + length_offset_in_bytes());
  }

这个内存中的偏移是如何计算的 来到上面一点

  // The _length field is not declared in C++.  It is allocated after the
  // declared nonstatic fields in arrayOopDesc if not compressed, otherwise
  // it occupies the second half of the _klass field in oopDesc.
 // 翻译:在C++中没有声明长度字段,如果不压缩,则在arrayOopDesc中声明的非静态字段
 // 之后分配,如果压缩的话,它将占用oopDesc中_klass字段的后半部分
  static int length_offset_in_bytes() {
     // UseCompressedOops压缩的是对象指针的长度(指针压缩)
     // UseCompressedClassPointers压缩的是Klass对象指针的长度
    return UseCompressedClassPointers ? klass_gap_offset_in_bytes() :
                               sizeof(arrayOopDesc);
  }

https://github.com/peteryuanpan/openjdk-8u40-source-code-mirror/blob/master/hotspot/src/share/vm/oops/oop.hpp#L59

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass; // 8B
    narrowKlass _compressed_klass; // 4B
  } _metadata;

1.union _metadata 是联合体,含义是 _klass 和 _compressed_klass 只能用一个,整个联合体占8B,它的含义是类型指针,在不压缩情况下占8B,压缩情况下占4B 2.length_offset_in_bytes函数的注释:在C++中没有声明长度字段,如果不压缩,则在arrayOopDesc中声明的非静态字段之后分配,如果压缩的话,它将占用oopDesc中_klass字段的后半部分。这句话的含义是,如果压缩了话,联合体8B中,前4B给类型指针用,后4B给数组长度用

通过这段代码分析下,其中数组长度为3 image 在开启指针压缩情况下,用HSDB分析,发现0x00000003f800016e,其中0x00000003就是数组长度的值,它占用了类型指针的一半内存 image 在关闭指正压缩情况下,用HSDB分析,发现0x00000003跑到了下面,而原来的位置是8B的类型指针 image

peteryuanpan commented 4 years ago

录播笔记 00:41:00 - last

include "memory_chunk.h"

include

include

using namespace std;

class MemoryPool {

/**
 * 所有需要释放内存的成员
 */

private: list<MemoryChunk *> m_chunks;

public: ~MemoryPool();

public: /**

public: /**

include

include

include

typedef void * pvoid; typedef unsigned char byte; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong;

typedef union { long l_dummy; double d_dummy; void * p_dummy; }Align;

define ALIGN_SIZE (sizeof(Align))

- memory_chunk.h
1、MemoryChunk 直接持有内存
2、8字节对齐,如果申请79B内存,会自动分配80B
3、将分配到的内存分块处理,块是MemoryCell
```cpp
#pragma once

#include "../common.h"
#include "memory_cell.h"
#include <list>

using namespace std;

class MemoryChunk {

private:
    /**
     * 创建Chunk的文件名
     */
    char *m_filename;

    /**
     * 创建Chunk的文件位置
     */
    uint m_line;

    /**
     * 该Chunk的内存大小
     */
    uint m_size;

    /**
     * 以多少字节对齐
     */
    uint m_align_size;

    /**
     * 该Chunk包含多少Cell
     */
    uint m_cell_num;

    /**
     * 被用了的Cell数量
     */
    uint m_used_cell_num;

    /**
     * 当前Cell的起始位置
     *      复制算法用
     */
    uint m_cell_start;

    /**
     * 需要释放内存的数据
     */
private:
    /**
     * 存储数据的地方
     */
    pvoid m_data;

    list<MemoryCell *> m_available_table;
    list<MemoryCell *> m_used_table;

    /**
     * 空闲的内存
     *      复制算法用
     */
    list<MemoryCell *> m_idle_table;

    /**
     * 整理内存时中转用,暂存打了标记的对象,对象移动后释放
     */
    list<MemoryCell *> m_transer_table;

private:
    MemoryCell *real_malloc(MemoryCell *available_cell, uint cell_num);

public:
    MemoryChunk(uint size, char *filename, uint line);

    ~MemoryChunk();

public:
    char *get_filename();
    uint get_line();
    uint get_size();
    pvoid get_data();
    uint get_align_size();
    uint get_cell_num();

    MemoryChunk *inc_used_cell_num(uint step);
    MemoryChunk *desc_used_cell_num(uint step);

    list<MemoryCell *> *get_available_table();
    list<MemoryCell *> *get_used_table();
    list<MemoryCell *> *get_transer_table();
    list<MemoryCell *> *get_idle_table();

    uint get_cell_start();
    MemoryChunk *get_cell_start(uint val);

    uint get_new_cell_start();
    uint get_old_cell_start();
    MemoryChunk *renew_cell_start();

    MemoryChunk *set_available_table(list<MemoryCell *> &table);

    MemoryChunk *set_used_cell_num(uint val);

public:
    MemoryCell *malloc(uint size);

    /* 标准-整理算法运行后分配内存 */
    MemoryCell *malloc_after_gc(MemoryCell *transer_cell);

    MemoryCell *malloc_after_mark_copy_gc(MemoryCell *used_cell);

    void free_available_table();
    void free_used_table();

public:
    void to_string();

    void print_available_table();
    void print_used_table();
    void print_transer_table();
    void print_idle_table();
    void print_all_table();
};

include "../common.h"

using namespace std;

class MemoryCell { private: uint m_start; uint m_end;

/**
 *  Cell的数量,每个Cell占8字节
 */
uint m_size;

bool m_mark;

/**
 *  是否是中转对象
 *
 *  在GC标记阶段会将原对象放入Chunk的transer_table中(因为之前malloc返回的指针指向的是这个对象,这个对象释放了旧指针就失效了)
 *  然后生成一个克隆对象放入Chunk的used_table中用于后续的内存释放与整理(如果是多线程,GC阶段需要STW,否则内存的数据会被其他线程覆盖掉)
 *  这个属性就是标识是否是这个克隆对象
 */
bool m_transer_object;

private: /**

public: MemoryCell(uint start, uint size); MemoryCell(MemoryCell &cell); ~MemoryCell();

public: uint get_start(); MemoryCell *set_start(uint val);

uint get_end();
MemoryCell *set_end(uint val);

uint get_size();
MemoryCell *set_size(uint val);

bool get_mark();
MemoryCell *set_mark(bool val);

pvoid get_belong_chunk();
MemoryCell *set_belong_chunk(pvoid chunk);

bool get_transer_object();
MemoryCell *set_transer_object(bool val);

/**
 * m_start已step为步长递增
 * @param step
 * @return
 */
MemoryCell *inc_start(uint step);
MemoryCell *desc_start(uint step);

MemoryCell *inc_end(uint step);
MemoryCell *desc_end(uint step);

MemoryCell *inc_size(uint step);
MemoryCell *desc_size(uint step);

public: void to_string(); void to_string(char *msg);

pvoid ptr();

};

- 操作系统也有内存模型
1、堆(OS堆)
2、栈
3、静态区域
4、代码区域 可读可写可执行
- memory_poll.cpp
1、new_chunk向操作系统申请内存
2、print_chunks调试用
3、free_chunks释放内存
```cpp
#include "../include/memory_pool.h"
#include "../include/memory_chunk.h"

MemoryPool::~MemoryPool()
{
    INFO_PRINT("[调用析构函数]%s\n", __func__);

    free_chunks();
}

//=====
MemoryChunk *MemoryPool::new_chunk(uint mem_size)
{
    MemoryChunk *mem_chunk = new MemoryChunk(mem_size, __FILE__, __LINE__);

    this->m_chunks.push_front(mem_chunk);

    return mem_chunk;
}

//=====
void MemoryPool::print_chunks()
{
    INFO_PRINT("[打印未释放的内存]开始\n");

    list<MemoryChunk *>::iterator iterator;
    for (iterator = m_chunks.begin(); iterator != m_chunks.end(); iterator++) {
        MemoryChunk *chunk = *iterator;

        INFO_PRINT("\t [未释放的内存]申请位置:( %s:%d ), 内存大小:%d 字节\n",
                chunk->get_filename(), chunk->get_line(), chunk->get_size());
    }

    INFO_PRINT("[打印未释放的内存]结束\n");
}

void MemoryPool::free_chunks()
{
    list<MemoryChunk *>::iterator iterator;
    for (iterator = m_chunks.begin(); iterator != m_chunks.end(); iterator++) {
        delete (*iterator);
    }

    //TODO 网上说clear时会调用每个元素的析构函数,这里测试发现并不会
    m_chunks.clear();
}

MemoryChunk::MemoryChunk(uint size, char filename, uint line): m_size(size), m_filename(filename), m_line(line) { m_align_size = ALIGN_SIZE; m_cell_num = ((size - 1) / m_align_size) + 1; m_size = m_cell_num m_align_size;

this->m_data = calloc(m_size, sizeof(byte));
if (NULL == this->m_data) {
    ERROR_PRINT("分配内存失败\n");

    exit(1);
}

switch (DEFAULT_GC_TYPE) {
    case GC_MARK_CLEAN:
    case GC_MARK_COLLOECT:
        m_used_cell_num = 0;
        m_available_table.push_front(new MemoryCell(0, m_cell_num));
        break;
     case GC_MARK_COPY:
        m_used_cell_num = 0;
        m_cell_start = 0;
        m_available_table.push_front(new MemoryCell(0, m_cell_num / 2));
        m_idel_table.push_front(new MemoryCell(m_cell_num / 2, m_cell_num / 2));
        break;
}

print_available_table();

print_idle_table();

}

MemoryChunk::~MemoryChunk() { PRINT("[调用析构函数%s]释放资源\n", func);

if (m_data) {
    PRINT("\t 释放资源, 申请内存位置( %s:%d ),内存大小:%d 字节\n", m_filename, m_line, m_size);

    free(m_data);
}

free_available_table();

free_used_table();

}

//===== pvoid MemoryChunk::real_malloc(MemoryCell cell, uint cell_num) { / 计算出内存地址 / pvoid ret = (pvoid)((ulong)get_data() + cell->get_start() get_align_size());

INFO_PRINT("[真正分配内存]Data起始地址=%X, cell_start=%d, ret=%X\n", get_data(), cell->get_start(), ret);

/**
 * 创建used cell并加入list
 */
m_used_table.push_front(new MemoryCell(cell->get_start(), cell_num));

/**
 * 处理Cell信息
 */
cell->inc_start(cell_num)->desc_size(cell_num);

/**
 * 更新used_cell_num
 */
inc_used_cell_num(cell_num);

/**
 * 如果Chunk用光了,就清空available_table
 */
if (m_cell_num == m_used_cell_num) {
    free_available_table();
}

return ret;

}

//===== pvoid MemoryChunk::get_data() { return this->m_data; }

char *MemoryChunk::get_filename() { return m_filename; }

uint MemoryChunk::get_line() { return m_line; }

uint MemoryChunk::get_size() { return m_size; }

uint MemoryChunk::get_align_size() { return m_align_size; }

MemoryChunk *MemoryChunk::inc_used_cell_num(uint step) { m_used_cell_num += step;

if (m_used_cell_num > m_cell_num) {
    ERROR_PRINT("cell of chunk size overflow\n");

    exit(1);
}

return this;

}

MemoryChunk *MemoryChunk::desc_used_cell_num(uint step) { m_used_cell_num -= step;

if (m_used_cell_num < 0) {
    ERROR_PRINT("cell of chunk size overflow\n");

    exit(1);
}

return this;

}

//===== pvoid MemoryChunk::malloc(uint size) { pvoid ret = NULL;

if (0 == size) {
    ERROR_PRINT("申请的内存大小不得等于0\n");

    exit(1);
}

uint cell_num = ((size - 1) / m_align_size) + 1;
if (cell_num > m_cell_num) {
    ERROR_PRINT("需要的内存(%d字节)超过最大可用内存(%d字节)\n", size, m_size);

    exit(1);
}

/**
 * 遍历available_table查找满足条件的MemoryCell
 */
list<MemoryCell *>::iterator available_iterator;
for (available_iterator = m_available_table.begin(); available_iterator != m_available_table.end(); available_iterator++) {
    MemoryCell *cell = *available_iterator;

    if (cell->get_size() >= cell_num) {
        cell->to_string("找到了满足条件的Cell");

        ret = real_malloc(cell, cell_num);
    }
}

if (NULL == ret) {
    ERROR_PRINT("没有满足条件的Chunk,无法分成内存,程序退出\n");

    exit(1);
}

print_all_table();

return ret;

}

void MemoryChunk::free_available_table() { PRINT("[释放available_table]开始\n");

list<MemoryCell *>::iterator tmp;
for (tmp = m_available_table.begin(); tmp != m_available_table.end(); tmp++) {
    delete (*tmp);
}

m_available_table.clear();

}

void MemoryChunk::free_used_table() { PRINT("[释放used_table]开始\n");

list<MemoryCell *>::iterator tmp;
for (tmp = m_used_table.begin(); tmp != m_used_table.end(); tmp++) {
    delete (*tmp);
}

m_available_table.clear();

}

//===== void MemoryChunk::to_string() { PRINT("占 %d 个Cell,占 %d 字节\n", m_cell_num, m_size); }

void MemoryChunk::print_available_table() { PRINT("[打印available_table]开始\n");

list<MemoryCell *>::iterator tmp;

for (tmp = m_available_table.begin(); tmp != m_available_table.end(); tmp++) {
    PRINT("\t cell_start=%d, cell_end=%d, cell_size=%d\n", (*tmp)->get_start(), (*tmp)->get_end(), (*tmp)->get_size());
}

PRINT("[打印available_table]结束\n");

}

void MemoryChunk::print_used_table() { PRINT("[打印used_table]开始\n");

list<MemoryCell *>::iterator tmp;

for (tmp = m_used_table.begin(); tmp != m_used_table.end(); tmp++) {
    PRINT("\t cell_start=%d, cell_end=%d, cell_size=%d\n", (*tmp)->get_start(), (*tmp)->get_end(), (*tmp)->get_size());
}

PRINT("[打印used_table]结束\n");

}

void MemoryChunk::print_all_table() { print_available_table();

print_used_table();

}

- 垃圾回收算法课上笔记

标记-清除算法 面向整个堆 会产生碎片 如果说你需要分配大对象,需要连续的空间 你的内存是碎片化的,分配不到内存 这个时候不是因为你没有了内存分不到,而是因为你的内存不是连续的 标记-整理算法 内存碎片合并算法(源码展示) 老年代基于这个算法实现的 能合并内存 耗CPU Eden区 对象通常在第一次gc就会被释放,生命周期很短 碎片很多 合并碎片的时候需要STW 暂停所有用户线程 分代+复制算法 JVM中8:1:1 这个算法存在的目的:解决标记-整理算法合并碎片消耗性能过高、gc停止用户线程过长的问题 将内存五五分,一半用(0-10,From),一半空闲(11-20,To) 发生gc的时候,切换角色 一半空闲(11-20,From) 一半用(0-10,To)


- 卡表和记忆集
这一块看一下《深入理解JAVA虚拟机》第三版