一、 JVM学习笔记
JVM:Java Virtual Machine
JVM总感觉那么的神秘,底层的东西,试着把一些复杂的概念,用简单的方式做一个总觉。
那么学习JVM主要是学习哪些内容。
1.运行时数据区。
2.堆模型。
3.垃圾回收算法。
4.引用分类。
JVM在开发中的位置:
1.1、运行时数据区:
- 程序计数器program Counter Register:
线程私有,当前线程。
指向当前线程正在执行的字节码指令地址。每条现成都有一个独立的程序计数器,互不影响、独立存储。字节码解释器通过改变这个计数器来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复都需要依赖它。 - 虚拟机栈 VM Stack:
线程私有,当前线程执行方法。- 每个方法都包含:局部变量表、操作数栈、动态链接(运行时多态)、出口(去哪里-正常、非正常)
- StackOverflowError,如果线程请求的栈深度大于虚拟机允许的深度时。
- OutOfMemoryError,如果虚拟机栈进行扩展无法申请到足够的内存时。
- 本地方法栈 Native Method Stack:C++、C提供的功能。
- StackOverflowError
- OutOfMemoryError
- 方法区 Method Area(永久代):类信息、常量final、静态变量static、JIT。
- OutOfMemoryError,申请不到内存时。
- 堆 Heap:所有线程共享,存放对象实例。
- 通过-Xmx 和 -Xms 控制
- OutOfMemoryError,当堆无法申请到内存时。
1.2、堆模型
年轻代Young Generation
一般都是朝生夕死的对象,大约80%以上,采用复制算法进行垃圾回收。(复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片)
年轻代分为:1个eden['i:dən]、两个survivor sə'vaɪvə。默认比例为Eden:Survivor0:Survivor1=8:1:1。survivor区的意义在于它进行筛选进入老年代的对象,减少被送到老年代对象的数量,进而减少FullGC的开销。两个survivor区主要解决内存碎片化,也是使用复制算法的前提。
新创建的的对象会分配到eden区(大对象可能会特殊)。进行MinorGC以后,在eden区中存过下来的对象会移动到Survivor的to区,在Survivor的from区的存活的对象根据年龄(一次GC涨一岁)进行判断,达到临界值(可以通过-XX:MaxTenuringThreshold来设置) 会被复制到老年代,未达到的复制到To区。经过这次GC以后eden区和From区就会被清空。这个时候From和To交换角色。此时Eden区和To区都是空的,等待下一轮GC。一直等待To区填满,就会把这些对象移动到老年代。
年轻代GC(MinorGC),非常频繁,回收速度也比较快。GC算法:复制算法。
老年代Old Generation
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。
老年代GC(Major GC/Full GC),比新生代慢10倍。GC算法:标记清楚或标记整理。
永久代(持久代)Permanent Generation:
用于存放静态文件,如今Java类、方法等。
永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Spring Hibernate 等,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。
永久代大小通过-XX:MaxPermSize=<N>进行设置。
永久带也称为方法区。
永久代在java8以后的HotSpot将被废弃,原因主要有两个:
- 由于永久代内存经常不够用或发生内存泄露,引发恼人的java.lang.OutOfMemoryError: PermGen (在Java Web开发中非常常见)。
- 移除永久代可以促进HotSpot JVM与 JRockit VM的融合,因为JRockit没有永久代。
根据上面的各种原因,永久代最终被移除,方法区移至Metaspace,字符串常量移至Java Heap。JDK 8开始把类的元数据放到本地化的堆内存(native heap)中,这一块区域就叫Metaspace,中文名叫元空间。
元空间
JDK 8开始把类的元数据放到本地化的堆内存(native heap)中,这一块区域就叫Metaspace,中文名叫元空间。
最直接的表现就是java.lang.OutOfMemoryError:
PermGen空间问题将不复存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说本地内存剩余多少,这解决了空间不足的问题。不过,让Metaspace变得无限大显然是不现实的,因此我们也要限制Metaspace的大小:使用-XX:MaxMetaspaceSize参数来指定Metaspace区域的大小。JVM默认在运行时根据需要动态地设置MaxMetaspaceSize的大小。
如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区
新增参数
- -XX:MetaspaceSize是分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark),此值为估计值。MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
- -XX:MaxMetaspaceSize是分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
- -XX:MinMetaspaceFreeRatio表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小比例,不够就会导致垃圾回收。
- -XX:MaxMetaspaceFreeRatio表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最大比例,不够就会导致垃圾回收。
1.3 垃圾回收
-
引用计数法Reference Counting
- 实现简单、效率高,适合简单场景。Flash、Python使用.
- 很难解决循环引用问题。
标记-清除法
复制算法
标记-整理算法
分代收集算法
新生代-复制算法(Scavenge)
通过Eden和两个Survivor区的复制,可避免碎片化。-
老生代-标记清楚或标记整理CMS(Concurrent MarkSweep)
- 初始标记,需要Stop The World
- 并发标记,需要Stop The World
- 重新标记
- 并发清除
当找不到足够的连续控件分配会进行一次Full GC
1.4 引用类型
- 强引用:不会被回收。
Object o=new Object(); // 强引用
- 软引用:非必须的对象。当内存足够不会被回收,当内存不足,就会被回收。可以被程序使用。使用类java.lang.ref.SoftReference定义
`
String str=new String("abc");// 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用 Browser prev = new Browser();// 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null) {
rev = (Browser) sr.get();
// 还没有被回收器回收,直接获取
} else {
prev = new Browser();
// 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
- 弱引用:非必须对象,只能生存到下一次垃圾收集发生之前。可以获取对象。
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
- 虚引用:幽灵引用、幻影引用。不会对对象生存时间构成影响,也无法通过虚引用取得一个对象的实例。唯一目的是在背收集器回收时受到一个系统通
知。