影子的知识库

影子的知识库

  • 知识库
  • GitHub

›JVM系列

JVM系列

  • JVM内存区域
  • 对象创建-布局-访问
  • 内存溢出实战
  • 内存区域回收
  • 四大引用
  • 垃圾回收算法
  • HotSpot回收算法细节

Java系列

  • java注解
  • springboot请求参数绑定
  • springboot请求参数校验框架
  • YAML语法
  • 动态代理
  • classpath和java命令
  • springboot-aop编程
  • springboot统一异常处理
  • springboot数据库和事务
  • springboot拦截器
  • springboot中的web配置
  • docker的简单开发
  • springboot自动配置
  • 数据库的隔离级别
  • springboot监控
  • java类加载
  • java-agent的相关内容
  • 类加载器详解
  • java的SecurityManager
  • maven学习

Node

    JS 基础

    • 语法基础和数据类型
    • 数据类型转换
    • 语句 表达式 运算符
    • 变量与对象
    • 函数
    • 数据处理
    • 常用 API
    • 重点知识

    ES6

    • 块级作用域
    • 字符串和正则表达式
    • 函数
    • 对象
    • Symbol
    • Set和Map
    • 迭代器和生成器
    • 类
    • 数组
    • Promise

    Node 基础

    • 模块系统
    • package.json
    • 内置对象
    • npm脚本的使用
    • Buffer
    • Stream
    • 事件循环机制
    • 示例代码

    stream系列

    • 流的缓冲
    • 可读流
    • 可写流
    • 双工流和转换流
    • 自定义流

后期计划

  • 学习计划
  • 专题研究计划
Edit

本文内容

为什么栈上没有内存回收

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。栈帧随着方法的执行和返回而进入和退出虚拟机栈,所以对于这些内存来说,垃圾收集器不需要去回收他们。随着线程的执行他们都是有条不紊的在使用和回收内存,方法结束了栈帧就自动被回收了,线程结束了虚拟机栈、程序计数器、本地方法栈也就自动被回收了,全程都不需要垃圾收集器来掺和。

堆和方法区需要内存回收

Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理,本文后续讨论中的“内存”分配与回收也仅仅特指这一部分内存。

引用计数法(并未使用)

原理很简单,就是对象会给自身记录一个引用次数,每有一个指针指向它,引用次数加1,如果引用次数是0,就将对象回收。

但是有个致命的缺点,无法处理循环引用的情况。所以主流的 Java 虚拟机都没有使用这种方法

可达性分析算法

现在主流的回收算法,都是通过可达性分析来进行的。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

说白了就是:有一些引用作为根节点,这些根节点一级一级的往下引用,能够引用到的就是还活着的对象,引用不到的就是需要回收的对象

所谓一级一级引用,它的意思是:一个引用,引用它的对象,然后引用这个对象里面的属性对象,属性对象再引用自己的属性对象

如图

image-20200116212238040

对象object 5、object 6、object 7虽然互有关联,但是它们到GC Roots是不可达的,因此它们将会被判定为可回收的对象。

GC Roots 走下来的时候,找不到它们,所以它们属于需要回收的对象

GC Roots 包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

    这里指的是所有的栈帧,而不是最顶层正在执行的栈帧。只有栈帧滚蛋了,栈帧里引用的对象才能被回收

  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

    顺带一提,方法区里面存储的静态变量,存的是它的引用值,引用指向的对象依然还是在堆中的

  • 在方法区中常量引用的对象,也就是类中使用 static final 定义的静态常量(没毛病,跟着类被加载的时候放进方法区)

  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。(实际来说跟虚拟机栈是一个意思)

  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

    这些引用都是 JVM 自己单独保留的,对于我们来说基本没有什么讨论的必要。

    这一块儿的意思应该是:JVM(C++代码)中保留了堆里的一个内存地址(C++ 里有个指针变量指向了堆的某个内存地址),而这个内存地址处存着一个 JAVA 对象

  • 所有被同步锁(synchronized关键字)持有的对象。

  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

回收前的标记 finalize

注意:finalize 方法不推荐使用,以下只是解释其原理,了解即可,最好不要自己去使用

finalize 方法可以使得对象进行自救,如果对象已经是不可达的了,那么虚拟机在以下情况下会执行它的 finalize 方法:

  • 对象重写了自己的 finalize 方法
  • 对象的 finalize 方法之前没有被虚拟机调用过

也就是说,finalize 方法只能调用一次,对象只能自救一次

虚拟机会将对象放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。

注意:虚拟机仅仅是触发执行对象的 finalize 方法,但并不保证会将其执行完毕,有可能执行了一半提前就不执行了。

因为如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,将很可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃

如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量。那么对象就自救成功了,逃过了本轮的回收。

但是下一轮就没这么幸运了,finalize 方法最多只会被 JVM 执行一次

回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

判定一个类型是否属于“不再被使用的类”的条件就十分苛刻。需要同时满足下面三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。

相关的 VM 参数

VM 参数定义
-Xnoclassgc不进行类型的回收
-verbose:class跟踪类的加载和卸载信息 (Product版JVM可用)
-XX:+TraceClassLoading跟踪类的加载信息 (Product版JVM可用)
-XX:+TraceClassUnloading跟踪类的卸载信息 (FastDebug版JVM可用)

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力

Last updated on 11/8/2020
← 内存溢出实战四大引用 →
  • 为什么栈上没有内存回收
  • 堆和方法区需要内存回收
  • 引用计数法(并未使用)
  • 可达性分析算法
  • 回收前的标记 finalize
  • 回收方法区
影子的知识库
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHub
Copyright © 2020 Cen ZhiPeng