提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
JVM是java语言跨平台的基础,java跨平台但是jvm不跨平台。理解jvm需要先了解jvm内存结构以及对象的生命周期(加载到回收)。
如下图:

线程独占:虚拟机栈,本地方法栈和程序计数器
线程共享:方法区,堆
jvm每调用一个方法,需要在虚拟机栈开辟一个空间用于执行,也就是栈帧,这个动作叫做压栈。为了执行方法,每个栈帧又分为三部分,分别是局部变量表、操作数栈和常量池指针。在方法执行完成后,这个栈帧就会弹出回收,这个动作叫做出栈。
虚拟机栈是用来运行java方法的,除了运行java方法外,还需要有本地方法栈来执行C方法。
保证了并发操作下线程切换能找到上次执行的指令位置。
放弃永久代使用本地内存是为了灵活使用系统内存空间。
这里还有一点需要注意,方法区存储的是常量、静态变量以及即时编译内容,静态方法是保存在class对象尾部的,也就是保存在堆中,而不是方法区。
堆是jvm储存实例化对象的地方,根据特性分为年轻代和老年代。
关于堆的数据结构这里为什么要划分需要从下面三点分析:
如下图:java的类加载过程主要步骤: 加载>连接>初始化, 其中连接又可以分为三个步骤:验证>准备>解析。

a. Bootstrap虽然作为最上层类加载器,在JDK中实际上是空实现,并没有实现ClassLoad
b. ExtenSion类加载器用于加载 jre/lib/* 下的包或类
c. Application类加载器用于加载classpath指定路径下的包或类
d. Custom自定义类用于扩展自定义加载,例如tomcat、jboss会根据j2ee规范实现自定义类加载器
类的加载器选择顺序自上向下选择,当前一个类进行加载时,会先将其委派给这个加载类的父类加载器执行,同理父类加载器会继续委派给父类,直到对顶级父类加载器,再从顶级父类加载器开始判断是否能够执行加载,如果可以,就由父类进行类加载,不行则返还给下一级类加载器,逐级判断,直到符合加载的加载器或最后一层类加载器。
a. 验证:验证字节码是否正确,Java语法使用是否正确
b. 准备:提前为静态变量内存分配和成员变量赋值默认值
c. 解析:将符号引用解析成为实际引用(例如方法引用)
分析初始化步骤,需要分析在实例化对象时有哪些动作是自动执行的,这里包括使用final修饰的常量成员变量赋值,静态成员变量和普通成员变量在声明时有直接赋值操作,静态代码块以及构造方法。
在java环境中,需要一个规则来确定操作与操作之间的先后执行顺序,这个判断规则就是Happens-Before规则。分别有以下几条规则:
特点:速度快,空间利用率比较低。
过程:执行的时候需要额外的空间进行复制,浪费一定的空间。
适用场景:因为年轻代的特点,即需要频繁创建和回收,以及每次回收后保留的数据比较少,所以复制算法适合使用于年轻代GC。
对应垃圾回收器(都是年轻代回收器):Serial(单线程)、Parallel Scavenge(多线程)、ParNew(多线程)
特点:响应速度快,但是会导致磁盘碎片化,在大对象时容易出现内存溢出。
过程:先全盘扫描标记需要回收的对象,再将标记对象回收。扫描的时候不影响系统并发响应,在回收时只需要根据标记直接回收目标对象。
适用场景:
对应垃圾回收去:CMS
特点:速度较慢,最大化利用空间。
过程:先全盘扫描标记需要回收的对象,再将标记对象回收。扫描的时候不影响系统并发响应,在回收时不只需要根据标记直接回收目标对象还需要移动后面对象位置重新整理磁盘空间。
适用场景:因为老年代的特点,即创建回收的频率不高,每次回收需要整理的保证空间利用率,所以标记-整理算法适用于老年代GC。
对应垃圾回收器:Serial Old(单线程)、Parallel Old(多线程)
不同分代的垃圾回收器以及组合方式如下图:

年轻代收集器(都是复制算法):Serial(单线程)、Parallel Scavenge、ParNew
老年代收集器:Serial Old(单线程,标记-整理)、Parallel Old(多线程,标记整理)、CMS(标记-清除)
组合收集器:G1
分析垃圾回收器除了使用算法和作用域以外,还需要根据一下几点分析:
Serial和Serial Old是最早版本的收集器,使用的是单线程回收方式。特点是:简单,多核环境下依然只能使用单线程回收,吞吐量低。但是如果是在单核环境下,使用Serial收集器由于使用简单且不需要切换线程交互,反而效率最高。
除了使用多线程收集,其它部分与Serial一模一样,在进行收集时采用多线程并发方式收集,更大限度上提高了cpu的使用率,减少了用户线程等待时间,提高了吞吐量。
Parallel Scavenge也是使用多线程并发方式收集,但是与ParNew最大的区别是Parallel Scavenge提供了GC自适应调节策略,可以配置灵活启动收集器的时机。
Parallel Old是Parallel Scavenge的老年代实现版本。
CMS是作用于老年代的标记-清除算法垃圾回收器。收集器的主要目标是实现最短的服务停顿时间,垃圾回收时与用户线程并发执行。
G1垃圾回收器采样并行并发和分块方式管理。可以充分利用多cpu、多核、大内存环境下的硬件优势。
程序在申请内存时,剩余内存空间不够分配就会导致内存溢出。
例如栈溢出、堆内存溢出、方法区内存溢出等。
内存中有一部分空间已经被使用了,但是程序中并没有这个空间的引用,也没办法对这部分内存空间进行整理,从逻辑理解上分析就是这部分内存空间消失了。
java出现内存泄漏场景:长生命周期对象持有短生命周期对象引用。
栈溢出(StackOverflowError):一般是代码问题,例如使用了递归算法却没有写递归终止条件或者设置递归深度限制。
堆溢出(OutOfMemoryError:java heap space):出现这个问题,有可能是内存泄漏,也有可能是内存溢出。内存溢出需要判断代码中是否写了错误的循环新建了许多对象或者创建了过大的实例对象。
方法区溢出(OutOfMemoryError: PermGen space):方法区主要储存的是常量池、静态变量和静态方法、热部署即使编译信息。所以方法区溢出有可能是常量池溢出或者热部署即使编译信息没有及时回收导致。