概念
JAVA运行环境(Java代码需要编译为字节码,jvm就是字节码运行环境)
好处:
1 一次编译到处运行(运行于操作系统之上,屏蔽了操作系统之间的差异)
2 自动内存管理,垃圾回收机制(C语言需要自己管理内存,使用不当会造成内存泄漏。JVM减少了程序员出错的机会)
JVM组成
1 什么是程序计数器
如果多线程并发执行,线程1执行到第十行,时间片被线程2夺走,线程1需要记录当前执行的位置,以便将来重新执行时不用从头执行,而是接着之前执行的位置开始执行。
所以其保存的是字节码的行号,用于记录正在执行的字节码指令的地址。
2 什么是Java的堆
是一个线程共享的区域,用于存储对象或数组。当堆中没有内存空间可以分配给实例时,抛出OOM异常。
堆分为年轻代和老年代。
年轻代分为 Eden S0 S1
一个对象一开始在Eden区域 ,如果对象在垃圾回收后还存活,就会进入S0 S1区域,如果数次后还存活,将移动到老年代
元空间:保存类信息,静态变量,常量,编译后的代码
在java8后原本堆中的方法区移动到本地内存,变成元空间
3 什么是虚拟机栈
每个线程运行时所需要的内存就是虚拟机栈,先进后出 。
类加载器
1 什么是类加载器?
jvm只会运行类加载器,类加载器的作用就是将字节码文件加载到JVM中
从而让Java程序启动起来
2 有哪些类加载器
启动类加载器:加载JAVA_HOME/jre/lib目录下的库
扩展类加载器:主要加载JAVA_HOME/jre/ext目录中的类
应用类加载器: 用于加载classPath下的类,一般我们自己写的类就在这里加载 自定义类加载器:实现自定义类加载规则
双亲委派模型:
加载某一个类,先委托上一级加载器进行加载,如果上一级也有上级,则继续向上委托,如果上级没有加载该类,子加载器将尝试加载该类
例如:自己编写一个Student类,首先在应用类加载器这里先不加载,而是委托上级扩展类加载器-》启动类加载器 加载,由于Student是我们自己写的,不在lib或ext目录下。所以这两种都没有加载,则应用类加载器自己加载。
但是对于String类而言,同样重复以上步骤,不同的是String在 jre/lib目录下,
所以会被启动类加载器加载
为什么采用双亲委派机制呢?
1 避免某一个类被重复加载,当父类被加载后无需重复加载,保证唯一性
2 为了安全保证类库API不被修改
举例:
java
public class String{
public static void main (String[]args){
System.out.println("123")
}
}
以上这段代码会报错:找不到main方法
原因是String类在jre核心库中已经存在,所以会被 启动类加载器,而其中的String类没有main方法。这样可以防止恶意篡改核心API库。
类装载的过程:
概念:类加载到虚拟机中开始,直到卸载。整个周期包括了加载、验证、准备、解析、初始化、使用和卸载7个阶段。
垃圾回收
对象什么时候会被垃圾回收。
为什么要垃圾回收?
主要回收堆中的对象。因为堆中存放对象,如果一直不回收,那么内存早晚会被耗尽的。
什么对象会被回收呢?
如果一个或多个对象没有任何引用指向他了,那么这个对象就是垃圾,需要被回收。
有两种办法可用定位垃圾
1 引用计数法
一个对象被引用一次,对象头会递增一次引用次数。如果引用次数是0,则代表可被回收。
造成情况下,一个 String a = new String("ttt") 这种代码会在引用次数加一,
当 String a = null,时,引用次数变成0 , 此时对象可回收
但是在互相引用的情况下,引用次数是2,此时将变量指向null,引用次数变成1,也就是说
现在这两个相互引用的对象没有被使用,但是仍然不会被回收,后续可能引发内存泄漏
综上一般不使用这种方法定位。
2 可达性分析算法
这是java虚拟机采用的方式,看是否能沿着GC Root为起点的引用链找到该对象,找不到,代表可用被回收。
哪些对象可以作为GC Root呢?
1 虚拟机栈中引用的对象
2 方法区静态属性引用的对象
3 方法区常量引用的对象
4 本地方法栈中引用的对象
垃圾回收算法有哪些
1 标记清除
通过可达性分析算法得出的垃圾进行标记,对标记为可回收的内容进行垃圾回收
优点:标记和清除速度快
缺点:出现内存碎片化,内存不连贯
由于数组存储需要连续的内存空间,所以如果使用这种办法
可能无法存储新的对象
2 标记整理
他和标记清除类似。但是他在清除后会进行整理,将存活的对象向一端移动。这样就避免了内存碎片问题。
优点:避免标记清除那种可能无法存储新对象的情况
缺点:由于要移动碎片,所以效率受到影响。
很多老年代垃圾回收区都使用该算法。
3 复制
将内存分为A,B两个区域。在A中进行标记,然后将存活对象复制到B中,在复制过程中自然完成了整理。再整体清除A区域。
优点:
在垃圾多的情况下,效率高。
清理后,内存无碎片。
缺点:分配的2快内存空间,同一时刻只能用一半,
内存使用效率低。
分代垃圾回收
Java中 堆被分为新生代、老年代(1:2)两份
在新生代中,分为三个区域
伊甸园区Eden: 新生对象都分配到这里
幸存者区域(分成from 和 to)
eden:from:to 为 8:1:1
逻辑:
新创建的对象都会先分配到eden区域
当eden区域内存不足,标记eden区域和from区域的存活对象
将存活对象使用复制算法复制到to中,复制完毕后伊甸园和from内存都得到释放
经过一段时间后,伊甸园又出现不足,标记eden和to存活的对象,将存活的对象复制到from区域
当幸存者熬过几次回收(最多15次),晋升到老年代(幸存者内存不足或大对象会导致提前晋升)
MinorGC :发生在新生代的垃圾回收,暂停时间短
Mixed GC:新生代+老年代部分区域的垃圾回收,G1收集器持有
FullGC:新生代+老年代完整垃圾回收,暂停时间长,应该避免(一般发生在新生代和老年代内存严重不足时)
暂停时间(STW):暂停所有应用程序线程,应该竭力避免
有哪些垃圾回收器
1 串行垃圾回收器 (适用于堆内存小。适合个人电脑)
Serial 作用于新生代,采用复制算法
Serial Old作用于老年代,采用标记-整理算法
垃圾回收时,只有一个线程工作,并且java应用中所有线程都要暂停,等待垃圾回收完成。
2 并行垃圾回收器(JDK8默认使用该垃圾回收器)
Paraller New作用于新生代,采用复制算法
Parallel Old作用于老年代 采用标记整理算法
垃圾回收时,多个线程工作来完成垃圾回收,并且java应用中所有线程都要暂停,等待垃圾回收完成。
3 CMS(并发)垃圾回收器
使用标记清除算法,针对老年代进行回收。特点是停顿时间最短,并且垃圾回收时,应用仍然可用运行。
G1 垃圾回收器
应用于新生代和老年代中,jdk9后默认使用G1
1 划分为多个区域,每个区域都可以充当eden,survivor,
old,humongous,其中humongous专为大对象准备
采用复制算法
响应时间和吞吐量兼顾
垃圾回收分为三个阶段 :新生代回收,并发标记,混合收集
如果并发失败(回收速度赶不上创建对象速度)出发fullGC
流程整理:
1 整个堆空间划分为大大小小一致的空间,每个区域都能作为伊甸园,幸存者,老年代
2 刚开始这些区域都是空闲的,当创建对象时,就会挑出一些区域作为伊甸园区域。
3 当伊甸园数量够了之后(达到6-8%时)触发一次伊甸园的垃圾回收,采用的也是复制算法,幸存对象放入幸存者区
4 当一段时间后伊甸园内存又不够了。会把伊甸园中幸存者对象和上一次放入幸存者区域的对象一起复制到新的幸存者区域中。此时比较老的对象跻身老年代中。
5 此时伊甸园区和上一次的幸存者区域可释放。
6 当老年代达到45%时触发标记,这时无需暂停用户线程。并发标记老年代中存活的对象。进入混合收集阶段