虚拟机把描述类的数据从Class文件加载到内存,并且对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型;
工作原理:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式;
优势
避免类的重复加载;
记录当前正在执行的虚拟机字节码的指令地址;
如果正在执行的是本地方法则为空;
每个Java方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法的调用直至完成的过程中,对应一个栈帧在Java虚拟机中入栈和出栈的过程;
该区域可能抛出以下异常:
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
所有对象在这里分配内存,是垃圾回收的主要区域(“GC堆”),被所有线程所共享;
从内存回收的角度,现在的垃圾收集器都是采用分代收集算法,主要是针对不同类型的对象采取不同的垃圾回收算法,可以将堆分为两块:
其中新生代按照8:1:1的比例分为Eden区、from Survivor、to Survivor三个区域,
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常,可以通过 -Xms和-Xmx这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设定初始值,第二个参数设定最大值;
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
在Java1.8开始,移除永久代,并且把方法区移至元空间,位于本地内存中,而不是虚拟机内存中;
运行时常量池是方法区的一部分,Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
为对象添加一个引用计数器,当对象增加一个引用计数器加1,引用失效时计数器减1,引用计数为0的对象可以被回收;
缺点:无法解决对象直接的循环引用问题;
以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。Java虚拟机中使用该算法来判断对象是否可以回收,GCRoots一般包含以下内容:
单弱多强
被强引用关联的对象不会被回收,使用new一个新对象的方式来创建强引用:
Object obj = new Object();
被软引用的对象只有在内存不够的情况下才会被回收,使用SoftReference类来创建软引用。
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。使用WeakReference 类来创建弱引用:
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知;
使用PhantomReference来创建虚引用:
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj,null);
obj = null;
首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象;
不足:
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;不会产生内存碎片;
不足:
将可用内存分为大小相等的两块,每次只使用其中的一块,当其中一块内存用完之后,就将存活的对象复制到另外一块上,然后再把已使用的内存空间一次性清理掉,使得每次都可以对整个半区进行内存回收
在商业虚拟机中并不需要按照1:1的比例进行划分内存空间,将内存分为一个较大的Eden和两块较小的Survior空间;当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间;
HotSpot虚拟机默认Eden和Sruvivor的大小比例是8:1;
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
一般将堆分为新生代和老年代。
CMS收集器是牺牲吞吐量为代价来获取最短停顿时间为目标的垃圾回收器,
CMS收集器是基于“标记——清除”算法,整个过程分为四个步骤:
优点:并发收集、低停顿;
缺点:
作为Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:
Parallel Scavenge 收集器的老年代版本。
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
单线程收集器,只会使用一个线程进行垃圾回收工作;
优点:简单高效,没有线程交互的开销,拥有最高的单线程收集效率;
在内存不大的场景下,收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这些停顿时间是可以接受的;
Serial 收集器的多线程版本;
Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用;
与 ParNew 一样是多线程收集器
达到可控制的吞吐量,吞吐量是指CPU用于运行用户程序的时间占总时间的比值;
高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算程序;
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
G1是一种兼顾吞吐量和停顿时间的GC实现,是JDK9以后的默认GC选项;一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。
通过引入Region的概念,将原先的一整块内存空间划分成多个小空间,使得每个小空间可以单独进行垃圾回收;
步骤分为四步:
空间整合:
从整体来看是基于"标记-整理"算法实现的收集器,从局部上来看是基于复制算法实现的,这意味着运行期间不会产生内存空间碎片;
最大的特点是引入了分区的思路,弱化了分化的概念;
每个分区被标记了E、S、O和H,H表示这些Region中存储的是巨型对象,新建对象大小超过Region大小一半时,直接在歆的一个或者多个连续分区中分配,并标记为H;
大多数情况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组;
经常出现大对象会提前出发垃圾收集来获取足够的连续空间分配给大对象;
-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在Eden和Survivor之间的大量内存复制;
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中;
-XX:MaxTenuringThreshold 用来定义年龄的阈值;
虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
在发起Minor GC之前,虚拟机先检查老年代中最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么Minor GC可以确认是安全的;
对于MinorGC,其触发条件十分简单,当Eden空间满时,就将触发一次Minor GC,而Full GC相对复杂,条件如下:
只是建议虚拟机执行 FullGC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
老年代空间不租的常见问题是大对象直接进入老年代、长期存活的对象进入老年代等;
应当避免创建过大的对象和数组,除此之外还可以通过-Xmn虚拟机参数来调大新生代的大小,让对象尽量在新生代中被回收掉,不进入老年代;还可以通过-XX:MaxTenuringThreshold调大对象进入老年代的年龄,让对象在新生代中多存活一段时间;
使用复制算法的 MinorGC需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC;
执行CMS GC的过程中同时有对象要放入老年代中,而此时的老年代空间不租(可能是GC过程中浮动垃圾过多导致暂时性地空间不足),便会报Concurrent Mode Failure错误,并且触发Full GC;
可以通过一些性能检测工具,如JProfiler等工具查找内存泄漏;
内存溢出和内存泄漏的区别:
https://blog.csdn.net/WantFlyDaCheng/article/details/81808064
以Student s = new Student()为例: