Jvm分为两大块,第一部分是是Jvm内存区域的管理,第二部分垃圾收集器。目前Jvm默认的是sun公司(已被oracle公司收购)的HotSpot虚拟机;此前还有BEA公司的JRockit(原来研发weblogic的公司,之后也被oracle公司收购,之后java8中虚拟机是两者的标准);还有IBM公司的J9 VM,最终还是HotSpot获得最后的胜利。
一、Jvm示意图简介
-
Class Loader类加载器
负责加载class文件,class文件在文件开头有特定标示,并且ClassLoader只负责class文件的加载,至于他是否运行,则由Exeution Engine(执行引擎)决定。 -
Native Interface本地库接口
本地接口的作用是为了融合C/C++语言,为了调用C/C++程序,在内存中开辟了一块区域处理标记为native的代码,它的具体做法是在Native Method Stack(本地方法栈)登记native方法,在Exeution Engine(执行引擎)执行时加载native libraries,native关键字,指的是java 本地库接口(java可以直接调用c语言)JNI指的是Java native interface -
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法信息都保存在该区域,此区属于共享区间。
静态变量+常量+类信息+运行时常量池 存在方法区中 -
PC register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。 -
Native Method Stack 本地方法栈
它的具体做法是在Native Method Stack登记native方法,在Exeution Engine(执行引擎)执行时加载native libraries。
自问自答:什么是栈?什么是堆?
栈管运行,堆管存储。
-
Stack栈是什么
栈也叫栈内存,主管java程序运行,是在线程创建时创建,它的生命周期跟随线程的生命周期,线程结束栈内存就释放,对于栈来说不存在垃圾回收问题,生命周期和线程一致,是线程私有的(私有的内存不需要优化)。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
方法存放在栈中,并且称之为“栈帧”(可以用子弹夹的例子作为理解),栈帧主要保存的数据有:本地变量(输入参数、输出参数以及方法内的变量)、栈操作、栈帧数据(包括类文件、方法等)
栈内存放的数据有:八种基本类型的变量,引用变量,方法
可能出现的异常:java.lang.StackOverflowError(栈内存溢出异常) -
堆是什么?
堆分为新生区(伊甸区Eden space、幸存0区Survivor 0 space、幸存1区Survivor 1 space)、养老区(Tenure Generation space)、永久存储区。
新生区:是类的诞生、成长、消亡的区域,当伊甸园区的空间快被使用完时(占用空间的七成),程序又需要创建对象,Jvm的垃圾收集器将对伊甸园区进行垃圾回收(Minor GC,轻量级),直到养老区也满,将进行Major GC,又称FullGC,全局回收。FullGC后仍无法进行对象的保存,就会产生OOM异常,java.lang.OutOfMemoryError:java heap space,导致java虚拟机堆内存空间不够的原因:
1、设置的堆内存不够用,可以通过设置参数 -Xms、-Xmx来调整
2、代码中创建来大量大对象,长时间不能被回收(存在被引用)。
养老区:用于保存从新生区筛选出来的Java对象,一般池对象都在这个区活跃。
永久区:永久存储区是一个常驻内存区域,用于存放JDK自身携带的class、interface的元数据。永久区也会出现OOM异常,不过打印出来的异常是 PermGen space,说明java虚拟机对永久代内存设置不够,一般情况下,都是程序启动需要加载大量的第三方jar包。
二、GC垃圾收集器以及优化
**jvm优化,百分之99优化堆,百分之1优化方法区,优化的都是共享空间**
下面简介一下java6、7、8常量池的情况
jdk1.6 有永久代,常量池在方法区
jdk1.7 有永久代,准备去永久代,常量池在堆内存中
jdk1.8 去永久代,常量池在元空间
永久代/元空间只是堆的逻辑组成部分,实际在内存中并不占用堆的组成内存,堆只由新生区和养老区组成。
堆内存调优介绍:
铭记三个参数:
-Xms(start):设置最初分配的大小,默认为物理内存的1/64
-Xmx(max):设置最大分配内存,默认物理内存的1/4
-XX:+PrintGCDetails:输出详细的GC处理日志
GC是什么?
频繁收集Young区(较小收集)
较少收集Old区(较大收集)
基本不动Perm区
新生区GC
新生区GC一般都是较小频繁收集的情况,使用的是复制算法copying,也是MinorGC,或者叫 普通GC。复制算法的规则,新生区可以划分为8:1:1,其中8是指新建对象存放的比例,1:1则是from区域、to区域,当经过垃圾回收之后,剩余存活下来的对象会放到to区域,作为下次复制算法的from区域,以此不断循环,可以提一下的是,一般是满15(有参数可以控制)由新生区进养老区,to区域放满后也可以进养老区。
优点:完整性高,不会产生内存碎片
缺点:1⃣️浪费了一半的内存空间(对于from、to区域而言是1:1)
2⃣️如果对象的存活率过高,java虚拟机将会复制所有的对象,以及对象引用的变量地址,严重影响到性能。
养老区GC
养老区一般使用的是标记清除或者标记清除与标记整理的混合实现,是FullGC,又叫全局GC。
标记清除Mark-Sweep缺点:
1⃣️效率低下,需要双重扫描
2⃣️产品内存碎片,内存回收空间不连续(延伸出下面另一算法)
标记整理Mark-Compact缺点:
效率低下,标记整理算法要低于复制算法
三者的特点与区别
内存效率:复制算法>标记清除算法>标记整理算法(此处只是简单的从时间复杂度进行比对)
内存整齐度:复制=标整>标清(内存碎片角度考虑)
内存利用率:标清=标整>复制(复制消耗大量内存)