虚拟机将所管理的内存分为如下几个部分:html
在Java8中,永久代已经被移除,被一个称为“元数据区”(Metaspace)的区域所取代。元空间的本质和永久代相似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。java
在 Java 中,堆被划分红两个不一样的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。 程序员
(1)大多状况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;(2)大对象(须要大量连续内存空间的对象)直接进入老年代;(3)长期存活对象将进入老年代。算法
Minor GC(新生代GC):在新生代的垃圾收集动做,比较频繁,回收速度较快,采用复制算法。数组
Full GC(老年代GC,Major GC):发生在老年代的GC,速度较慢,采用标记-清除算法等。缓存
回收过程:多线程
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在通过一次 Minor GC 后,若是对象还存活,而且可以被另一块 Survivor 区域所容纳(上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另一块 Survivor 区域 ( 即 to 区域 ) 中,而后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),而且将这些对象的年龄设置为1,之后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,能够经过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。并发
注:有的资料还有持久代的概念,存储类信息、常量、静态变量、类方法。app
自动内存管理解决的主要问题:给对象分配内存以及回收分配给对象的内存。内存分配规则并不是彻底固定,取决于当前使用的垃圾回收器组合还有虚拟机内存参数设定。如下是广泛的内存分配规则:分布式
对象的建立
(1)检查常量池是否能够定位到一个类的符号引用,检查是否被加载、解析和初始化过
(2)加载检查经过后,为新生对象分配内存(内存分配方式:指针碰撞、空闲列表)
(3)内存分配完成后,初始化零值
(4)对对象进行必要的设置,如对象是哪一个类的实例、元数据信息、对象的哈希码、
GC分代信息,这些信息存放在对象头中。
(5)执行<init>方法,把对象按照程序员的意愿进行初始化。
对象的内存布局
三块区域:对象头(对象自身运行时数据;类型指针->对象指向它的类元数据的指针,用于肯定该对象属于哪一个类的实例)、实例数据(对象真正存储的有效信息)、对齐填充(起到占位符的做用)。
对象的访问定位
经过栈上的reference数据来操做堆上的具体对象。主流的访问方式有:使用句柄、直接指针。
(1)使用句柄:划份内存做为句柄池,reference中存储对象的句柄地址,最大的好处是存储的句柄地址是稳定的,在对象移动时reference自己不须要修改。
(2)使用直接指针:reference中存储的直接就是对象对象地址,速度更快,节省了一次指针定位的时间开销。HotSpot采用。
引用计数法:经过引用计数器实现,任什么时候刻计数器为0的对象就是不可能再被使用的。实现简单,断定效率很高,但很难解决对象之间的互相循环引用问题。
可达性分析算法:经过一系列的称为“GC ROOTS”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOTS没有任何引用链相连时(图论的观点,就是从GC ROOTS到这个对象不可达),则证实对象是不可用的。
标记-清除算法:分为两个阶段,首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象。存在的两个问题:效率问题;产生大量不连续的内存碎片。
复制算法:每次只使用其中一块,当一块内存用完了,就将还存活的对象复制到另外一块上面,而后再把已使用过的内存空间一次清理掉。在存活对象很少的状况下,性能高,能解决内存碎片问题。经常使用于回收新生代。万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间,而老年代因为没有其额外内存空间进行分配担保,通常不采用复制算法。
标记-整理算法:标记过程与标记-清除算法同样,但后续步骤不是直接对可回收对象进行整清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。
分代收集算法:根据各个年代的特色采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存活,那就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集。而老年代中由于对象存活率较高、没有额外空间对它进行分配担保,就必须使用后面两种算法来进行回收。
Serial垃圾收集器(单线程、复制算法):Serial 是一个单线程的收集器,它不但只会使用一个CPU或一条线程去完成垃圾收集工 做,而且在进行垃圾收集的同时,必须暂停其余全部的工做线程,直到垃圾收集结束。是java 虚拟机运行在Client模式下默认的新生代垃圾收集器。
ParNew垃圾收集器(Serial+多线程),Serial收集器的多线程版本,使用复制算法,是不少java虚拟机运行在Server模式下新生代的默认垃圾收集器。
Parallel Scavenge 收集器(多线程复制算法、高效)
SerialOld收集器(单线程标记整理算法)
ParallelOld收集器(多线程标记整理算法)
CMS收集器(多线程标记清除算法):一种以获取最短回收停顿时间为目标的收集器,在重视服务的响应速度、注重用户体验的场景下很是适用。基于“标记-清除”算法,特色是:并发收集、低停顿。具体分为四个阶段:初始标记->并发标记->从新标记->并发清除。
初始标记和从新标记两个步骤须要STW,sun官方文档称做并发低停顿收集器,主要有如下局限性:(1)对CPU资源很是敏感,在并发阶段,虽然不会致使用户线程停顿,可是会占用一部分线程或者说CPU资源致使应用程序变慢,总吞吐量下降,默认启动的回收线程数是(CPU数量+3)/4。(2)没法处理浮动垃圾,运行期间预留的内存没法知足程序须要,出现Concurrent Mode Fail, 可能致使另外一次Full GC的发发生。(3)内存碎片,能够牺牲停顿时间开启内存碎片的合并整理过程。
G1收集器:面向服务端应用的垃圾收集器,可能代替CMS收集器,特色:并行与并发,分代收集(能够不须要其余收集器配合就能独立管理整个堆,采用不一样的方式分代收集),空间整合(总体上是“标记-整理”算法实现,局部上是“复制”算法实现,不会产生内存空间碎片),能够预测的停顿时间(创建可预测的停顿时间模型)。具体能够分为四个阶段:初始标记->并发标记->最终标记->筛选回收。
引入分区的思路,弱化了分代的概念,使用G1垃圾收集器时,Java堆的内存布局与其余收集器有很大的差异,它将整个堆划分对多个大小相等的独立区域(region),新生代、老年代的概念依然保留,再也不是物理隔离而是一部分region(不须要连续)的集合,回收时则以分区为单位进行回收。
G1的设计原则是"首先收集尽量多的垃圾"。所以,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是使用启发式算法,跟踪各个region里面的垃圾堆积的价值大小(回收所得到的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,优先回收价值最大的region(这是garbage first名字的由来)。同时G1能够根据用户设置的暂停时间目标自动调全年轻代和总堆大小。
G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即便堆内存很大时,也能够限制收集范围,从而下降停顿。
Minor GC(新生代GC):在新生代的垃圾收集动做,比较频繁,回收速度较快,采用复制算法。
Full GC(老年代GC,Major GC):发生在老年代的GC,速度较慢,采用标记-清除算法等。
jmap:用于生成堆转储快照(heap dump或dump文件),还能够查询finalize队列,java堆和永久代的详细信息,如空间使用率、当前使用的收集器等。
jstack:java堆栈跟踪工具,生成虚拟机当前时刻的线程快照,主要目的在于定位线程长时间停顿的缘由,如出现死锁、死循环、请求外部资源致使的长时间等待。线程出现停顿的时候经过jstack查看各个线程的调用堆栈,就能够知道没有响应的线程到底在后台作什么或者等待什么资源。
jconsole:可视化监视、管理工具,内存监控(至关于可视化的jstat命令,用于监视受收集器管理的虚拟机内存的变化趋势),线程监控(至关于可视化得jstack命令,遇到线程停顿时能够进行监控分析)
类加载或者初始化的三个步骤:加载、链接、初始化
(1)加载:将类的class文件读入内存,并为之建立一个java.lang.Class对象,类的加载由类加载器完成。
(2)链接:链接阶段负责把类的二进制数据合并到JRE中,具体又分为以下三个阶段:验证,验证阶段用于检验被加载的类是否有正确的内部结构,并和其余类协调一致;
准备,类准备阶段负责为类的类变量分配内存,并设置默认初始值。
解析,将类的二进制数据中的符号引用替换为直接引用
(3)初始化:虚拟机负责对类初始化,主要就是对类变量进行初始化。假设这个类尚未被加载和链接,则程序先加载并链接这个类,假设该类的直接父类尚未被初始化,则先初始化其直接父类,假设类中有初始化语句,则系统依次执行这些初始化语句。
BootstrapClassLoader(根类加载器)、ExtensionClassLoader(扩展类加载器)、ApplicationClassLoader(应用程序类加载器或者叫系统类加载器)
(1)根类加载器:也被称为引导(原始)类加载器,负责加载Java的核心类,并非java.lang.ClassLoader的子类,而是由JVM自身实现的。
(2)扩展类加载器,负责加载JRE的扩展目录中JAR包的类,经过这种方式,就能够为java扩展核心类之外的新功能,只要把本身开发的类打包成JAR文件,而后放入JAVA_HOME/jre/lib/ext路径便可。
(3)应用程序类加载器,负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。若是没有特别指定,用户自定义的类加载器都以系统类加载器做为父加载器。
如何建立并使用自定义的类加载器?
继承ClassLoader并重写其findClass()方法。
URLClassLoader---系统类加载器和扩展类加载器的父类(继承关系),既能够从本地文件系统获取二进制文件来加载类,也能够从远程主机获取二进制文件来加载类。
双亲委派模型
若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的父加载器都是如此,所以全部的请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈本身没法完成这个加载请求时,子加载器才会尝试本身去加载。双亲委派模型对于保证JAVA程序的稳定运做很重要。
(暂时跳过)
定义了一些规则,保证多个线程间能够高效地、正确地协同工做,关键的工做围绕多线程的原子性、可见性和有序性展开。
(1)原子性指一个操做不可被中断
(2)可见性是指一个线程修改了共享变量的值,其余线程是否能知道这个修改
(3)有序性是指程序在执行的过程当中会发生指令重排,指令重排能够保证串行语义的一致性,但不能保证多线程下的语义也一致。
*为什么进行指令重排?
尽可能少的中断流水线,提升CPU性能。
哪些指令不能进行重排?(Happen-before规则)
(1)程序顺序原则:一个线程内保证语义的串行性
(2)volatile规则:volatile变量的写先于读,保证了volatile变量的可见性
(3)锁规则:解锁(unlock)先于加锁(lock)
......(详见:《实战Java高并发程序设计》)
以上原则保证了指令重排不会破坏原有的语义结构。
强引用
在 Java 中最多见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即便该对象之后永远都不会被用到 JVM 也不会回收。所以强引用是形成 Java 内存泄漏的主要缘由之 一。
软引用
软引用须要用 SoftReference 类来实现,对于只有软引用的对象来讲,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用一般用在对内存敏感的程序中。
弱引用
弱引用须要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来讲,只要垃圾回收机制一运行,无论JVM 的内存空间是否足够,总会回收该对象占用的内存。
虚引用
虚引用须要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要做用是对象在被收集器收集时收到一个系统通知。