所谓虚拟机
通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的计算机系统
JVM的组成部分
- 类加载器(ClassLoader)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
- 本地库接口(Native Interface)
类加载机制
装载-链接(验证,准备,解析)-初始化-使用-卸载
类的生命周期
- 加载: 通过一个类的权限名来获取定义此类的二进制字节流,将二进制字节流代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类各种数据的访问入口
- 验证: 文件格式验证,原数据验证,字节码验证,符号引用验证.
- 准备: 正式为类变量分配内存病设置初始值
- 解析: 把间接引用转换为直接引用
- 初始化: 执行(),初始化类变量、静态代码块
- 使用
- 卸载
类加载方式
- 最常见方式: 本地文件系统中加载、从jar等归档文件中加载
- 动态的方式:
将 java 源文件动态编译成class - 其他方式:
网络下载、从专有数据库中加载等等
类加载器
- java 虚拟机自带的加载器包括以下几种
- 启动类加载器(BootstrapClassLoader)
- 平台类加载器(PlatformClassLoader) JDK8: 扩展类加载器(ExtensionClassLoader)
- 应用程序类加载器(ApplicationClassLoader)
- 用户自定义的加载器 :是java.lang.ClassLoader 的子类,用户可以定制类的加载方式;只不过自定义类加载器其加载的顺序是在所有系统类加载器的最后
堆的结构
- 新生代
分为 Eden,Survivor1,Survivor2 内存占比8:1:1
Eden区存储刚创建的对象,1次young GC后,剩余未回收对象放入Survivor区,实现内存重排列 - 老年代
存储young GC 15次以上的对象 和 大对象 - 内存占比(老年代: 新生代 = 2:1)
metaspace(元空间)
jdk 1.8才有, 又叫方法区,使用的是直接内存,1.7之前方法区使用的是jvm开辟的内存空间,防止OutOfMemoryError
JVM运行时数据区
- 方法区(1.8以后叫元空间): 与堆一样,是线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据.
- java堆: Java堆(java Heap)是Java虚拟机所管理的内存中最大的一块.Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存.
- 虚拟机栈: 每个方法在执行的同时都会创建一个栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息.每一个方法调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.
- 本地方法栈: 与虚拟机栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务.本地方法栈最著名的应该是System.currentTimeMillis()
- 程序计数器: 可以看作是当前线程所执行的字节码的行号指示器.
其中,方法区和堆(heap)是线程共有的.虚拟机栈,本地方法栈和程序计数器是线程私有的
判断对象是否可以被回收
- 引用计数器,为每个对象创建一个引用计数,有对象引用时计数器+1,引用被释放时计数-1.当计数器为0是就可以被回收.缺点是不能解决循环引用的问题
- 可达性分析: 从GC roots开始向下搜索,搜索所走过的路径称为引用链.当一个对象到GC roots没有任何引用链时,则证明此对象是可以被回收的.
栈溢出和内存溢出
虚拟机栈规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;
如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展),如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常
JVM主内存和工作内存
Java内存模型中规定了所有的变量都存储在主内存中.
每条线程还有自己的工作内存,线程对变量的操作都必须在工作内存中进行,而不能直接读写主内存中的变量.这里的工作内存是JMM(jvm内存模型)的一个抽象概念,也叫本地内存,其存储了该线程以读/写共享变量的副本.
JVM有哪些垃圾回收算法?
- 标记-清除算法
- 标记-整理算法
- 复制算法
- 分代算法
列举几个垃圾收集器
- Serial: 最早的单线程串行垃圾回收器
- ParNew: 是Serial的多线程版本
- CMS: 一种以获得最短停顿时间为目标的收集器,非常实用B/S系统
- Parallel Old 是Parallel 老生代版本,Parallel使用的是复制的内存回收算法,Parallel Old使用的是标记-整理的内存回收算法.
- G1: 一种兼顾吞吐量和停顿时间的GC,是JDK9 以后的默认GC选项.
CMS垃圾收集器(老年代)并发类收集器
CMS是英文Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器.对于要求服务器响应速度的应用上,这种垃圾回收器非常适合.
CMS使用的是标记-清除的算法实现的,所以在gc的时候会产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现Concurrent Mode Failure,临时CMS会采用Serial Old回收器进行垃圾清除,此时的性能将会被降低.
G1垃圾收集器
G1是一个分代的,增量的,并行与并发的标记-复制垃圾回收器.它的设计目标是为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time),同事兼顾良好的吞吐量.G1回收器和CMS比起来,有以下不同:
- G1垃圾回收器是compacting的,因此其回收得到的空间是连续的.这避免了CMS回收器因为不连续空间所造成的问题.如需要更大的堆空间,更多的floating garbage.连续空间意味着G1垃圾回收器可以不必采用空闲链表的内存分配方式,而可以直接采用bump-the-pointer的方式;
- G1回收器的内存与CMS回收器要求的内存模型有极大的不同.G1将内存划分一个个固定大小的region,每个region可以是年轻代,老年代的一个.内存的回收是以region作为基本单位的;
G1还有一个极其重要的特性:软实时(soft real-time).所谓的实时垃圾回收,是指在要求的时间内完成垃圾回收."软实时"则是指,用户可以指定垃圾回收时间的限时,G1会努力在这个时限内完成垃圾回收,但是G1并不担保每次都能在这个时限内完成垃圾回收.通过设定一个合理的目标,可以让达到90%以上的垃圾回收时间都在这个时限内.
垃圾回收过程
- 大多情况下对象在Eden分配,当Eden没有足够空间时讲发起一次Minor GC (英[ˈmaɪnə®])
- 当Eden执行Minor GC后还不足以为对象分配空间,大对象直接进入老年代,可以用参数设置大对象直接进入老年代,避免频繁Minor GC
- 如果对象在Eden出生,发生Minor GC后仍然存活,且能被Survivor容纳,年龄加1,达到一定年龄进入老年代,默认15
- 占Survivor 空间一半以上且年龄相等的对象,大于等于该年龄以上的对象直接进入老年代.
- 发生Minor GC之前会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果大于说明Minor GC安全;否则会判断是否允许担保失败,如果允许担保失败,判断老年代最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试MinorGC,否则执行FullGC
什么是Full GC
通常意义上的full gc 会回收整个堆的内存,包含老年代,新生代,metaspace等.