JVM探究
-
JVM的位置
-
JVM的体系结构
-
-
栈,本地方法栈,程序计数器不会有垃圾回收(栈如果有垃圾,会将整个程序堵住)
-
方法区以及堆产生垃圾
-
JVM主要调堆
-
-
类加载器
-
作用:加载Class文件
-
实例化的对象,其名字在栈里面
-
-
虚拟机自带的加载器
-
启动类(根)加载器
-
扩展类加载器
-
应用程序(系统类)加载器
-
-
双亲委派机制
- 最终执行的是根加载器
- App->EXc ->Boot
-
沙漏安全机制
-
NAtive
- 凡是带了native关键字的,说明Java的作用范围达不到,会去调用底层c语言的库!
- 会进入本地方法栈
- 调用本地方法接口 JNI(Java Native Interface)
- JNI作用:扩展Java的使用,融合不同的编程语言为Java所用!
- 它在内存区域专门开辟了一块标记区域:Native Method Stack 登记native方法
- 在最终执行的时候,加载本地方法库中的方法通过JNI
- 或者硬件:Java程序驱动打印机,管理系统,Robot()类等
-
pc寄存器
-
方法区
-
栈
-
Java对象在内存中实例化
-
最初代码执行时
-
代码执行,类中的成员变量和方法会先进去到方法区
-
程序执行到main方法时,main方法会被压入到栈中
-
执行到Person person = new Person();会在栈中创建Person类的引用,会在堆中存放实例化的对象,然后将成员变量和成员方法压在实例中(都是取得成员变量和成员方法地址值)
-
接下来person对象进行赋值
-
在方法体void()被调用完成后,就会立刻马上从栈内弹出(出栈) ;最后,在main()函数完成后,main()函数完成后也会出栈
-
-
-
三种Jvm
- sun公司
- BEA HRockit
- IBM J9 VM
- 我们学习的都是:HotSPot
-
堆
- Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
- 类加载器读取了类文件后,一般会把类,方法,常量,变量~,保存我们所引用类型的真实对象
- 堆内存还细分为三个区:
-
新生区 (伊甸园区) Young/New
-
养老区 Old
-
永久区 Perm
-
-
GC垃圾回收 ,主要是在伊甸园区和养老区
-
-
新生区,老生区
- 新生区:
- 类:诞生和成长的地方,甚至死亡
- 伊甸园区 所有的对象都在伊甸园区new出来的
- 幸存者区(0,1)
- 新生区:
-
永久区
-
这个区域常驻内存。用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时一些环境或类信息,这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个区域的内存
-
一个启动类,加载了大量第三方jar包。tomocat部署了太多的应用,大量动态生成的反射类,不断的被加载,知道内存溢出(oom)
-
jdk1.6之前 :永久代,常量池在方法区;
-
jdk1.7 :永久代,但是 慢慢退化、
去永久代
,常量池在堆中 -
jdk1.8之后 :无永久代,常量池在元空间
-
oom的排错:默认情况下:分配的总内存是电脑内存的1/4,而初始化的内存:为电脑内存的1/64
- 1.尝试扩大堆内存看结果
- 分析内存,看一下哪个地方出现了问题(专业工具)
-
-Xms1024m -Xmx2014m -XX:+PrintGCDetails //修改初始内存和最大内存 ,打印GC垃圾回收信息
-
-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError //打印Dump文件
-
元空间,逻辑上存在,物理上不存在
-
-
在一个项目中,突然出现了OOM故障,如何排除?
- 能够看到第几行代码出错:内存快照分析,MAT,Jprofiler
- Debug,一行一行代码分析
-
MAT,Jprofiler作用
- 分析Dump内存文件,快速定位内存泄漏
- 获得堆中数据
- 获得大的对象
- ~
-
-
堆内存调优
-
GC
-
GC常用算法
-
JMM
-
JMM:(Java memory model的缩写)
-
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)
-
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory)
-
-
解决共享对象可见性这个问题:volitale,synchronized
-
内存交互操作
- lock锁定
- unlock解锁
- read读取
- load载入
- use使用
- assign赋值
- store存储
- writer写入
-
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,(必须)read与load之间、store与write之间是可插入其他指令的。
(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
-
-
GC垃圾回收器
-
-
JVM在进行GC时,并不是对这三个区域统一回收,大部分的时候,回收都是新生代
-
新生区
-
幸存区(from ,to)
-
老年区
-
GC两种类:轻GC(普通的GC),重GC(全局GC)
-
-
GC题目:
-
JVM的内存模型和分区,详细到每个区放什么?
-
堆里面的分区有哪些?Eden,from,to,老年区,说说他们的特点
-
轻GC和重GC分别在什么时候发生?
-
引用计数法:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c20yLQ1j-1653494209901)(C:\Users\26738\AppData\Roaming\Typora\typora-user-images\image-20220524161358083.png)]
-
复制算法
-
- 好处:没有内存的碎片
- 坏处:浪费了内存空间;多了一半空间永远是空to
-
复制算法最佳使用场景:对象存度较低的时候;新生区
-
标记清楚算法
- 优点:不需要额外的空间!
- 缺点:两次扫描,严重浪费时间,会产生内存碎片
-
标记压缩
-
标记清楚压缩
- 先标记
后清除一次:
- 先标记
-
-
-
常用算法
- 标记清除法,标记压缩,复制算法,引用计数器
-
-
总结
- 内存效率:复制算法>标记清楚算法>标记压缩算法(时间复杂度)
- 内存整齐度:复制算法=标记压缩算法>标记清楚算法
- 内存利用率:标记压缩算法=标记清楚算法>复制算法
- GC:分代收集算法
- 年轻代
- 存活率低
- 复制算法
- 老年代
- 区域大:存活率
- 标记清楚(内存碎片不是太多)+标记压缩混合 实现
缩算法(时间复杂度)
2. 内存整齐度:复制算法=标记压缩算法>标记清楚算法
3. 内存利用率:标记压缩算法=标记清楚算法>复制算法
4. GC:分代收集算法
5. 年轻代
1. 存活率低
2. 复制算法
6. 老年代
1. 区域大:存活率
2. 标记清楚(内存碎片不是太多)+标记压缩混合 实现