JVM内存模型
JVM内存模型其实就是JVM在启动的时候从操作系统内存中要了一块大内存,然后将这个大内存分成五个区域:方法区、堆区、虚拟机栈、本地方法栈、程序计数器。
图例:
方法区
-
永久代和元空间
永久代:jdk8之前方法区的具体实现,直接存在堆区 元空间:jdk8及以后方法区的具体实现,直接内存,os内存
可以这么理解:方法区是一个规范,概念,类似java中的接口。而永久代和元空间是具体的实现类。
类的元信息instanceKlass就是存储在元空间里的 -
JVM为什么用元空间代替了永久代?
1.gc算法
我们知道堆区是存储对象的,而之前把像什么类信息,字段这些存再堆。gc做标记的时候就需要区分开那一块是那一块。而改成元信息之后,gc算法就比较好写了
2.OOM
类有时候动态生成,比如像cglib,很容易就OOM了
3.硬件的发展
之前是32bit,现在可以有64bit -
元空间最大最小是多少?
最大: 4294901760 =4095M 最小: 21807104 =20.75M
-
元空间如何调优?
-XX:MetaspaceSize
-XX:MaxMetaspaceSize
跟堆一样,最大最小调成一样(可以防止内存抖动)
调成多少,物理内存的1/32
visualVm、arthas调试工具
本地方法栈
java调用c、c++的动态链接库,运行里面的函数需要的栈
程序计数器
执行引擎的指针
虚拟机栈
-
一个虚拟机有多少个虚拟机栈?
- 一个线程一个 一个虚拟机栈有多少个栈帧?
- 方法调用个,一个方法一个
栈帧:
局部变量表
存储局部变量的表(LocalVariableTable)
操作数栈
是使用字节码指令对数据进行操作了,push,pop之类的,需要查看:字节码手册
动态链接
指向方法对象的内存地址
返回地址,保存现场
这一步干了很多事,看这个代码
每个方法都都会生成一个栈帧,对把。我们执行到add()的时候
1. 生成add的栈帧
2. 在add方法的栈帧中保存main方法的字节码的下一行程序计数器
3. 将线程的局部表开始指针(main的)保存到add的栈帧中
4. 将线程的操作数栈开始指针(main的)保存到add的栈帧中
5. 将add的局部表开始指针赋值给线程的局部表指针
6. 将add的操作数栈开始指针赋值给线程的操作数栈的指针
堆区
最大大小:1/4实际内存大小
最小大小:1/32实际内存大小
大对象什么时候会进入老年代?
1.15次gc之后,自动进入老年代
为啥是15次?可以调成16次嘛?
因为它占4bit,对应的二进制等于15.所以只能调小,不能调大
2.大对象
超过eden区的一半,直接进去老年代
eden的大小是动态调整的
3.空间担保
gc以后,Eden区剩下的对象还是比form区或to区都大,又不能丢弃,所以进去老年代
4.动态调整
gc之后,eden区和from区的对象,to区放不下,进入老年代
总结问题
-
1.虚拟机栈指向方法区
- 动态链接 2. 虚拟机指向堆区
- 对象的引用Test obj = new Test() 3. 方法区指向堆区
- 引用类型的静态属性 4. 堆区指向方法区
- Klacc pointer,指向改对象的InstanceKalss实例