JVM GC垃圾回收

目录

1 JVM内存结构

1.1 JVM体系概述

1.2 四种类装载器

1.3 双亲委派

1.4 java类加载过程中的杀箱安全机制

1.5 Java8以后的JVM

2 GC作用域

3 常见的垃圾回收算法

3.1 引用计数

3.1.1 应用:

3.1.2 缺点:

3.1.3 为什么主流的JAVA虚拟机里面都没有选用这种算法?

3.2 复制(一般使用于年轻代)

3.2.1 MinorGC过程(复制->清空->互换)

3.2.2 特点

3.3 标记清除

3.3.1 垃圾收集算法-标记清除法(Mark-Sweep)图示

3.3.2 缺点

3.4 标记整理

3.4.1 标记-压缩(Mark-Compact)  原理图示

3.4.2 缺点

4 小总结

5 什么是GC Roots

5.1 什么是垃圾

5.2 要进行垃圾回收,如何判断一个对象是否可以被回收,确定是垃圾

5.2.1 引用计数法

5.2.2 枚举根节点做可达性分析(根搜索路径)

5.3 JAVA中哪些可以作为GC Roots 对象?

6 JVM参数配置

6.1 JVM的参数类型

6.1.1 标配参数

6.1.2 X参数

6.1.3 XX参数

7 查看JVM默认值(查看参数盘点家底)

7.1 方案一

7.2 方案二

7.2.1 查看初始默认: -XX:+PrintFlagsInitial

7.2.2 查看修改更新: -XX:+PrintFlagsFinal

7.2.3 PrintFlagFinal举例,运行JAVA命令的同时打印参数

7.2.4 XX:+PrintCommandLineFlags

8 JVM常用基本配置参数有哪些

8.1 -Xmx -Xms

8.1.1 DEMO

8.2 常用参数

8.2.1 -Xms 初始大小内存,默认物理内存的1/64

8.2.2 -Xmx 最大分配内存,默认为物理内存的1/4

8.2.3 -Xss 栈空间

8.2.4 -Xmn 设置新生区,年轻代的大小,一般默认就行

​8.2.5 -XX:MetaspaceSize 设置元空间的大小

8.2.6 典型设置案例

8.2.7 -XX:+PrintGCDetails 输出详细GC收集日志信息 (GC, FullGC)

8.2.8 -XX:SurvivorRatio 幸存区 设置新生代中Eden和S0/S1空间的比例

8.2.9 -XX:NewRatio 配置年轻代与老年代在堆结构的占比

8.2.10 -XX:MaxTenuringThreshold 吞吐量 设置垃圾最大年龄 ​

9 其他(有空再看)

10 参考文献


1 JVM内存结构

1.1 JVM体系概述

1.2 四种类装载器

  1. 启动类(根)加载器  bootstrap
  2. 拓展类加载器
  3. 应用加载器
  4. 自定义:继承class抽象类

1.3 双亲委派

1.4 java类加载过程中的杀箱安全机制

灰色:线程私有

亮色:线程公有

1.5 Java8以后的JVM

堆内存:伊甸园区,幸存者0区,幸存者1区,养老区

java7以前: 永久代

java8 : metaspace 云空间

2 GC作用域

3 常见的垃圾回收算法

3.1 引用计数

Java中,引用和对象是有管理的。如果操作对象 则必须用引用进行。

因此,可通过引用计数来判断一个对象是否可以回收。简单说,给对象加一个引用计数器。

每当有一个地方引用它,计数器值+1;

每当有一个引用失败时,计数器值-1;

任何时刻计数器值为0的对象就是不可能再被使用的,那么这个对象就是可回收对象。

即:有对象引用+1, 没对象引用-1,到0回收

3.1.1 应用:

微软的COM/ActionScrip3/Python...

3.1.2 缺点:

  • 每次对对象赋值时均要飞虎引用计数器,且计数器本身也有一定的消耗。
  • 较难处理循环引用

3.1.3 为什么主流的JAVA虚拟机里面都没有选用这种算法?

