文章目录
JVM笔记
所谓的JVM调优,就是调优方法区(方法区是一个特殊的堆)和堆,因为栈(本地方法栈、Java栈)和程序计数器不可能有垃圾。
main方法在栈最底下,如果栈有了垃圾,程序直接就崩了。
1. JVM体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNmekRpH-1618942980059)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20200604091239778.png)]
2. 类加载器
作用:加载Class文件~
1、虚拟机自带的加载器
2、启动类(根)加载器 java语言获取不到
rt.jar包里的类 通过根加载器加载
3、扩展类加载器
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/ext里的东西通过扩展类加载器进行加载
4、应用程序加载器
自己写的类通过应用程序加载器进行加载
3. 双亲委派机制
用于保证安全
APP–>EXC–>BOOT(最终执行根加载器里的同名类)【向上委托】
加载器总会优先加载根加载器里的同名类,找不到才加载扩展器加载器里的类(即、抛出异常,通知子类加载器进行加载),还找不到才会加载当前应用程序加载器
对于一些重要的东西,我们可以把他定义在ext文件目录下,用于防止被盗取。但是尽量不要修改rt.jar
类加载器采用的机制就是双亲委派机制
4. native
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库,进入本地方法栈。
调用本地方法本地接口:JNI
JNI作用: 扩展Java的使用,融合不同的编程语言为java所用。
它在内存区域中专门开辟了一片标记区域:Native Mehtod Stack,登记native方法
在最终执行的时候,加载本地方法库中的方法通过JNI
5. 程序计数器
它是线程私有的,每个线程都有一个程序计数器。
6. 方法区
方法区被线程共享,所有字段和方法字节码,以及构造函数,接口代码等特殊方法也在此定义。所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量在堆内存中,和方法区无关。
static、final、Class模板(类加载器)、常量池
7. 栈
栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就over
栈:8大基本类型+对象引用+实例方法
栈+堆+方法区的交互关系
栈里面的引用指向堆里面的具体位置
8. 三种JVM
- Sun公司
- BEA Oracle JRockit
- IBM J9VM
我们学习的都是HotSpot 即第一种
9. 堆
Heap、一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般把什么东西放到堆中?
- 类、方法、常量、变量,保存我们所有引用类型的真实对象。
- 注意,常量池是在jdk1.7的时候,进入了堆中
堆内存中细分为三个区域:
- 新生区(伊甸园区) Young/New
- 养老区 Old
- 永久区 Perm
新生区
- 伊甸园(Eden Space)
- 幸存区0区
- 幸存区1区
轻量级GC在新生区进行
所有的对象都是在伊甸园里new出来的,假设伊甸园最多放10个对象,当伊甸园满了以后,就要进行一次轻量级GC,检查一下哪些是还有用的,哪些是没用的,没用的就删掉了,有用的就“活着”了,并且把活着的放到幸存区0区,放完以后,伊甸园就空了,又能存10个对象了。
假设幸存区也全都满了,就会进行一次重量级GC,把伊甸园和幸存区全部清理一遍,还活着的就放到老年区(养老区)里。
假设养老区也满了,那就OOM了,没法清理了。
养老区
重量级GC(Full GC)在养老区进行
真理:经过研究,99%的对象都是临时对象!所以能进养老区的对象本身就很少,所以很少见到OOM
永久区
即永久存储区
这个区域是常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或类信息。这个区域不存在垃圾回收!
关闭虚拟机就会释放这个区域的内存。
一个启动类,加载了大量的第三方jar包。或者Tomcat部署了太多的应用。或者大量动态生成的反射类不断的被加载。那么最终会导致OOM。
- jdk1.6之前: 永久代,常量池在方法区里
- jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
- jdk1.8:无永久代,常量池在元空间
jdk1.8之后可以这样理解:
- 元空间里包含了方法区,方法区里包含了常量池
- 元空间是逻辑上存在,但是物理上不存在的
虚拟机改变参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1y1fhWUs-1618942980069)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/截屏2021-04-19 下午8.38.37-8835961.png)]
注1:默认情况下,分配的总内存是电脑内存的1/4、而初始化的内存是电脑内存的1/64
注2:上面的-Xms1024m是连起来写的,没有空格
即:
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
(总内存) (初始内存)
解决OOM的思路
- 尝试扩大堆内存看结果
- 分析内存,看一下哪个地方出问题了(专业工具)
10. 使用JProfiler工具分析OOM原因
在一个项目中,突然出现了OOM故障,如何排除?研究为什么出错~
- 能够看到代码第几行出错:内存快照分析工具,MAT,JProfiler
- DeBug,一行行分析代码!
MAT,JProfiler作用
- 分析Dump内存文件,快速定位内存泄漏;
- 获得堆中的数据
- 获得大的对象
- …
JProfiler安装与配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UV68k25s-1618942980070)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210420144003854.png)]
Jprfiler的具体使用案例
// JProfiler与OOM 测试如下:
public class TestUtils {
byte[] arr = new byte[1*1024*1024]; // 1MB
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
int count = 0;
try{
// 测试通过JProfile查看OOM是什么情况
// Dump
/*
* ---------------------------------------------
* VM options里加参数:
* -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
* 参数分析:
* -Xms 设置初始化内存分配大小 默认为电脑内存的1/64
* -Xmx 设置最大分配内存 默认为电脑内存的1/4
* -XX:+PrintGCDetails 打印GC清理垃圾信息
* -XX:+HeapDumpOnOutOfMemoryError 生成OOM时的dump文件
* ---------------------------------------------
* 然后得到结果:
* java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid71447.hprof ...
Heap dump file created [5515476 bytes in 0.012 secs]
* ---------------------------------------------
* 然后分析结果:
* 首先找到Dump文件
* 左侧 open in finder
* 然后使用JProfiler打开并分析
* 使用完以后,记得删掉dump文件及打开dump以后生成的文件夹(里面有一堆文件)
* ---------------------------------------------
* */
while(true){
list.add(new TestUtils());
count++;
}
}catch (Exception e){
System.out.println("count:"+count);
}
}
}
11. 一些面试问题
- 请你谈谈JVM的理解?
- java8虚拟机和之前的变化更新?
- JVM的常用调优参数有哪些?
- 内存快照如何抓取?怎么分析Dump文件?
- JMM?
12. GC垃圾回收机制
-
新生区
- 新生代(伊甸园)
- 幸存区
- from区
- to区
-
老年区
GC的种类
轻GC(普通GC)
重GC(全局GC)
GC算法
默认情况下,Eden:from:to == 8:1:1
新生区:老年区 = 1:2
1. 引用计数法
一个对象用过一次,就让它的计数器+1,检查的时候,如果计数器为0,则删掉它。(因为没有地方用过嘛,说明没用)
成本:每个对象都有一个计数器
2. 复制算法
谁空谁是to
from和to是在轮流交换的,不是固定的
复制算法流程:
- 首先初始化两块等大小的空间from和to
- 每次GC都会将Eden活的对象移到幸存区from中(一旦GC,则Eden就会变成空的)
- 如果from满了,则把from里活的对象放到to中,同时清理掉死的对象,最后把to变成from,from变成to
- 当一个对象经历了15次(默认值)GC还没有死的话,则进入养老区
-XX:MaxTenuringThreshold=5 通过JVM调参可以修改最大存活时间
通过这个参数可以设定进入老年代的时间。
注:复制算法内部区域中,地址空间是连续的,且对象按照地址由小到大严格排序。
优点:没有碎片空间
缺点:一定会浪费一半的内存空间(即to区大小)
- 假设对象100%存活(极端情况)则复制算法的消耗极大。
- 复制算法最佳使用场景:对象存活度较低的时候 —> 新生区(存活率低)
使用地点:新生区(伊甸园区和幸存区)
3. 标记清除法
标记清除法的流程:
- 先扫描一次,对对象进行标记
- 再扫描一次,对没有标记的对象进行删除
优点:不需要额外的空间!
缺点:两次扫描严重浪费时间;会产生内存碎片
使用地点:老年区,因为区域大、对象存活率高。
4. 标记压缩法(标记整理法)
再优化:
- 再一次扫描,向一端移动存活的对象
优点:没有内存碎片
缺点:多了一次移动成本
可以选择执行数次标记清除法以后,再执行一次标记压缩。
使用地点:老年区,因为区域大、对象存活率高。
GC总结
大于号代表优于
时间复杂度:复制算法 > 标记清除 > 标记压缩
内存整齐度:复制算法 = 标记压缩 > 标记清除
内存利用: 标记压缩 = 标记清除 > 复制算法
13. JMM
Java Memory Model
Java内存模型
JMM的作用
它相当于一个缓存一致性协议,用于定义数据读写的规则(遵守这样的规则)
JMM定义了JVM在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。
JMM定义了 线程和主内存之间的抽象关系,所以JMM是一个抽象的概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他硬件和编译器的优化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuHkVIw6-1618942980072)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210421020242261.png)]
如图,每个线程都有自己的私有的本地内存,它如何和主内存交互,以及保证数据的正确性,都是JMM来控制的。(因为线程改了数据以后,还没有到CPU那一层,没有经过flush,数据没有得到刷新)
解决共享对象可见性这个问题:volatile(保证可见性,不保证原子性)
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
怎么解决原子性问题呢?可以使用juc包下的atomic包下的对象就可以了。详见JUC笔记。
JMM的一些规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGmgLsbA-1618942980073)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210421022003356.png)]
JVM进阶
深究JVM需要很多时间,可以阅读《深入理解JVM》,等先把基础补得差不多了以后,再看这本书吧。最近的时间任务重心应该放在:多线程、JUC、DP、项目实践以及设计模式,之后看ssm框架,中间件的使用与了解,然后有时间还要搞毕设、看论文。这些事情完了以后,再深究JVM吧!