写在前面:基于我的的知识对jvm进行白话理解,有不对的地方欢迎留言讨论。java
1、JVM知识点的3条主线算法
3条主线都是围绕JVM的内存模型展开,咱们把JVM的内存模型和数据库进行横向对比来理解基本概念。sql
2、JVM内存模型图数据库
基本内存模型安全
核心内存模型性能优化
(一)JVM内存模型重点数据结构
(二)白话理解概念多线程
咱们尝试经过与数据库横向类比了解JVM的内存模型中4个最主要的区域,架构
为何说常量池是另类空间呢,由于JAVA是面向对象的,理论上处理的全部事情都是类对象。而后Java语言在真实设计上须要处理的数据有两类,1.基本类型,2.java定义的类,基于这个设计要求,因此jvm也作了相应的优化处理并发
1.若是是基本类型的数据,保存在常量池中。基本类型对应的如String、int、long、double、float、short、byte、boolean、char
2.若是是类对应的实例对象引用,保存在堆中.
这个常量池还有一个另类的地方,就是jdk1.6是定义在方法区的,jdk1.七、1.8之后是定义在堆空间的,为何会这样的,这是由于在jvm加载class文件处理常量的时候就用到了常量池,因此当时认为他们是一块儿的,其实咱们真实分析方法区的核心功能就是用来映射class文件到内存的数据模型,就是用来存储元数据的;而堆就是用来存全局变量的,不管他是基本类型变量仍是java的对象实例,这样的定位更清晰。
3、JVM的类加载机制
类加载过程图
虚拟机类加载双亲委派顺序图
(一)JVM类加载机制重点
BootstrapClassLoader启动类加载器:加载系统环境变量下JAVA_HOME/lib目录下的类库。
ExtClassLoader扩展类加载器:加载JAVA_HOME/lib/ext目录下的类库。
AppClassLoader应用程序类加载器(系统类加载器):加载用户类路径Class_Path指定的类库。
自定义类加载器:若是须要自定义加载时的规则(好比:指定类的字节流来源、动态加载时性能优化等),能够本身实现类加载器。
(二)白话理解概念
咱们依然尝试经过与数据库横向类比了解JVM的类加载机制JVM的类加载相似于咱们执行建立表的sql脚本,最终的结果是在数据库中完成建立元数据表、表的元数据记录、业务表、业务表记录。类加载机制是与内存模型关系最紧密:就是把class文件(实际工做中主要存在jar包中)加载进内存、而且把对象实例化。
加载:把class文件读取到内存中;
准备、验证、解析:就是把class文件的数据结构在内存中映射一份,这个时候涉及到若是是静态常量就要给他初始化,若是是基本类型就要给他一个默认值,后面在理论部分详细讲解。
初始化:这个时候须要首先把类的静态变量初始化,而后把成员变量初始化,而后构造,把对象放到堆中。初始化一个类的内部成员顺序是核心,后面在理论部分详细讲解。
双亲委派就是儿子永远相信父亲,父亲永远相信爷爷。若是父亲和爷爷都没有提供方法,那就相信本身周边。
JVM的双亲委派模型:就是jvm永远认为jdk定义的类是最可信的。为何这么说呢,首先咱们要知道JVM的classloader有3个等级,BootstrapClassLoader、ExtClassLoader、AppClassLoader。
AppClassLoader是咱们程序启动的基本类加载器,他负责class文件的加载。若是咱们程序须要加载一个类,首先会请求AppClassLoader干活,AppClassLoader他会先问他的领导ExtClassLoader里面有没有这个类,ExtClassLoader会继续问他的领导BootstrapClassLoader里面是否有这个类,若是BootstrapClassLoader里面有就直接用了,若是没有就告诉ExtClassLoader你本身去找吧,若是ExtClassLoader里面也没有那就告诉AppClassLoader你本身去搞定吧。
4、JVM的垃圾回收机制
JVMHeap区域(年轻代、老年代)和方法区(永久代)结构图
(一)JVM垃圾回收机制重点
Mark-Sweep(标记-清除)算法
标记-清除算法分为两个阶段:标记阶段和清除阶段,标记阶段的任务是标记出全部须要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。有碎片问题。
Copying(复制)算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。有多花费内存空间问题。
Mark-Compact(标记-整理)算法
算法标记阶段和Mark-Sweep同样,可是在完成标记以后,它不是直接清理可回收对象,而是将存活对象都向一端移动,而后清理掉端边界之外的内存
Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不一样的区域。通常状况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特色是每次垃圾收集时只有少许对象须要被回收,而新生代的特色是每次垃圾回收时都有大量的对象须要被回收,那么就能够根据不一样代的特色采起最适合的收集算法。
目前大部分垃圾收集器对于新生代都采起Copying算法,由于新生代中每次垃圾回收都要回收大部分对象,也就是说须要复制的操做次数较少,可是实际中并非按照1:1的比例来划分新生代的空间的,通常来讲是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另外一块Survivor空间中,而后清理掉Eden和刚才使用过的Survivor空间。
而因为老年代的特色是每次回收都只回收少许对象,通常使用的是Mark-Compact算法。
Serial/Serial Old:
Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,而且在它进行垃圾收集时,必须暂停全部用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优势是实现简单高效,可是缺点是会给用户带来停顿。
ParNew:
ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不须要暂停其余用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不一样,它主要是为了达到一个可控的吞吐量。
Parallel Old:
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
CMS:
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法
G1:
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。所以它是一款并行与并发收集器,而且它能创建可预测的停顿时间模型。
(二)白话理解概念
咱们尝试用数据库的管理方式与JVM垃圾回收机型进行类比理解。
上面第三节讲过JVM类加载器按照咱们的要求把类加载进来,就如同执行SQL脚本进行元数据表和业务表的建立,接下来程序就开始工做,程序的这个工做过程就相似与拼命的建立业务表,建立临时表,删除临时表,向业务表中写数据记录。
这里就有一个问题,若是业务表太多了,数据库就要爆掉了,怎么办,这就有了JVM的垃圾回收机制,业务表太多了就要干掉,这里咱们知道了原来垃圾回收机制是用来管理业务表的,也就是JVM的堆空间的。
JVM的垃圾回收主要是管理堆空间,由于用户程序在不停的建立对象,销毁对象,防止堆内存空间不足,就要垃圾回收。
JVM都回收哪些空间呢,目前主流的就是分代垃圾回收机制,把表空间的业务表分红两类,刚建立的表就年轻表明,长久使用的叫老年表明,年轻代的表若是用一次就不用了直接干掉,这就youngGC,老年代的表若是长时间不适用了也要干掉,这就叫oldGC。若是年轻代里面的表常用,就要从新归类把他分到年老表明空间中。由于JVM在垃圾回收的时候是要中止程序运行的,因此咱们要尽可能减小老年代的GC。
5、JVM架构图
6、JVM知识图谱
三种状况:
java7以前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时能够设置一个固定值,不可变;
java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并无彻底移除,譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics)转移到了Java heap。
java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
Native memory:本地内存,也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。
知乎上:R大神解答:
Oracle JDK7 / OpenJDK 7的HotSpot VM是把Symbol的存储从PermGen移动到了native memory,而且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内)。
“常量池”若是说的是SymbolTable / StringTable,这俩table自身本来就一直在native memory里,是它们所引用的东西在哪里更有意思。上面说了,7是把SymbolTable引用的Symbol移动到了native memory,而StringTable引用的java.lang.String实例则从PermGen移动到了普通Java heap。
传送门:jdk8以后永久代去哪了?
因和这篇文章说的相同(R说的SymbolTable 即为符号引用,StringTable即字符串常量),故认为此理解正确,即 java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并无彻底移除。譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics)转移到了Java heap。
为何移除永久代?
一、字符串存在永久代中,容易出现性能问题和内存溢出。
二、永久代大小不容易肯定,PermSize指定过小容易形成永久代OOM
三、永久代会为 GC 带来没必要要的复杂度,而且回收效率偏低。
四、Oracle 可能会将HotSpot 与 JRockit 合二为一。
在JDK1.7中, 已经把本来放在永久代的字符串常量池移出, 放在堆中. 为何这样作呢?
由于使用永久代来实现方法区不是个好主意, 很容易遇到内存溢出的问题. 咱们一般使用PermSize和MaxPermSize设置永久代的大小, 这个大小就决定了永久代的上限, 可是咱们不是老是知道应该设置为多大的, 若是使用默认值容易遇到OOM错误。
类的元数据, 字符串池, 类的静态变量将会从永久代移除, 放入Java heap或者native memory。其中建议JVM的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中. 这样能够加载多少类的元数据就不在由MaxPermSize控制, 而由系统的实际可用空间来控制.
为何这么作呢? 减小OOM只是表因, 更深层的缘由仍是要合并HotSpot和JRockit的代码, JRockit历来没有一个叫永久代的东西, 可是运行良好, 也不须要开发运维人员设置这么一个永久代的大小。固然不用担忧运行性能问题了, 在覆盖到的测试中, 程序启动和运行速度下降不超过1%, 可是这一点性能损失换来了更大的安全保障。