JVM的实现一般不采用这种方式,很难解决对象之间互相循环引用的问题

3.2 复制(一般使用于年轻代)

复制之后又交换,谁空谁是two

3.2.1 MinorGC过程(复制->清空->互换)

3.2.2 特点

没内存碎片,浪费空间,大对象费时

3.3 标记清除

算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象

即:先标记,后清除

3.3.1 垃圾收集算法-标记清除法(Mark-Sweep)图示

3.3.2 缺点

存在碎片

3.4 标记整理

标记清除整理

3.4.1 标记-压缩(Mark-Compact)  原理图示

3.4.2 缺点

移动对象需要成本

4 小总结

新生代使用 复制算法(费空间)

老年代使用 标记清除(有内存碎片),标记整理(耗时)


以上是回顾


5 什么是GC Roots

5.1 什么是垃圾

Person person = null; 没人用了,就会被回收

5.2 要进行垃圾回收,如何判断一个对象是否可以被回收,确定是垃圾

5.2.1 引用计数法

可查看前情回顾中 3.1 引用计数的内容

5.2.2 枚举根节点做可达性分析(根搜索路径)

为了解决引用计数器的循环引用问题,JAVA使用了可达性分析的方法:

跟踪(Tracing)

  • 复制(Copying)
  • 标记-清除(Mark-Sweep)
  • 标记-压缩(Mark-Compact)
  • Mark-Sweep(-Compact)

所谓“GC roots”,或者说tracing GC的 “根集合”就是一组必须活跃的引用

 

基本思路就是通过一系列名为“GC Roots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,说明此对象不可用。

即给定一个集合的引用作为根出发,通过引用关系便利对象图,能够被遍历到的(可到达的)对象就被判定为存活,没有被遍历到的就自然被判定为死亡。

案例:

右侧数据不是从GCROOT开始的,引用不可达,就要被回收。

5.3 JAVA中哪些可以作为GC Roots 对象?

  1. 虚拟机栈(帧栈中的局部变量区,也叫做局部变量表)中引用的对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

6 JVM参数配置

-Xms :初始堆空间

-Xmx :初始堆空间最大值

-Xss  :初始栈空间

.....

from...to...   :从初始值调到期望值

MateSpace :元空间

-Xms和-Xmx,最好调试成一样的,避免忽高忽低

6.1 JVM的参数类型

6.1.1 标配参数

JDK1.0 到 java12一直在

  • -version
  • -help
  • java -showversion

案例:

6.1.2 X参数

  • -Xint:解释执行
  • -Xcomp:第一次使用就编译成本地代码
  • -Xmixed:混合模式

混合模式:javac 先编译class文件 再java 执行

6.1.3 XX参数

6.1.3.1 Boolean类型

公式:

开启(+代表启动,激活true): -XX:+属性值

关闭(-代表本次运行,在这个参数的影响下,没有被激活使用): -XX:-属性值

XX第一个参数是boolean类型,开关

案例:

是否打印GC收集细节:

-XX:-PrintGCDetails

-XX:+PrintGCDetails

是否使用串行垃圾回收器

-XX:-UseSerialGC

-XX:+UseSerialGC

实际操作(如何查看一个正在运行中的java程序,某个JVM参数是否开启,具体值是多少?)

jdk安装的/bin目录中,jps 可查看后台进程, jinfo 查看正在运行的java信息

public class HelloGC {
    public static void main(String[] args) throws Exception{
        System.out.println("****HelloGC");

        Thread.sleep(Integer.MAX_VALUE);
    }
}

可以看出helloGC的进程是10800

查看进程ID命令:jps -l

打印GC收集细节参数:PrintGCDetails

查看某运行的java信息命令:jinfo -flag 进程ID 参数

-XX:固定写死的XX参数,固定写法

-PrintGCDetails中的 “-”符号代表没有加入过这个参数

增加参数 -XX:+PrintGCDetails,再次测试

可以看出 -XX:+PrintGCDetails, 代表添加了这个参数激活并使用了

