目录
3.1.3 为什么主流的JAVA虚拟机里面都没有选用这种算法?
3.3.1 垃圾收集算法-标记清除法(Mark-Sweep)图示
3.4.1 标记-压缩(Mark-Compact) 原理图示
5.2 要进行垃圾回收,如何判断一个对象是否可以被回收,确定是垃圾
7.2.1 查看初始默认: -XX:+PrintFlagsInitial
7.2.2 查看修改更新: -XX:+PrintFlagsFinal
7.2.3 PrintFlagFinal举例,运行JAVA命令的同时打印参数
7.2.4 XX:+PrintCommandLineFlags
8.2.4 -Xmn 设置新生区,年轻代的大小,一般默认就行
8.2.5 -XX:MetaspaceSize 设置元空间的大小
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 吞吐量 设置垃圾最大年龄
1 JVM内存结构
1.1 JVM体系概述
1.2 四种类装载器
- 启动类(根)加载器 bootstrap
- 拓展类加载器
- 应用加载器
- 自定义:继承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 对象?
- 虚拟机栈(帧栈中的局部变量区,也叫做局部变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中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 方案一
- jps -l
- jinfo -flag 具体参数 java进程编号
- 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