JVM常见知识点总结
1、JVM的位置
可以把JVM理解为一个应用程序,它是运行在操作系统之上的,我们在安装java运行环境JRE的时候,里面就已经包含了JVM。
2、JVM的体系结构
栈内存中不存在GC(因为栈就是方法不断的压栈、出栈,如果有垃圾会发生堵塞,不可能有垃圾的),所以常说的JVM调优指的是操作堆内存
3、类加载器
应用程序加载器、扩展类加载器、根加载器(启动类加载器)
4、双亲委派机制
当我们创建类,并且执行方法时。会先去根加载器下面找有没有这个类,如果有,就执行根加载器下的类和方法,而不会再执行本地创建的。当根加载器下没有时,会去扩展加载器下找有没有这个类,如果有,就执行扩展加载器下的类和方法,而不会再执行本地创建的。如果没有,才会执行本地应用程序加载器。
(1)、类加载器收到类加载的请求
(2)、将该请求向上委托给父类的加载器去完成,一直向上委托,直到根加载器
(3)、根加载器检查能否加载该类,能加载就结束了,使用当前的根加载器去加载即可。如果不能,抛出异常,通知子加载器进行加载(扩展、应用)
(4)、重复3过程
5、Native
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库。native标记的方法会进入本地方法栈,然后通过本地方法库去调用本地方法接口(JIN), 这样可以扩展java的使用,融合不同的语言为java所用(本地方法库和本地方法接口的作用就是可以调用访问底层的C、C++所写的库)。
6、PC寄存器(程序计数器)
每个线程都有一个程序计数器,是线程私有的,执行下一条指令时候加1,可以保证线程能够有序不乱的运行(有1,2,3,4,5,6,7,8这些顺序编号)
7、方法区
方法区线程共享,里面存放 静态变量、常量、代码片段(.class字节码文件)、运行时的常量池。JDK1.8之前,方法区有自己单独的空间,JDK8之后,方法区存在于堆内存的元空间(永久区)中,也就是,方法区属于堆了
8、栈
(1)一个线程一个栈,线程结束,栈内存释放
(2)不存在垃圾回收问题
(3)存放 局部变量(8种基本数据类型)、方法、对象的引用
9、三种JVM
HotSpot(我们只用这个)、JRockit、J9VM
10、堆
(1)一个JVM只有一个堆内存,所以它里面会有很多垃圾。堆内存大小可以调节
(2)堆中主要存放 实例变量、对象
(3)堆主要包括,新生区、老年区、永久区(元空间)。GC垃圾回收机制主要存在于新生区中的伊甸园区和老年区
(4)OOM(OutOfMemoryError):堆内存溢出,堆内存满了会报此错
11、新生区、老年区
新生区:创建对象时(new Student())就是在新生区里的伊甸园区创建的。它是类诞生和死亡的地方。大部分对象是临时对象,会在伊甸园
区用完以后直接被轻GC杀死,少部分会跑到幸存区(from、to),
极少会进入到老年区
12、永久区(元空间)
永久区常驻于内存,用来存储JDK自身携带的Class对象,也就是java运行时的一些环境,不存在垃圾回收。一般永久区是不会出现OOM的,除非几个特殊情况:一个启动类加载了大量的第三方jar包、Tomcat部署了太多的应用。
元空间逻辑上存在,物理上不存在
14、遇到OOM怎么办:
(1)OOM原因:新生区创建对象,存在于伊甸园区,不断的创建,不断的用轻GC回收,如果对象不多,轻GC完全可以应付在伊甸园区就把垃圾清理完毕。但是如果对象过多,产生的垃圾过多,就会跑到幸存区,这时候轻GC还在努力地做清理工作,如果垃圾还是在不断的产生,过多,那么就会跑到老年区,这时候,重GC就会登场,重GC的清理范围会比轻GC大得多,它会清理新生区、老年区里的所有垃圾。当垃圾过多,老年区也存满了,重GC也顶不住的时候,就会报OOM堆内存溢出的错误。
2、解决方法(堆内存调优):
(1)尝试扩大堆内存空间,观察运行结果,如果正常了,说明是
堆内存空间不足导致的
-Xms1024m -Xmx1024m -XX:+PrintGCDetails(打印GC信息)
-Xms:设置初始化内存分配大小
-Xmx:设置最大内存
(2)分析内存,看一下哪个地方出现了问题(专业工具Jprofiler)
在IDEA中安装Jprofiler插件,当报出OOM错误时,在IDEA
中设置Dump文件:
Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError,此时就会Dump出一个文件,然后用Jprofiler就可以打开这个文件,通过文件里面的信息就可以准确判断出程序是在哪里甚至哪几行出现了问题,然后就可以去修改。
15、GC(常用算法)
a)标记清除法
优点:不需要额外的内存空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
b)标记压缩法
对标记清除算法进行再次优化,再次扫描,向一端移动存活的对象
c)复制算法:新生区主要使用复制算法。当伊甸园区满了,垃圾会进入幸存区(from、to),遵循谁空谁是to的原则,在from和to之间不断的进行交换操作。假如from、to都有数据,那么此时就需要把其中一个区的数据复制到另一个区,然后就腾出来一个空的区作为to区以方便后续的存放垃圾,这就是复制算法。每一次GC过后,伊甸园区和to区都是空的。当15次GC还没有回收掉的垃圾(默认15次,可以修改),就会进入到老年区。
优点:没有内存碎片
缺点:浪费了一半内存空间(to区永远是空的)。当垃圾过多时,从from
区复制到to区的成本太高。所以复制算法最佳使用场景:对象存活
度较低的时候,也就是在新生区使用。
d) 引用计数法(对每个对象的引用次数进行计数,次数为0的就回收。这种方法很少用,因为对象太多了,每个都计数太浪费空间了)
e)总结各个算法
内存效率:复制算法>标记清除>标记压缩(次数最多)
内存利用率:标记压缩>标记清除>复制算法(一半空间为空)
没有最好的算法,只有更合适的算法。所以GC算法也被称为分代收集算法。
在新生区一般使用复制算法,在老年区一般使用标记清除+标记压缩(具体的清除多少轮再进行压缩是个关键,当内存碎片不多时候可以继续再进行清除,当碎片很多了再进行压缩,这也是JVM调优的关键步骤)混合实现。