6.1.3.2 KV设值类型

公式:

-XX:属性key=属性值value

案例:

-XX:MetaspaceSize=128m

-XX:MaxTenuringThreshold=128m

实际操作:

1.设置元空间

元空间数据查询,没有配置的时候,打印出默认值。21M多

可发现这个数据,没有配置也会出现

现在我们设置一下

2.young区 15年龄 升级到老年区,

6.1.3.3 jinfo案例,如何查看当前运行程序的配置

JVM调优需要了解一下

配置参数 测试:

jinfo -flags 16984 批量查询

Non-default VM flags 初始化加载默认的数据

Command line: 人工修改的

6.1.3.4 -Xmx和-Xmx参数怎么理解

就是简写(因为经常用), 还是XX参数

-Xms :等价于-XX:InitialHeapSize

-Xmx :等价于-XX:MaxHeapSize 

6.1.3.5 小总结

第一个蓝圈,boolean类, 第二个蓝圈,kv型

7 查看JVM默认值(查看参数盘点家底)

7.1 方案一

  1. jps -l
  2. jinfo -flag 具体参数 java进程编号
  3. jinfo -flags java进程编号

7.2 方案二

7.2.1 查看初始默认: -XX:+PrintFlagsInitial

公式:

java -XX:+PrintFlagsInitial -version

java -XX:+PrintFlagsInitial

案例:

如何查看JVM初始参数(所有)

java -XX:+PrintFlagsInitial

global flags java本身的 出厂默认, 没动过的

= 说明是JVM默认加载的

:=  说明JVM默认加载的时候修改的或人为修改过的参数

7.2.2 查看修改更新: -XX:+PrintFlagsFinal

公式:

java -XX:+PrintFlagsFinal -version 

7.2.3 PrintFlagFinal举例,运行JAVA命令的同时打印参数

执行命令: java -XX:+PrintFlagsFinal -version

用修改后的值测试

java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m T

验证:536870912/1024/1024 = 512

7.2.4 XX:+PrintCommandLineFlags

 -XX:+PrintCommandLineFlags -version

看出了默认的垃圾回收器: -XX:+UseParrallelGC

8 JVM常用基本配置参数有哪些

8.1 -Xmx -Xms

-Xmx -Xms 这些是必须的,基本会配置一样的

以 8处理器CPU,16G内存为例

一般初始的堆内存大小64分之一

一般初始的最大内存量四分之一

243.5M*64=15.584 大约16G

8.1.1 DEMO

 

8.2 常用参数

8.2.1 -Xms 初始大小内存,默认物理内存的1/64

等价于-XX:InitialHeapSize

8.2.2 -Xmx 最大分配内存,默认为物理内存的1/4

等价于-XX:MaxHeapSize 

8.2.3 -Xss 栈空间

栈空间的初始大小 ,私有的 管运行

设置单个线程栈的大小,一般默认为512k~1024k

等价于 -XX:ThreadStackSize

案例

0代表使用的是默认初始值。

尝试重新配置数据 

JAVA8 看官网:https://docs.oracle.com/javase/9/docs/index.html

这个值依赖于平台,windows 默认值依赖于虚拟内存是多大

linux 64位 一般是1024KB

8.2.4 -Xmn 设置新生区,年轻代的大小,一般默认就行

8.2.5 -XX:MetaspaceSize 设置元空间的大小

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

不过元空间与永久代之间最大的区别在于

  • 元空间并不在虚拟机中,而是使用本地内存。
  • 因此,默认情况下,元空间的大小仅受本地内存限制。

-Xms10m -Xmx10m +XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal

metaSpaceSize也不是越大越好

OOM里存在这种报错: java.lang.OutOfMemoryError:Metaspace

频繁反复的NEW对象,可能会撑爆元空间MetaspaceSize,所以会调节大一些。默认选择是20M左右

8.2.6 典型设置案例

java -XX:+PrintFlagsFinal -version

JVM调整

