jstat -gcutil 1000
jstat -gc 1000
jinfo
cpu使用率
top -p
尚硅谷_宋红康_JVM从入门到精通: https://www.bilibili.com/video/BV1PJ411n7xZ?from=search&seid=12508719405042682431
参考笔记连接:
https://blog.csdn.net/weixin_45759791/category_10123040.html
jvm(java虚拟机):一次编译到处运行,自动的管理内存,自动垃圾回收,降低内存泄漏的概率
建议书籍:
P2 02-如何看待Java上层技术与JVM
P3 03-为什么要学习JVM
P4 04-面向人群和课程特点
java 因为有jvm 成了跨平台的语言
jvm(java虚拟机): 一个抽象的计算机
除了java以外可以使用jvm(java虚拟机)
jvm : 跨语言的平台
P15 15-SUN Classic VM的介绍
P16 16-Exact VM的介绍
P18 18-JRockit VM的介绍
P19 19-IBM J9 VM的介绍
P20 20-KVM、CDC、CLDC的介绍
P21 21-Azul VM和BEA Liquid VM的介绍
P22 22-Apache Harmony的介绍
P23 23-Microsoft JVM和TaobaoJVM
P24 24-Dalvik VM及其他虚拟机的介绍
P25 25-Graal VM的介绍
P34 34-ClassLoader的常用方法及获取方法
P35 35-双亲委派机制的工作原理及演示
P36 36-双亲委派机制的优势
P37 37-沙箱安全机制
P38 38-类的主动使用与被动使用等
运行时数据区常见面试题
百度
三面:说一下JVM内存模型吧,有哪些区?分别干什么的?
蚂蚁金服:
Java8的内存分代改进
JVM内存分哪几个区,每个区的作用是什么?
一面:JVM内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个survivor区?
二面:Eden和survior的比例分配
小米:
jvm内存分区,为什么要有新生代和老年代
字节跳动:
二面:Java的内存分区
二面:讲讲vm运行时数据库区
什么时候对象会进入老年代?
京东:
JVM的内存结构,Eden和Survivor比例。
JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和survivor。
天猫:
一面:Jvm内存模型以及分区,需要详细到每个区放什么。
一面:JVM的内存模型,Java8做了什么改
拼多多:
JVM内存分哪几个区,每个区的作用是什么?
美团:
java内存分配
jvm的永久代中会发生垃圾回收吗?
一面:jvm内存分区,为什么要有新生代和老年代?
视频链接:https://www.bilibili.com/video/BV1PJ411n7xZ
三面:说一下JVM内存模型吧,有哪些区?分别干什么的?
蚂蚁金服:
Java8的内存分代改进
JVM内存分哪几个区,每个区的作用是什么?
一面:JVM内存分布/内存结构?栈和堆的区别?堆的结构?为什么两个survivor区?
二面:Eden和survior的比例分配
小米:
jvm内存分区,为什么要有新生代和老年代
字节跳动:
二面:Java的内存分区
二面:讲讲vm运行时数据库区
什么时候对象会进入老年代?
京东:
JVM的内存结构,Eden和Survivor比例。
JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和survivor。
天猫:
一面:Jvm内存模型以及分区,需要详细到每个区放什么。
一面:JVM的内存模型,Java8做了什么改
拼多多:
JVM内存分哪几个区,每个区的作用是什么?
美团:
java内存分配
jvm的永久代中会发生垃圾回收吗?
一面:jvm内存分区,为什么要有新生代和老年代?
PC寄存器用于存储下一条要执行代码的地址,及PC寄存器指向下一条要执行的代码
pc寄存器没有GC(垃圾回收),也不会出现内存溢出
P42 42-PC寄存器的使用举例
栈不存在GC(垃圾回收)
递归自己调自己的方法,在递归没有结束时一直不释放栈内存,如果递归很深,容易出现栈溢出/内存泄漏
P49 49-字节码中方法内部结构的剖析
P50 50-变量槽slot的理解与演示
P51 51-静态变量与局部变量的对比及小结
P52 52-操作数栈的特点
P53 53-涉及操作数栈的字节码指令执行分析
P54 54-栈顶缓存技术
为什么需要运行时常量池?
因为在不同的方法,都可能调用常量或者方法,所以只需要存储一份即可,节省了空间
常量池的作用:就是为了提供一些符号和常量,便于指令的识别
P56 56-方法的绑定机制:静态绑定与动态绑定
P58 58-invokedynamic指令的使用
如下图所示:如果类中重写了方法,那么调用的时候,就会直接在虚方法表中查找,否则将会直接连接到Object的方法中。
P60 60-方法返回地址的说明
P61 61-栈桢中的一些附加信息
栈的相关面试题
举例栈溢出的情况?(StackOverflowError)
通过 -Xss设置栈的大小, 递归深度很深,或者递归出现死循环
调整栈大小,就能保证不出现溢出么?
不能保证不溢出, 递归深度很深,或者递归出现死循环
分配的栈内存越大越好么?
不是,一定时间内降低了OOM(内存溢出)概率,但是会挤占其它的线程空间,因为整个空间是有限的。
垃圾回收是否涉及到虚拟机栈?
不会
运行时数据区,是否存在Error和GC?
/**
* 面试题
* 方法中定义局部变量是否线程安全?具体情况具体分析
* 何为线程安全?
* 如果只有一个线程才可以操作此数据,则必是线程安全的
* 如果有多个线程操作,则此数据是共享数据,如果不考虑共享机制,则为线程不安全
* @author: 陌溪
* @create: 2020-07-06-16:08
*/
public class StringBuilderTest {
// s1的声明方式是线程安全的,因为是内部产生,内部消亡的
public static void method01() {
// 线程内部创建的,属于局部变量
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
}
// 这个也是线程不安全的,因为有返回值,有可能被其它的程序所调用
public static StringBuilder method04() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder;
}
// stringBuilder 是线程不安全的,操作的是共享数据
public static void method02(StringBuilder stringBuilder) {
stringBuilder.append("a");
stringBuilder.append("b");
}
/**
* 同时并发的执行,会出现线程不安全的问题
*/
public static void method03() {
StringBuilder stringBuilder = new StringBuilder();
new Thread(() -> {
stringBuilder.append("a");
stringBuilder.append("b");
}, "t1").start();
method02(stringBuilder);
}
// StringBuilder是线程安全的,但是String也可能线程不安全的,以为返回的是String,可能被其他线程调用
public static String method05() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder.toString();
}
}
尚硅谷JVM从入门到精通宋红康版|第六章、本地方法接口
P65 65-JVM学习路线与内容回顾
尚硅谷JVM从入门到精通宋红康版|第八章、堆
堆的核心概念
堆针对一个JVM进程来说是唯一的,也就是一个进程只有一个JVM,但是进程包含多个线程,他们是共享同一堆空间的。
一个java 程序运行起来就是一个进程,一个进程对应一个jvm实例,一个jvm实例对应一个运行时数据区,一个运行时数据去对应一个堆和方法区
一个进程包的所有线程是共享同一堆空间和方法区的,每个线程有自己独立的栈、程序计数器、本地方法栈。
idea设置java程序运行时堆大小
运行HeaoDemo java程序
查看运行的堆空间使用情况
下图就是使用:Java VisualVM查看堆空间的内容,通过 jdk bin提供的插件
/**
* -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
* -X:是jvm运行参数
* ms:memory start
* -Xmx:用来设置堆空间(年轻代+老年代)的最大内存大小
*
* @author: 陌溪
* @create: 2020-07-06-20:44
*/
public class HeapSpaceInitial {
public static void main(String[] args) {
// 返回Java虚拟机中的堆内存总量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
// 返回Java虚拟机试图使用的最大堆内存
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("-Xms:" + initialMemory + "M");
System.out.println("-Xmx:" + maxMemory + "M");
}
}
方式一:
看某个进程内存分配信息:
jps -> staat -gc 进程id
25600+153600+409600=588800/1024=575
25600+25600+153600+409600=614400/1024=600
因为存储的时候s0c和s1c都能放,但有一个始终是空的,所以我们打印的是575(有一个没算),而我们查询内存分配总的是600
即打印gc细节
-XX:+PrintGCDetails
old区已满溢出
我们简单的写一个OOM例子
/**
* OOM测试
*
* @author: 陌溪
* @create: 2020-07-06-21:11
*/
public class OOMTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
while(true) {
list.add(999999999);
}
}
}
然后设置启动参数
设置堆内存:
-Xms10m -Xmx:10m
运行后,就出现OOM了,那么我们可以通过 VisualVM这个工具查看具体是什么参数造成的OOM
s0和s1谁为空时谁为to
伊甸园(Eden)(《圣经》中亚当和夏娃最初居住的地方)
伊甸园区(Eden)满时触发MinorGC
我们创建的对象,一般都是存放在Eden区的,当我们Eden区满了后,就会触发GC操作,一般被称为 YGC / Minor GC操作
当我们进行一次垃圾收集后,红色的将会被回收,而绿色的还会被占用着,存放在S0(Survivor From)区。同时我们给每个对象设置了一个年龄计数器,一次回收后就是1。
同时Eden区继续存放对象,当Eden区再次存满的时候,又会触发一个MinorGC操作,此时GC将会把 Eden和Survivor From中的对象 进行一次收集,把存活的对象放到 Survivor To区,同时让年龄 + 1
我们继续不断的进行对象生成 和 垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion晋升的操作,也就是将年轻代中的对象 晋升到 老年代中
思考:幸存区区满了后?
特别注意,在Eden区满了的时候,才会触发MinorGC,而幸存者区满了后,不会触发MinorGC操作
如果Survivor区满了后,将会触发一些特殊的规则,也就是可能直接晋升老年代
一般情况下大部分的对象在Eden区就被MinorGC回收了
/**
* 代码演示对象创建过程
*
* @author: 陌溪
* @create: 2020-07-07-9:16
*/
public class HeapInstanceTest {
byte [] buffer = new byte[new Random().nextInt(1024 * 200)];
public static void main(String[] args) throws InterruptedException {
ArrayList<HeapInstanceTest> list = new ArrayList<>();
while (true) {
list.add(new HeapInstanceTest());
Thread.sleep(10);
}
}
}
然后设置JVM参数
-Xms600m -Xmx600m
然后cmd输入下面命令,打开VisualVM图形化界面
jvisualvm
然后通过执行上面代码,通过VisualGC进行动态化查看
常用的调优工具
我们都知道,JVM的调优的一个环节,也就是垃圾收集,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW的问题
而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上,Minor GC会引发STW。
所有jvm调优主要避免GC,调优主要是针对Major GC 和 Full GC
STW: 在进行垃圾回收的时候要暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行,会造成一段时间用户线程不可用。
我们编写一个OOM的异常,因为我们在不断的创建字符串,是存放在元空间的
/**
* GC测试
*
* @author: 陌溪
* @create: 2020-07-07-10:01
*/
public class GCTest {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "mogu blog";
while(true) {
list.add(a);
a = a + a;
i++;
}
}catch (Exception e) {
e.getStackTrace();
}
}
}
设置JVM启动参数
-Xms10m -Xmx10m -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 2038K->500K(2560K)] 2038K->797K(9728K), 0.3532002 secs] [Times: user=0.01 sys=0.00, real=0.36 secs]
[GC (Allocation Failure) [PSYoungGen: 2108K->480K(2560K)] 2405K->1565K(9728K), 0.0014069 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2288K->0K(2560K)] [ParOldGen: 6845K->5281K(7168K)] 9133K->5281K(9728K), [Metaspace: 3482K->3482K(1056768K)], 0.0058675 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5281K->5281K(9728K), 0.0002857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 5281K->5263K(7168K)] 5281K->5263K(9728K), [Metaspace: 3482K->3482K(1056768K)], 0.0058564 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 60K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0f138,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 5263K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 73% used [0x00000000ff600000,0x00000000ffb23cf0,0x00000000ffd00000)
Metaspace used 3514K, capacity 4498K, committed 4864K, reserved 1056768K
class space used 388K, capacity 390K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.atguigu.java.chapter08.GCTest.main(GCTest.java:20)
问题:堆空间都是共享的么?
不一定,因为还有TLAB这个概念,在堆中划分出一块区域,为每个线程所独占
查看tlab是否开启
对于单个线程TLAB分配过程:
对象首先是通过TLAB开辟空间,如果不能放入,那么需要通过Eden来进行分配
jvm堆空间常用参数:
官网:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间:
逃逸分析:对象的使用作用域是否超出一个方法。
如果对象的使用作用域只在一个方法内,就可以把此对象分配到堆上。
完整的逃逸分析代码举例
/**
* 逃逸分析
* 如何快速的判断是否发生了逃逸分析,大家就看new的对象是否在方法外被调用。
* @author: 陌溪
* @create: 2020-07-07-20:05
*/
public class EscapeAnalysis {
public EscapeAnalysis obj;
/**
* 方法返回EscapeAnalysis对象,发生逃逸
* @return
*/
public EscapeAnalysis getInstance() {
return obj == null ? new EscapeAnalysis():obj;
}
/**
* 为成员属性赋值,发生逃逸
*/
public void setObj() {
this.obj = new EscapeAnalysis();
}
/**
* 对象的作用于仅在当前方法中有效,没有发生逃逸
*/
public void useEscapeAnalysis() {
EscapeAnalysis e = new EscapeAnalysis();
}
/**
* 引用成员变量的值,发生逃逸
*/
public void useEscapeAnalysis2() {
EscapeAnalysis e = getInstance();
// getInstance().XXX 发生逃逸
}
}
**栈上分配: **
举例
我们通过举例来说明 开启逃逸分析 和 未开启逃逸分析时候的情况
/**
* 栈上分配
* -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
* @author: 陌溪
* @create: 2020-07-07-20:23
*/
class User {
private String name;
private String age;
private String gender;
private String phone;
}
public class StackAllocation {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start) + " ms");
// 为了方便查看堆内存中对象个数,线程sleep
Thread.sleep(10000000);
}
private static void alloc() {
User user = new User();
}
}
设置JVM参数,表示未开启逃逸分析
-XX:-DoEscapeAnalysis 关闭逃逸分析
-Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
运行结果,同时还触发了GC操作
花费的时间为:664 ms
-XX:+DoEscapeAnalysis 开启逃逸分析
-Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
然后查看运行时间,我们能够发现花费的时间快速减少,同时不会发生GC操作
花费的时间为:5 ms
然后在看内存情况,我们发现只有很少的User对象,说明User发生了逃逸,因为他们存储在栈中,随着栈的销毁而消失
同步省略:如果一个对象被发现只有一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
public static void main(String args[]) {
alloc();
}
class Point {
private int x;
private int y;
}
private static void alloc() {
Point point = new Point(1,2);
System.out.println("point.x" + point.x + ";point.y" + point.y);
}
以上代码,经过标量替换后,就会变成
private static void alloc() {
int x = 1;
int y = 2;
System.out.println("point.x = " + x + "; point.y=" + y);
}
可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。
标量替换为栈上分配提供了很好的基础。
尚硅谷JVM从入门到精通宋红康版|第九章、方法区
方法区主要存放的是 类(Class)信息,而堆中主要存放的是 实例化的对象
栈、堆、方法区的交互关系
Person:存放在元空间,也可以说方法区
person:存放在Java栈的局部变量表中
new Person():存放在Java堆中
方法区主要存放的是 类(Class)信息,而堆中主要存放的是 实例化的对象
/**
* non-final的类变量
*
* @author: 陌溪
* @create: 2020-07-08-16:54
*/
public class MethodAreaTest {
public static void main(String[] args) {
Order order = new Order();
//Order order = null;
order.hello();
System.out.println(order.count);
}
}
class Order {
public static int count = 1;
public static final int number = 2;
public static void hello() {
System.out.println("hello!");
}
}
如上代码所示,即使我们把order设置为null,也不会出现空指针异常
P95 95-运行时常量池的理解
P96 96-图示举例方法区的使用
方法区的演进细节
首先明确:只有Hotspot才有永久代。BEA JRockit、IBMJ9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一
Hotspot中方法区的变化:
JDK7的时候
JDK8的时候,元空间大小只受物理内存影响
P98 98-StringTable为什么要调整位置
P99 99-如何证明静态变量存在哪
P100 100-方法区的垃圾回收行为
P101 101-运行时数据区的总结与常见大厂面试题说明
P102 102-对象实例化的几种方式
P103 103-字节码角度看对象的创建过程
P104 104-对象创建的六个步骤
P105 105-对象的内存布局
P106 106-对象访问定位
P107 107-直接内存的简单体验
P108 108-使用本地内存读写数据的测试
P109 109-直接内存的00M与内存大小的设置
P110 110-执行引擎的作用及工作过程概述
P111 111-Java程序的编译和解释运行的理解
P112 112-机器码_指令_汇编_高级语言理解与执行过程
P113 113-解释器的使用
P114 114-HotspotVM为何解释器与JIT编译器并存
P115 115-热点代码探测确定何时JIT
P116 116-Hotspot设置模式_C1与C2编译器
P117 117-Graal编译器与AOT编译器
P118 118-String的不可变性
P119 119-String底层Hashtable结构的说明
P120 120-String内存结构的分配位置
P121 121-两个案例熟悉String的基本操作
P122 122-字符串拼接操作的面试题讲解
P123 123-字符串变量拼接操作的底层原理
P124 124-拼接操作与append操作的效率对比
P125 125-intern()的理解
P126 126-new String()到底创建了几个对象
P127 127-关于intern()的面试难题
P128 128-面试的拓展问题
P129 129-intern()的课后练习1
P130 130-intern()的课后练习2
P131 131-intern()的空间效率测试
P132 132-StringTable的垃圾回收测试
P133 133-G1垃圾收集器的String去重操作
尚硅谷JVM从入门到精通宋红康版|第十四章、垃圾回收概述
垃圾回收大厂面试题
蚂蚁金服
你知道哪几种垃圾回收器,各自的优缺点,重点讲一下cms和G1?
JVM GC算法有哪些,目前的JDK版本采用什么回收算法?
G1回收器讲下回收过程GC是什么?为什么要有GC?
GC的两种判定方法?CMS收集器与G1收集器的特点
百度
说一下GC算法,分代回收说下
垃圾收集策略和算法
天猫
JVM GC原理,JVM怎么回收内存
CMS特点,垃圾回收算法有哪些?各自的优缺点,他们共同的缺点是什么?
滴滴
Java的垃圾回收器都有哪些,说下g1的应用场景,平时你是如何搭配使用垃圾回收器的
京东
你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,
包括原理,流程,优缺点。垃圾回收算法的实现原理
阿里
讲一讲垃圾回收算法。
什么情况下触发垃圾回收?
如何选择合适的垃圾收集算法?
JVM有哪三种垃圾回收器?
字节跳动
常见的垃圾回收器算法有哪些,各有什么优劣?
System.gc()和Runtime.gc()会做什么事情?
Java GC机制?GC Roots有哪些?
Java对象的回收方式,回收算法。
CMS和G1了解么,CMS解决什么问题,说一下回收的过程。
CMS回收停顿了几次,为什么要停顿两次?
Java垃圾回收机制 ,优点:
Java垃圾回收机制 ,担忧:
GC主要关注的区域
GC主要关注于 方法区 和堆中的垃圾收集
垃圾回收主要包含连个阶段
垃圾标记阶段:判断对象存活一般有两种方式:
循环引用
当p的指针断开的时候,内部的引用形成一个循环,这就是循环引用,从而造成内存泄漏
##P140 140-Java代码举例_Python的引用计数实施方案
概念
可达性分析算法:也可以称为 根搜索算法、追踪性垃圾收集
标记阶段:可达性分析算法(java使用)
由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态(面试题)。
代码演示
我们使用重写 finalize()方法,然后在方法的内部,重写将其存放到GC Roots中
/**
* 测试Object类中finalize()方法
* 对象复活场景
*
* @author: 陌溪
* @create: 2020-07-12-11:06
*/
public class CanReliveObj {
// 类变量,属于GC Roots的一部分
public static CanReliveObj canReliveObj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("调用当前类重写的finalize()方法");
canReliveObj = this;
}
public static void main(String[] args) throws InterruptedException {
canReliveObj = new CanReliveObj();
canReliveObj = null;
System.gc();
System.out.println("-----------------第一次gc操作------------");
// 因为Finalizer线程的优先级比较低,暂停2秒,以等待它
Thread.sleep(2000);
if (canReliveObj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
System.out.println("-----------------第二次gc操作------------");
canReliveObj = null;
System.gc();
// 下面代码和上面代码是一样的,但是 canReliveObj却自救失败了
Thread.sleep(2000);
if (canReliveObj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
}
}
最后运行结果
-----------------第一次gc操作------------
调用当前类重写的finalize()方法
obj is still alive
-----------------第二次gc操作------------
obj is dead
在进行第一次清除的时候,我们会执行finalize方法,然后 对象 进行了一次自救操作,但是因为finalize()方法只会被调用一次,因此第二次该对象将会被垃圾清除。
MAT是什么?
MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。用于查找内存泄漏以及查看内存消耗情况。
MAT是基于Eclipse开发的,是一款免费的性能分析工具。
大家可以在http://www.eclipse.org/mat/下载并使用MAT
JProfiler的GC Roots溯源
我们在实际的开发中,一般不会查找全部的GC Roots,可能只是查找某个对象的整个链路,或者称为GC Roots溯源,这个时候,我们就可以使用JProfiler
当我们程序出现OOM的时候,我们就需要进行排查,我们首先使用下面的例子进行说明
/**
* 内存溢出排查
* -Xms8m -Xmx8m -XX:HeapDumpOnOutOfMemoryError
* @author: 陌溪
* @create: 2020-07-12-14:56
*/
public class HeapOOM {
// 创建1M的文件
byte [] buffer = new byte[1 * 1024 * 1024];
public static void main(String[] args) {
ArrayList<HeapOOM> list = new ArrayList<>();
int count = 0;
try {
while (true) {
list.add(new HeapOOM());
count++;
}
} catch (Exception e) {
e.getStackTrace();
System.out.println("count:" + count);
}
}
}
上述代码就是不断的创建一个1M小字节数组,然后让内存溢出,我们需要限制一下内存大小,同时使用HeapDumpOnOutOfMemoryError将出错时候的dump文件输出
-Xms8m -Xmx8m -XX:HeapDumpOnOutOfMemoryError
我们将生成的dump文件打开,然后点击Biggest Objects就能够看到超大对象
然后我们通过线程,还能够定位到哪里出现OOM
/**
* System.gc()
*
* @author: 陌溪
* @create: 2020-07-12-19:07
*/
public class SystemGCTest {
public static void main(String[] args) {
new SystemGCTest();
// 提醒JVM进行垃圾回收
System.gc();
//System.runFinalization();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("SystemGCTest 执行了 finalize方法");
}
}
即如果调用 System.gc();不一定会触发finalize方法,打印SystemGCTest 执行了 finalize方法,而如果调用System.runFinalization()会强制调用,并打印
运行结果,但是不一定会触发销毁的方法,调用System.runFinalization()会强制调用 失去引用对象的finalize()
/**
* System.gc()
*
* @author: 陌溪
* @create: 2020-07-12-19:07
*/
public class SystemGCTest {
public static void main(String[] args) {
new SystemGCTest();
// 提醒JVM进行垃圾回收
System.gc();
//System.runFinalization();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("SystemGCTest 执行了 finalize方法");
}
}
java使用的是可达性分析算法,内存泄漏都是针对可达性分析算法说的
内存泄漏举例:
StringBuffer str = new StringBuffer("hello mogublog");
局部变量str指向stringBuffer实例所在堆空间,通过str可以操作该实例,那么str就是stringBuffer实例的强引用对应内存结构:
如果此时,在运行一个赋值语句
StringBuffer str = new StringBuffer("hello mogublog");
StringBuffer str1 = str;
那么我们将 str = null; 则 原来堆中的对象也不会被回收,因为还有其它对象指向该区域
// 声明强引用
Object obj = new Object();
// 创建一个软引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用,这是必须的,不然会存在强引用和软引用
// 声明强引用
Object obj = new Object();
// 声明引用队列
ReferenceQueue phantomQueue = new ReferenceQueue();
// 声明虚引用(还需要传入引用队列)
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;
案例
我们使用一个案例,来结合虚引用,引用队列,finalize进行讲解
/**
* @author: 陌溪
* @create: 2020-07-12-21:42
*/
public class PhantomReferenceTest {
// 当前类对象的声明
public static PhantomReferenceTest obj;
// 引用队列
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("调用当前类的finalize方法");
obj = this;
}
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true) {
if (phantomQueue != null) {
PhantomReference<PhantomReferenceTest> objt = null;
try {
objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();
} catch (Exception e) {
e.getStackTrace();
}
if (objt != null) {
System.out.println("追踪垃圾回收过程:PhantomReferenceTest实例被GC了");
}
}
}
}, "t1");
thread.setDaemon(true);
thread.start();
phantomQueue = new ReferenceQueue<>();
obj = new PhantomReferenceTest();
// 构造了PhantomReferenceTest对象的虚引用,并指定了引用队列
PhantomReference<PhantomReferenceTest> phantomReference = new PhantomReference<>(obj, phantomQueue);
try {
System.out.println(phantomReference.get());
// 去除强引用
obj = null;
// 第一次进行GC,由于对象可复活,GC无法回收该对象
System.out.println("第一次GC操作");
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 不是 null");
}
System.out.println("第二次GC操作");
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj 是 null");
} else {
System.out.println("obj 不是 null");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
最后运行结果
null
第一次GC操作
调用当前类的finalize方法
obj 不是 null
第二次GC操作
追踪垃圾回收过程:PhantomReferenceTest实例被GC了
obj 是 null
P168 168-终结器引用的介绍(了解即可)
语法层面:Lambda表达式、switch、自动拆箱装箱、enum
API层面:Stream API、新的日期时间、Optional、String、集合框架
底层优化:JVM优化、GC的变化、元空间、静态域、字符串常量池位置变化
现在标准:在最大吞吐量优先的情况下,降低停顿时间
GC垃圾收集器是和JVM一脉相承的,它是和JVM进行搭配使用,在不同的使用场景对应的收集器也是有区别
serial parNew parallel scavenge
-XX:+PrintcommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo -flag 相关垃圾回收器参数 进程ID
Serial与Serial Old垃圾回收器主要用于单核CPU的场景
在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器。
等价于新生代用Serial GC,且老年代用Serial old GC
P179 179-如何设置使用ParNew垃圾回收器
Parallel与Parallel Old垃圾回收器 :吞吐量优先
JDK8默认垃圾回收器: 年轻代用parallel scavege,老年代用parallel old
方法区在jdk1.7的落地实现叫永久代,在jdk1.8的落地实现叫元空间
JDK1.7 JDK1.7对于新来的新生代edno区剩余空间放不下的大对象,会将endo的对象全部放到老年代,把新来的大对象再放到endo区
GC后
jdk1.8 JDK1.8会将新来的新生代edno区剩余空间放不下的大对象直接放到老年代