1、JVM的位置
jvm本质上也是一个软件,所以jvm是在操作系统上执行的
2、jvm体系结构
jvm的体系结构主要包含五大部分:方法区、堆、栈、本地方法栈、程序计数器
类的加载过程分为:加载、验证、准备、解析、初始化五个阶段,也就是加载、链接(验证、准备、解析)、初始化
3、类加载器
作用:加载Class文件
在jvm里面有三个类加载器:BootStrapClassLoader、ExtClassLoader、AppClassLoader
如果想自定义一个类加载器的话,可以继承ClassLoader类
每个类都有一个Class对象,而每个Class对象都有许多个实例
Class对象通过new创建实例,实例可以通过getClass获得Class对象
双亲委派机制
双亲委派机制就是当类需要去加载一个.class字节码文件的时候,会首先把它委托给自己的上级去加载,上级没有,自己才会加载,即向上查找缓存,向下加载
作用:
- 防止重复加载,双亲委派机制向上查找的时候会去查找缓存里有没有加载过这个类,加载过就不用重复加载了。
- 避免核心代码被篡改,通过委派的方式,如果上级加载可以加载这个类,就会直接加载,即使篡改了这个类,它也不会被加载。保证了安全
例如:
我自己编写了一个String类,并写了个toString方法
package java.lang;
public class String {
public String toString() {
return "tossssss";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.toString());
}
}
执行结果:
由于String是在根加载器中进行加载的,所以双亲委派机制会将String类向上提交,交给根加载器去加载,而根加载器中的String是没有main()方法的,也就是说,我们编写的这个类根本就没有被加载,所以才会说找不到 main 方法
4、堆
堆里面一般用来存放对象和数组
方法区一般存放静态变量、常量、类信息(构造方法/接口定义) 、运行时常量池
堆一般分为新生区、老年区
4.1、新生代(新生区)、老年区
1、新生代可以分为三个区域:伊甸园区(Eden)、幸存0去(S0)、幸存1区(S1),它们三个的空间比默认为8:1:1,可以设置
2、所有的对象在刚创建(new)的时候,基本都会放在伊甸园区,但是当这个对象太大了的时候,会直接放在老年区
3、当伊甸园区放满之后,会触发一次轻GC(Minor GC),将伊甸园区(Eden)和幸存0区(From)幸存下来的对象放入幸存1区(To),之后幸存0区和幸存1区互换名字,即幸存0区变成幸存1区,幸存1区变成幸存0区;当有对象在这样一次次Minor GC下存活了15次(默认)的时候,这个对象就会被放入老年区
4、当幸存1区的空间大小小于幸存0区和伊甸园区幸存下来的对象大小时会被放入老年代。若是老年代也放不下,则会触发Full GC
5、当新生区经过Minor GC后,老年区里面已经没有空间可以容纳下新生区存活下来的对象时,会触发一次Major/Full GC,回收用不到的对象,回收后还是没有内存的情况下,则会抛出OOM(Out Of Memery)
4.2、永久代(元空间)
注意:永久代(jdk1.8后变为元空间metaspace),也叫方法区,实际上,无论是永久代还是元空间,它们都是对方法区的实现,存储不在虚拟机中,而是存在本地,所以一般叫“非堆”
总结:JDK1.8 :
hotspot移除了永久代,用元空间(Metaspace) 取而代之。这时候,字符串常量池在堆里面,运行时常量池在方法区
逻辑上存在,物理上不存在
当出现了OOM故障,该如何排错
- Debug代码,一行行分析
- 使用内存快照分析工具,如MAT、Jprofiler
MAT、Jprofiler的作用:
- 分析Dump的内存文件,快速定位内存泄露
- 获得堆中的数据
- 获得大的对象
4.3、如何判断一个对象已经死亡
引用计数法
这个方法比较简单,就是给对象添加一个引用计数器,这个对象每被引用一次,计数器+1,每释放一次,引用-1,当计数器为0时可以回收。但是会出现循环引用的问题
可达性分析算法
通过GC Root对象作为起点,基于对象引用关系,向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,则证明对象可以进行回收
在Java中,可作为GC Root的对象有以下几个:
- 虚拟机栈中引用的对象
- 方法区中,类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI引用的对象(即一般锁的Native方法)
Object1、Object2、Object3、Object4与GC Root之间有关系,则存活,Object5、Object6、Object7、Object8没有与任何GC Root相连,则表示可以回收
4.4、垃圾回收算法
1、复制算法
将可用内存分为大小相等的两块,每次只是用其中一块,当空间满了之后会将存活的对象复制到另外一块上,再清理掉前一块。JVM中,一般是在新生代使用这个算法。
优点:没有内存碎片问题
缺点:浪费内存空间
2、标记清除算法
包含“标记”和“清除”两个阶段,首先会标记所有需要进行回收的对象,在标记完成后统一回收所有被标记的对象
主要缺点:
- 标记和清除的效率都不高(时间)
- 容易产生内存碎片(空间)
3、标记整理算法
标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后清理掉边界以外的内存。JVM中老年代适合使用这种算法
优点:不浪费内存空间,也不会出现内存碎片
缺点:整理过程较麻烦,只适用于存活率高的区域
4、分代收集算法
根据上面的三个算法我们知道它们各自有各自的优势,各自有各自的劣势,单独使用其中的某一个无法满足JVM的垃圾回收需求,所以我们可以将它们组合起来用
新生代中的对象大多数都是瞬间对象,存活率不高,只需要复制较少对象即可完成垃圾回收工作,因此新生代采用复制算法
老年代中的对象,身经百战,存活率高,又没有额外的担保内存,因此采用标记整理算法
总结
内存效率:复制算法 > 标记清除算法 > 标记整理算法(时间)
内存整齐度:复制算法 = 标记整理算法 > 标记清除算法(内存碎片)
内存利用率: 标记清除算法 = 标记整理算法 > 复制算法(空间)
5、线程暂停、安全点、安全区
在执行可达性分析的时候,会出现在分析的过程中,对象关系引用等发生了变化,为了保证分析的准确性,就必须在分析的时候暂停所有的Java线程,Sun将这一事件称作“Stop The World”;
只有到达某些点才可以进行GC操作,这些点称作安全点(Safepoint),比如,循环的末尾、方法临返回前/调用方法的call指令后、可能跑异常的位置等。
安全区(Safe region)是一块区域,在该区域中引用都不会被修改。比如,当线程进入到安全区的时候会标识自己进入了安全区,等它被唤醒准备离开时,先检查GC是否完成,如果完成则可以离开,否则就在安全区等待。
4.5、垃圾收集器
1、垃圾收集器分类
收集器之间的绿线表示可以搭配使用
例如,如果使用新生代的Parallel Scavenge收集器的话,可以搭配老年代的serial Old收集器或者Parallel Old收集器使用
该图来自https://www.bilibili.com/video/BV1nR4y1W7Vp?spm_id_from=333.337.search-card.all.click
2、Serial、Serial Old 串行收集器
这两个收集器的方法几乎就是一样的,由于是串行收集器,因此只有一条GC线程
当所有的线程都进入安全区后,GC线程才会进行可达性分析,垃圾回收等操作,之后应用进程会重新启动
3、ParNew、Parallel Scavenge、Parallel Old 并行收集器
这几个收集器的原理相差不多,与串行收集器的原理也差不多,只是这几个是并行收集器,也就是不只一条GC线程同时运行,这样的话效率比串行收集器的效率高
也是Java8使用的收集器
4、CMS(Concurrent Mark and Sweep)收集器
CMS:并发的标记和清理收集器
CMS收集器是第一款真正实现了并发收集的老年代收集器,采用多线程并发以及标记-清除算法来实现垃圾回收,CMS只会在初始标记和重新标记的时候挂起线程,造成一定的应用停顿(STW),在其他阶段GC线程与应用线程并发交替执行,不必挂起线程,所以并不会造成应用的停顿。CMS可以最大程度的减少因垃圾回收而造成的应用停顿的时间
5、G1(Garbage-First)收集器
从前面的学习可以知道,各个分区是连续的,但是如果我们使用了G1收集器的话,它会改变我们的内存布局,如下,各个区域是打散分开的,为了做价值分析,价值高的回收,价值低的不管
价值分析:死亡的对象比较多,价值就高
G1 内存布局:
G1垃圾回收步骤:
当垃圾回收停顿时间过长的时候,G1这次就不会去回收价值低的内存块,而是让应用线程先执行,下次进行回收
4.6、JVM内存调优参数
查看:
-XX:+PrintGCDetails
: 表示输出GC的详细情况
-XX:+PrintGCDateStamps
: 指定输出GC时的时间格式,比-XX:+PrintGCTimeStamps
可读性更高
-Xloggc
: 指定gc日志的存放位置
堆栈设置:
-Xss1024k
:每个线程栈的大小为1024k
-Xms1024m
:表示初始堆大小为1024m,默认是物理存储的1/64
-Xmx1024m
:表示最大堆大小为1024m,默认物理存储的1/4
-Xmn256m
:表示新生代大小为256m
-XX:NewSize
:设置新生代初始大小,应小于-Xms
的值
-XX:NewRatio=4
:老年代与新生代的比值为4/1,默认为2
-XX:SurvivorRatio
:默认8,表示一个Survivor区占用1/8的Eden内存
-XX:MetaspaceSize
:设置元空间大小
-XX:MaxMetaspaceSize
:设置最大元空间大小,默认是不受限制的,JVM Metaspace会进行动态扩展
收集器设置:
-XX:+UseSerialGC
:设置串行收集器
-XX:+UseParallelGC
:设置并行收集器
-XX:+UseParallelOldGC
:设置老年代使用并行收集器
-XX:+UseParNewGC
:设置在新生代使用并行收集器
-XX:+UseConcMarkSweepGC
:设置CMS并行老年代收集器
-XX:+UseG1GC
:设置G1收集器
-XX:+UseParallelGCThreads
:设置用于垃圾回收的线程数
并行收集器设置:
-XX:ParallelGCThreads
:设置并行收集器手机是使用的CPU数。并行手机线程数。
-XX:MaxGCPauseMillis
:设置并行收集最大暂停时间
-XX:GCTimeRatio
:社会组垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)