-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC

  • -Xms128m : 初始内存128
  • -Xmx4096m : 最大堆内存4G
  • -Xss1024k :初始栈大小1G
  • -XX:MetaspaceSize=512m:元空间512M
  • -XX:+PrintCommandLineFlags:后面的参数
  • -XX:+PrintGCDetails:打印GC回收细节
  • -XX:+UseSerialGC:串行垃圾回收期

实践:

运行demo:

public class HelloGC {
    public static void main(String[] args) throws Exception{
        System.out.println("****helloGC");
        Thread.sleep(Integer.MAX_VALUE);
    }
}

输出

-XX:InitialHeapSize=265971520   16G内存的64分支一

-XX:MaxHeapSize=4255544320 最大堆内存16G的4分之一

-XX:+PrintCommandLineFlags

-XX:+UseCompressedClassPointers

-XX:+UseCompressedOops

-XX:-UseLargePagesIndividualAllocation

-XX:+UseParallelGC 


****helloGC

以上打印出了JVM默认出厂的内容

-XX:+UseParallelGC 代表并行垃圾回收器

-XX:+UseSerialGC 代表串行垃圾回收器

使用之前的配置

-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC

打印结果: 

-XX:InitialHeapSize=134217728   //128M

-XX:MaxHeapSize=4294967296   //比以前大

-XX:MetaspaceSize=536870912  //512m

-XX:+PrintCommandLineFlags  

-XX:+PrintGCDetails -XX:ThreadStackSize=1024  //栈空间

-XX:+UseCompressedClassPointers

-XX:+UseCompressedOops

-XX:-UseLargePagesIndividualAllocation

-XX:+UseSerialGC //被改变

8.2.7 -XX:+PrintGCDetails 输出详细GC收集日志信息 (GC, FullGC)

测试 java.lang.OutOfMemoryError: Java heap space

配置 -Xmx10m -Xmx10m -XX:+PrintGCDetails,将内存调小

对比两张图

之前没有GC,现在有了GC

[GC (Allocation Failure) [PSYoungGen: 1791K->504K(2560K)] 1791K->680K(9728K), 0.0014547 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

新生1/3大小

养老2/3大小

->分割了前后数据

[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 248K->638K(5632K)] 736K->638K(8192K), [Metaspace: 3351K->3351K(1056768K)], 0.0061684 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

Full GC一般是养老区

8.2.8 -XX:SurvivorRatio 幸存区 设置新生代中Eden和S0/S1空间的比例

 

新生代一般是8:1:1

设置新生代中Eden和S0/S1空间的比例

默认:-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1

假如:-XX:SurvivorRatio=4,Eden:S0:S1=4:1:1

SurvivorRatio值就是设置Eden区的比例占多少,S0/S1相同

默认的情况

可以看出比例是8:1:1

修改配置

配置后运行:可以看出数据没变, 还是8:1:1

再次调试改成4

综上,该配置是 伊甸园区和幸存区的比例。

8.2.9 -XX:NewRatio 配置年轻代与老年代在堆结构的占比

一般用默认, 不需要调整。

默认:-XX:NewRatio=2 新生代占1,老年代2,年轻代占整个堆的1/3。

假如:-XX:NewRatio=4 新生代占1,老年代4,年轻代占整个堆的1/5。

NewRatio值就是设置老年代的占比,剩下的1给新生代。

一般15次才会去老年代,年轻代比例小了 GC就会频繁

默认:-Xmx10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2

 修改2, 打印结果和默认一致,说明是2

修改4:-Xmx10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=4

8.2.10 -XX:MaxTenuringThreshold 吞吐量 设置垃圾最大年龄 

先看一下默认数据, 默认是15

交换15次,才会从young到old区

秉着减少full GC的情况调试,增加交换要求,young数据停留时间久了,老年代数据减少了,full GC就少了,是否可以呢?下面测试调高

9 其他(有空再看)

10 参考文献

以上内容均来自于下方视频,博客内容仅作为个人学习笔记记录

【1】Java面试_高频重点面试题 (第一、二、三季)_ 面试 第1、2、3季_柴林燕_周阳_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值