JVM初步学习
参考 https://www.bilibili.com/video/BV1iJ411d7jS?p=14
1.基础知识
1.JVM运行在操作系统上,包含在jre里。
jvm调优主要在堆,方法区有一小部分。
2.类是一个模板是抽象的 对象是具体的,模板只有一个,对象创建后就有不同的地址
2.类加载器:
作用:加载.class文件 新建的对象放在堆里面,引用地址放到栈中,其引用指向堆里面对应的对象
public class Solution
{
public static void main(String[] args) {
Solution s1=new Solution();
Solution s2=new Solution();
Solution s3=new Solution();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
Class<?extends Solution>as1=s1.getClass();
ClassLoader classLoader=as1.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}
}
1)虚拟机自带的加载器
2)启动类(根)加载器 Bootstrap ClassLoader
3)扩展类加载器 Extension ClassLoader
4)应用程序(系统类)加载器 Application ClassLoader
3.双亲委派机制
检查顺序从下至上,加载顺序从上到下。
如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层(启动类加载器),当父加载器无法完成这个请求时,子类才会尝试去加载。
4.沙箱安全机制
控制远程代码执行的权限。
5.native关键字
包含native关键字的方法,说明java的作用范围不到了,会去调用底层c语言的库。
会进入本地方法栈。调用本地方法接口JNI。
JNI作用:扩展java的使用,融合不同的编程语言为java所用。 最初:c、c++
它在内存区域中专门开辟了一块标记区域:native method stack,登记native方法
在最终执行的时候,通过JNI加载本地方法库中的方法
6.方法区
是被所有线程共享的,所有的字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义方法的信息都保存在该区域,此区域属于共享空间
存储内容:static、final、.class、常量池
7.JVM中的栈
一旦线程结束,栈就结束了,内存释放。无限递归调用导致栈溢出
栈:数据结构;先进后出
队列:先进先出(FIFO)
main()先执行后结束——在栈中执行
栈:又称栈内存,主管程序的运行;生命周期和线程同步;线程结束,栈内存释放,不存在垃圾回收。
栈中存放:8大基本类型+对象引用+实例方法
栈的运行原理:栈满了StackOverflowError
存储:8大基本类型 + 对象引用 + 实例的方法
8.三种JVM
- Sun公司:HotSpot
- BEA:JRockit
- IBM:J9 VM
9.堆 heap
一个JVM只有一个堆,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把类,方法,常量,变量,保存所有引用类型的真实对象放到堆中。
垃圾回收:轻量级GC,重量级GC(Full GC)
10.堆内存
分为三个区域:新生区(伊甸园区Eden Space;幸存0区,幸存1区)、养老区、永久存储区。
GC垃圾回收,主要在伊甸园区和养老区
假设内存满了OOM,java.lang.OutOfMemoryError
在JDK8以后,永久存储区叫元空间
11 新生区、永久存储区、堆内存调优
-
新生区、类的诞生和成长的地方,甚至死亡 99%的对象都是临时对象
- 伊甸园区Eden Space:所有对象都在伊甸园区new出来的! 满了会触发轻GC
- 幸存0区
- 幸存1区
-
养老区Old:新生区幸存的,杀不死的
-
GC垃圾回收主要在伊甸园区和养老区
-
永久存储区Perm:这个区域是常驻内存的,用于存放JDK自身携带的class对象,Interface元数据,存储的是Java运行时的一些环境。这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存。
-jdk1.6之前:永久代,常量池在方法区之中
-jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
-jdk1.8:无永久代,常量池在元空间(方法区/非堆)一个启动类加载了大量的第三方jar包;tomcat部署了大量的应用;大量动态生成的反射类;不断地被加载,直到内存满,会导致这个区域崩溃,出现OOM,堆内存满了
默认的情况下:分配的总内存 是电脑内存的1/4;而初始化的内存:1/64
long max=Runtime.getRuntime().maxMemory();//字节1024*1024*1024
long total=Runtime.getRuntime().totalMemory();
System.out.println("max= "+max+" 字节\t "+(max/(double)1024/1024)+"MB");
System.out.println("total= "+total+" 字节\t "+(total/(double)1024/1024)+"MB");
OOM:
1)尝试扩大堆内存看结果
2)分析内存,看一下哪个地方出了问题(专业工具)
Xms1024m Xmx1024m -XX:+PrintGCDetails
在一个项目中,突然出现了OOM故障,那么该如何排除?研究为什么出错
看到代码的第几行出错,内存快照分析工具,MAT,jprofiler
一行行测试代码(不现实)
jprofiler作用:
1)分析dump内存文件,快速定位内存泄露
2)获得堆中的数据
3)获得大的对象
元空间:逻辑上存在 物理上不存在
12.GC 引用计数法
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是新生代。
轻GC:普通的GC
重GC:全局的GC (full GC)
引用计数器:每个对象都记录使用次数,清除使用次数为零的算法。微软,python,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oL1tOaOV-1610441850387)(C:\Users\lhh31\AppData\Roaming\Typora\typora-user-images\1610440088907.png)]
13-GC_复制算法
谁空谁是to
1。每次GC都会将Eden活得对象移到幸存区中,一旦Eden区被GC后,就会是空的。
2.如果空的相等,就把from移动到to,
当一个对象经历了15(-XX:MaxTenuringThreshold)次GC,都还没有死,进入老年代
年轻代主要使用复制算法。
好处:没有碎片
坏处:浪费内存空间,多了一半空间是空的,极端下,对象百分百存活,操作移动复杂
3.再优化:压缩
为了防止内存碎片产生,再次扫描,向一端移动存活的对象
标记清除压缩:先标记清除几次再压缩
14-标记压缩清楚算法
1.扫描对象,对活着的对象标记
2.清除:对没有标记的对象进行清除
优点:不需要额外空间
缺点:两次扫描,浪费时间,会产生内存碎片。
15-总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记清除算法>标记压缩算法
内存利用率:标记压缩算法=标记清除算法>复制算法
GC:分代收集算法
年轻代:存活率低,使用复制算法
老年代:区域大,存活率高 使用标记清除(内存碎片不是太多)+标记压缩 混合实现