近期看看JVM,看了狂神说入门教学,总结下给大家。
文章目录
- 1、JVM的位置
- 2、JVM的结构体系
- 3、类加载器及双亲委派机制
- 3.1、类加载器作用
- 3.2、类加载器类型
- [3.3、双亲委派机制 *](#3.3、双亲委派机制 *)
- 4、沙箱安全机制
- 5、Native、方法区
- [5.1、Native(本地方法栈引用) *](#5.1、Native(本地方法栈引用) *)
- 5.2、PC寄存器
- [5.3、方法区 *](#5.3、方法区 *)
- 6、栈
- 7、走近HotSpot和堆
- 8、使用JPofiler工具分析OOM原因
- [9、GC算法 *](#9、GC算法 *)
- 总结
- 面试点
1、JVM的位置
JVM处于操作系统之上,为Java程序在不同的系统平台上的运行提供便利,与硬件没有直接的交互。
2、JVM的结构体系
引用地址存在栈内,实际指向的内容存在堆中。
3、类加载器及双亲委派机制
3.1、类加载器作用
3.2、类加载器类型
-
虚拟机自带的加载器
-
启动器(根)加载器
-
扩展类加载器
-
应用程序加载器
3.3、双亲委派机制 *
类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。
优点:
避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。
安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。
扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。
缺点:
灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。
破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。
不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外
的处理。
4、沙箱安全机制
5、Native、方法区
5.1、Native(本地方法栈引用) *
5.2、PC寄存器
5.3、方法区 *
static、final、Class模板、常量池
从Java 8开始,常量池从方法区转移到Java虚拟机堆内存之中
6、栈
栈:先进后出
队列:先进先出(FIFO:First Input First Output)
堆、栈、方法区:(从Java 8开始,常量池从方法区转移到Java虚拟机堆内存之中)
7、走近HotSpot和堆
堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调整的。
类加载器读取了类文件后,一般会把什么东西放到堆中呢?
类、方法、常量、变量~,保存我们所有引用类型的真实对象(实例对象)
堆内存中细分为三个区域:
-
新生区(伊甸园区) Young/New
-
养老区 old
-
永久区 Perm
JDK8以后,永久存储区改名为元空间 (逻辑上存在(构思存在),物理上不存在)
GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够!
设置VM参数
public static void main(String[] args) {
// 获取虚拟机试图使用最大内存
long max = Runtime.getRuntime().maxMemory();
// 获取Jvm内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max memory :" + max+" 字节,"+max/(double)1024/1024+"MB");
System.out.println("total memory :" + total+" 字节,"+total/(double)1024/1024+"MB");
}
修改前:
max memory :3309305856 字节,3156.0MB
total memory :223346688 字节,213.0MB
修改后:(添加VM配置 -Xms1024m -Xmx1024m -XX:+PrintGCDetails)
max memory :1029177344 字节,981.5MB
total memory :1029177344 字节,981.5MB
Heap
PSYoungGen total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3432K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 370K, capacity 388K, committed 512K, reserved 1048576K
8、使用JPofiler工具分析OOM原因
下载JPofiler工具,安装idea的JPofiler插件
//-Xms 设置初始化内存分配大小
//-Xmx 设置最大分配内存
//-XX:+PrintGCDetails 打印GC垃圾回收信息
//-XX:+HeapDumpOnOutOfMemoryError OOM时拿到内存快照文件
public class OOMTest {
public static void main(String[] args) {
ArrayList<OOMTest> list = new ArrayList<>();
int count = 0;
// 创建数组,一直扩大
while (true) {
list.add(new OOMTest());
count ++;
}
}
}
VM参数加入 -Xms1m -Xmx2m -XX:+HeapDumpOnOutOfMemoryError (OOM时拿到内存快照文件)
运行后在项目根目录能拿到内存快照文件 ,打开后可以看线程转储这部分分析
由此可得出具体报错位置
9、GC算法 *
9.1、引用计数法
9.2、复制算法
- 好处:没有内存碎片
- 坏处:浪费空间,多了一半内存空间永远是空to
- 使用场景:对象存活率较低的情况下(新生区)
9.3、标记压缩清除法
标记清除法
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
优化:增加压缩(标记压缩算法)
总结
内存效率:复制算法>标记清除>标记压缩(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
没有最优算法。只有最合适的算法。
所以GC的好处:分代收集算法
年轻代:存活率低,用复制算法
老年代:存活率高,区域大,用标记清除+标记压缩混合实现(标记清除n次,进行标记压缩)
面试点
JVM的内存模型和分区~详细到每个区放什么?
JVM 分为堆区和栈区,还有方法区
堆区 存对象实例、数组、常量池(从Java 8开始,常量池从方法区转移到Java虚拟机堆内存之中)等
栈区 存引用地址,八大基础数据类型(局部变量),实例方法
方法区 存 static、final、Class模板
堆里面的分区有哪些?Eden,form,to,老年区,说说他们的特点?堆分为年轻代(伊甸园区,幸存区0和1)和老年代两个分区(元空间是逻辑上存在)
Eden区(伊甸园区)是年轻代中最大的一个区域,用于存放新创建的对象。当一个对象被创建时,首先会被分配到Eden区。当满员触发GC存活对象会放置幸存区;
Survivor区是年轻代中的两个区域之一,一般称为From区和To区。伊甸园区GC存活的的会转至幸存区的From区,下次GC时若存活则复制至To区(谁空或谁少为to区)
老年区是年轻代中对象经过多次轻GC仍然存活的对象所存放的区域,老年区的对象生命周期较长,因此老年区的垃圾回收频率较低,当需要GC是重GC(Full
GC),会对整个堆内存进行回收。
GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数法,怎么用?GC(分代收集法)的算法(GC的作用域是堆和方法区):标记清除法、标记压缩法、复制算法、引用计数法
引用计数法:各个对象每用一次,该对象计数器就+1(计数器本身也要有消耗)。当计数器为0时,说明该对象没有,就立刻进行垃圾回收。
复制算法:(年轻代主要用到复制算法:Eden区和幸存者区,要知道幸存区分为from和to两个区域:哪一个区域是空哪一个区域就是to区。)
标记清除:先扫描对象,对活着的对象进行标记。然后开始清除,对没有标记的对象进行清除
标记压缩:在标记清除的基础上再次扫描,向一端移动所有存活的对象(减少了内存碎片)
轻GC和重GC分别在什么时候发生?法、引用计数法**
引用计数法:各个对象每用一次,该对象计数器就+1(计数器本身也要有消耗)。当计数器为0时,说明该对象没有,就立刻进行垃圾回收。
复制算法:(年轻代主要用到复制算法:Eden区和幸存者区,要知道幸存区分为from和to两个区域:哪一个区域是空哪一个区域就是to区。)
标记清除:先扫描对象,对活着的对象进行标记。然后开始清除,对没有标记的对象进行清除
标记压缩:在标记清除的基础上再次扫描,向一端移动所有存活的对象(减少了内存碎片)
轻GC和重GC分别在什么时候发生?轻GC一般发生在 新生代和幸存区,重GC一般发生在老年代