一、类加载子系统
1、类加载的过程:装载、链接、初始化,其中,链接又分为验证、准备和解析
装载:加载class文件
验证:确保字节流中包含信息符合当前虚拟机要求
准备:分配内存,设置初始值
解析:将变量从符号引用改为直接引用
初始化:执行类构造器方法
2、类加载器的分类
加载阶段使用到类加载器,类加载器分为:由C++实现的引导类加载器和由java实现的自定义加载器
自定义加载器又分为三层类加载器:拓展类加载器,应用程序类加载器和系统类加载器
①、引导类加载器:启动类加载器,将lib目录下,或-Xbootclasspasth参数指定路径存放的,java虚拟机能识别的类库加载进虚拟机;
②、拓展类加载器:将\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库加载进虚拟机;
③、系统类加载器:应用程序类加载器:负责加载用户类路径上所有的类库,可以直接在开发中使用这个类加载器;
④、自定义类加载器:用户自定义的类。
3、双亲委派机制
java虚拟机对class文件采用按需加载的方式,即在需要使用到类时,才加载对应的类到内存中,并且使用双亲委派机制的模式进行加载:
①:当一个类加载器收到了类加载的请求,不会立刻自己加载,而是将请求委托给父类的加载器去加载;
②、当父类加载器还存在父类加载器,则继续往上依次委托,最终的委托到最顶层的启动类加载器;
③、如果父类的加载器可以完成类加载器,则成功返回;如果父类加载器无法完成加载,子类加载器才会尝试着自己去加载。
这就是双亲委派机制的加载过程。
双亲委派机制的优势:
可以避免类的重复加载;
保护程序的安全,避免核心的api被篡改。
4、沙箱安全机制
java代码被限定在虚拟机特定的运行范围中,并且严格限制代码对本地资源的访问,通过这样来保护代码的的有效隔离性,防止本地系统造成破坏,这就是沙箱安全机制。
沙箱主要限制系统资源访问,例如:cpu、内存,文件系统、网络。不同级别的代码相对这些资源的访问的限制也可以是不一样的。
二、运行时数据区
1、虚拟机栈
每个线程在创建的时候,都会创建一个虚拟机栈,虚拟机栈是属于线程私有的。
a、栈帧(Stack Frame)
虚拟机栈中会存放着一个一个的栈帧,一个栈帧对应着一个方法,用于支持方法的调用和方法执行的数据结构;
虚拟机栈帧中存放着方法的局部变量表、操作数栈、动态链接和方法返回地址,在编译代码时,栈帧中需要多大的局部变量表和多深的操作数栈都已经确定好了。
①局部变量表:是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。注意:在mian()方法中,第一个表中的第一个位置的参数是args;
局部变量表存放了编译器可知的各种基本数据类型,对象引用和方法返回地址;
局部变量表以变量槽为最小单位,每个槽可以存放32位的长度,即long类型和doubule类型占俩个变量槽,而其他的基本数据类型占一个变量槽。
②操作数栈:用于保存计算过程中的中间结果,同时作为计算过程中变量的临时存放空间;
③动态链接:指向运行时常量池中该帧所属方法的引用;包含这个引用的目的是为了支持当前方法的代码能够实现动态链接,如:invokedynamic指令。
在java源文件被编译到字节文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,程序运行时将其加载进方法区的运行时常量池中;
如描述一个方法调用了另外的其他方法时,就是 通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用 转为调用方法的直接引用。
④方法返回地址:存放该方法在寄存器中的值,即是该方法的指令地址,方便执行引擎在执行完该方法后,回到该方法对应的指令行号,这样才能继续执行下去。
2、程序计数器
程序计数器,又称pc寄存器,存储下一条要执行的指令的地址,每个线程都有专属的pc寄存器。
程序执行时,会不停的切换不同的线程进行执行,有些线程可能执行一般,就被停止执行,转而执行其他线程,这时候pc寄存器会记录下知道到的指令行号,等到线程获取到资源重新执行时,会依据pc寄存器中的指令,继续往下执行。
3、堆
堆由新生代和老年代组成
新生代
新生代由Eden区(伊甸园区)和俩个Survior(幸存区),Survior0,Survior1,新生代一般用来存放新创建的对象。
①Endn区:几乎所有的对象都在Eden区中创建,即Eden是大部分对象产生的地方;
创建的对象有部分存在的周期很短,有些对象的存在周期很长,当Eden区的内存满了时,会触发Minor GC 进行垃圾收集;
②Survior区: 幸存区 ,幸存区划分为俩个区Survior0和Surivor1即,幸存区0和幸存区1,在进行垃圾收集时,哪个区为空则为Survior0;一般都是俩个区互相替换成为Survior0;
Survior0 和Survior1俩个区的存储空间一般为1:8的比例,但实际大多数情况都达不到这个比例,可以使用-XX:SurviorRatio参数进行设置。
老年代
老年代一般用来存放生存周期较长的对象,当老年代的存储空间不足时,会触发Major GC或Full GC进行垃圾收集。
新生代和老年的存储空间比例为1:2,可以通过 -XX:NewRatio 参数进行设置。
垃圾收集
在堆中,有三个垃圾收集算法:Minor GC、Major GC 和 Full GC
①Minor GC
新生代垃圾收集,只是新生代(Eden、Survior0和Survior1)的垃圾收集。
触发机制:当新生代中的Eden区的空间不足时触发(注意:Survior区空间不足时不会主动触发,Survior区只会被动触发机制)进行垃圾收集。因为大多数java对象都具备朝生夕灭的特性,所以Minor GC触发的非常频繁,一般回收的速度也比较快。
收集过程:a、新生代中,对于每个对象都有一个引用计数器,当Eden区的空间满了只有,只有还被引用的对象能存活不被清理,并且会转移到Survior0区,对象的引用计数器增加1,代表这个对象存活的年龄增大一岁;
b、重复a步骤,将存活的对象复制到S0区,存活年龄增大,已存在在S0区的对象也相对应的增加存活年龄;
c、当Eden区和S0区空间满时,会将S0中的对象转移到S1中,存活的对象相对应的存活年龄,然后Eden和S0区会被清空,存活的对象全部在S1中,并且对象的存在着不一样的存活年龄。此时S0会变成意义上的S1(区中为空),S1会变成意义上的S0;如此反复,空的区为S1,对象存活的数据区为S0。
d、当不断进行Minor GC知道对象的存活年达到阈值(通过-XX:MaxTenuringThreshold设置,默认是15),则达到阈值年龄的对象,会判定为生命周期较长的对象,会被promote到老年代;不断的进行Minor GC,也会不断有对象被promote到老年代中。
TLAB:TLAB是一块为了解决线程安全问题所设置的存储空间,属于新生代的Ede区,TLAB内存空间非常小,仅占Eden的1%,不是所有的对象的实例都能在TLAB中成功分配内存,但JVM确实是以TLAB作为分配内存的首选。
堆空间是所有线程共享的,但Eden中的TLAB是线程私有的,解决线程的安全问题。
Major GC
老年代垃圾收集,只进行老年代的垃圾收集。
一个对象如果很大,那么会直接进入老年代中,即老年代存在着大对象和存活周期长的对象。
触发机制:老年代的空间不足时,会尝试触发Minor GC 进行新生代的垃圾收集,如果触发后空间还是不足,则触发Major GC;
Major GC的速度一般会比Minor GC的速度慢10倍以上,STW的时间更长,如果触发后依旧空间不住,则报OOm错误。
一把只有CMS GC 会有单独收集老年代的行为,很多时候,Major GC和 Full GC都是混淆这是用触发。
收集过程:一般Major GC 和 Full GC 同时触发,收集整个堆。
Full GC
整堆收集,收集整个java堆和方法区的垃圾。
触发机制:a、调用System.gc()时,系统会建议执行Full GC,但不必然执行;
b、老年代空间不足时;
c、方法区空间不足时;
d、通过Minor GC后进入老年代的平均大小大于老年代的可用内存;
e、有Eden区、Survior space0(From space) 区向Survior space(To space)区复制时,对象大小大于 To space的可用空间大小,则对象复制到老年代,但对象大小又大于老年代可用空间时触发。
Full GC是开发或调优中尽量避免的,这样暂停的时间会短一些
代码优化