1.JVM的生命周期
JVM实例和JVM引擎实例
a.JVM实例对应了一个独立运行的java程序,它是进程级别。
b.JVM执行引擎实例则对应了属于用户运行程序的线程,它是线程级别
2.JVM的生命周期
a.JVM实例的诞生:当启动一个java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点
b.JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均有该线程启动,JVM内部有两种线程:守护线程和非守护线程,mian()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程
守护线程和非守护线程(用户线程)的区别:守护线程:它的作用就是为其他线程运行提供服务。当一个用户线程结束后,JVM虚拟机会检测系统中是否还有其他用户线程,如果存在则按照正常的调用方法调用,如果只剩下守护线程没有用户线程,程序就会终止。最典型就是垃圾回收线程,垃圾回收线程负责回收程序中没用的内存空间,当程序中其他用户线程终止后,垃圾回收线程就没有任何存在的意义了。
c.JVM实例的消亡:当程序中所有的非守护线程都终止时,JVM才退出,若安全管理器允许,程序也可以使用Runtime或Sistem.exit()来退出。
3.JVM的体系:
a.JVM的内部体系分为三部分:1.类装载子系统:用来装载.class文件 2.执行引擎:执行字节码或者执行本地方法 3.运行时数据区:方法区,堆,java栈,PC寄存器,本地方法栈,
b.JVM的类加载器:JVM将整个类加载过程分为三个步骤:
1.装载:转载过程负责找到二进制字节码并加载到JVM中,JVM通过类名,类所在的包名通过ClassLoader来完成类的加载,同上采用以上三个元素来标识一个被加载了的类:
类名+包名+ClassLoader实例ID
2.链接:链接过程负责对二进制字节码的格式进行校验,初始化装载类中的静态变量以及解析类中调用的接口,类。在完成字节码的校验后,JVM初始化类中的静态变量,并将其赋值为默认值。最后一步对类中的所有属性,方法进行校验,以确保其需要调用的属性,方法存在,以及具备应得权限(如public,private等),会照成NoSuchMethodError,NosuchFileError等错误信息。
3.初始化:初始化过程及执行类中的静态初始化代码,构造器代码以及静态属性的初始化,在4中情况下初始化过程会被触发执行:1.调用了new;2.反射调用了类中的方法;3.子类调用了初始化;JVM启动过程中指定的初始化类。
三个类加载器:
JAM两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义装载器则是java 程序的一部分,必须是ClassLoader类的子类。
主要分为以下几类:
1.Bootstrap ClassLoader:这是JVM的跟ClassLoader,它是用C++实现的JVM启动时初始化该ClassLoader,并有该ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。
2.Extension ClassLoader:JVM调用用此ClassLoader来加载扩展功能的一些jar包
3.System ClassLoader:JVM用此ClassLoader来加载启动参数中指定的ClassPath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
(4) User-Defined ClassLoader:User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录
ClassLoader抽象类提供了几个关键方法:
1.loadClass():此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如果没有则继续从parentClassLoader中寻找,如仍然没找到,则从SystemClassLoader中寻找,最后在调用findClass()方法来寻找,如要改变类的加载顺序,则可覆盖此方法。
2.findLoadClass():此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。
3.findClass():此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
4.findSystemClass():此方法负责从SystemClassLoader中寻找类,如未找到,则继续从BootstrapClassLoader中寻找,如仍然未找到,则返回null。
5.defineClass:此方法负责将二进制的字节码转化为Class对象。
6.resolveClass:此方法负责完成Class对象的链接,如以链接过,直接返回。
JVM的执行引擎:
JVM通过执行引擎来完成字节码的执行,在执行过程中JVM采用的是自己的一套指令系统,每个线程在创建之后,都会产生一个程序计数器和栈,其中程序计数器中存放了下一条要执行的指令,栈中存放 Stack Frame,表示的为当前正在执行的方法,每个方法的执行都会产生Stack Frame,Stack Frame中存放了传递给方法的参数,方法内的局部变量以及操作栈,操作数栈用于存放指令运算的中间结果,指令负责从操作数栈中弹出参与运算的操作数,指令执行完毕后,再将计算结果压回到操作数栈,当前方法执行完毕后则从Stack中弹出,继续其他方法的执行。
在执行方法时JVM提供了invokestatic,invokevirtual,invokeinterface和invokespecial四种指令来执行。
1.invokestatic:调用类的static方法
2.invokevirtual:调用对象实例的方法
3.invokeinterface:将属性定义为接口来进行调用
4.invokespecial:JVM对于初始化对象(java构造器的方法为:<init>)以及调用对象实例中私有的方法。
java的反射机制:是java 的亮点之一,基于反射可动态调用某对象实例中对应的方法,访问查看对象的属性等,而无需再编写代码时就确定需要创建的对象,这使得java可以实现灵活的实现对象的调用。
Class actionClass=Class.forName(外部实现类);
Method method=actionClass.getMethod(“execute”,null);
Object action=actionClass.newInstance();
method.invoke(action,null);
反射的关键:要实现动态的调用,最明显的就是动态的生产字节码,加载到JVM中并执行。
(1)Class actionClass=Class.forName(外部实现类);
调用本地方法,使用调用者所在的ClassLoader来加载创建出Class对象 ;
(2)Method method=actionClass.getMethod(“execute”,null);
校验此Class是否为public类型的,以确定类的执行权限,如不是public类型的,则直接抛出
SecurityException;
调用privateGetDeclaredMethods来获取到此Class中所有的方法,在privateGetDeclaredMethods对此Class中所有的方法的集合做了缓存,在第一次时会调用本地方法去获取;
扫描方法集合列表中是否有相同方法名以及参数类型的方法,如有则复制生成一个新的Method对象返回;
如没有则继续扫描父类、父接口中是否有此方法,如仍然没找到方法则抛出NoSuchMethodException
(3) Object action=actionClass.newInstance();
第一步:校验此Class是否为public类型,如权限不足则直接抛出SecurityException;
第二步:如没有缓存的构造器对象,则调用本地方法获取到构造器,并复制生成一个新的构造器对象,放入缓存,如没有空构造器则抛出InstantiationException;
第三步:校验构造器对象的权限;
第四步:执行构造器对象的newInstance方法;构造器对象的newInstance方法判断是否有缓存的ConstructorAccessor对象,如果没有则调用sun.reflect.ReflectionFactory生成新的ConstructorAccessor对象;
第五步:sun.reflect.ReflectionFactory判断是否需要调用本地代码,可通过sun.reflect.noInflation=true来设置为不调用本地代码,在不调用本地代码的情况下,就转交给MethodAccessorGenerator来处理了;
第六步:MethodAccessorGenerator中的generate方法根据Java Class格式规范生成字节码,字节码中包括了ConstructorAccessor对象需要的newInstance方法,此newInstance方法对应的指令为invokespecial,所需的参数则从外部压入,生成的Constructor类的名字以:sun/reflect/GeneratedSerializationConstructorAccessor或sun/reflect/GeneratedConstructorAccessor开头,后面跟随一个累计创建的对象的次数;
第七步:在生成了字节码后将其加载到当前的ClassLoader中,并实例化,完成ConstructorAccessor对象的创建过程,并将此对象放入构造器对象的缓存中;
最后一步:执行获取的constructorAccessor.newInstance,这步和标准的方法调用没有任何区别。
(4) method.invoke(action,null);
这步执行的过程和上一步基本类似,只是在生成字节码时生成的方法改为了invoke,其调用的目标改为了传入的对象的方法,同时生成的类名改为了:sun/reflect/GeneratedMethodAccessor。
执行技术:主要的执行技术有解释器,即时编译,自适应优化,芯片级直接执行。
JVM运行时的数据区:
第一块:PC寄存器
PC寄存器时用于存储下一步执行的JVM指令,如该方法为native,则PC寄存器中不存储任何信息。
第二块:JVM栈
JVM栈是线程私有的,每个线程创建的同时会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量,部分返回结果以及StackFrame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
第三块:堆
Heap是大家最为熟悉的区域,它是JVM用来存储对象实例数组值的区域,可以认为java 中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
JVM将Heap分为New Generation和Old Generation(或Tenured Generation)两块来
管理:
(1)New Generation
又称为新生代,程序中新建的对象都将分配到新生代中,新生代又由Eden Space和两块Survivor Space构成,可通过-Xmn参数来指定其大小
(2) Old Generation
又称为旧生代,用于存放程序中经过几次垃圾回收还存活的对象,例如缓存的对象等,旧生代所占用的内存大小即为-Xmx指定的大小减去-Xmn指定的大小。
对堆的解释:
1.堆是JVM中线程所共享的,因此在其上进行内存的分配需要进行加锁,这也导致了new对象的开销是比较大的。
2.基于上面的原因,Sun Hotspot JVM/IBM J9 JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间,这块空间被称为TLAB,其大小由JVM根据运行的情况计算而得,在这种情况下JVM中分配对象内存和C基本一样高效。
(3)TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效, 但这种方法同时也带来了两个问题,一是空间的浪费,二是对象内存的回收上仍然没法做到像Stack那么高效,同时也会增加回收时的资源的消耗,可通过在启动参数上增加-XX:+PrintTLAB来查看TLAB这块的使用情况。
第四块:方法区域
1.方法区域存放了所加载的类的信息(名称,修饰等),类中的静态变量,类中定义为final的常量,类中的filed信息,类中的方法信息,当开发人员在程序中通过Class对象中的getName,isInterface等方法来获取信息时,这些数据都来源于方法区域,可见方法区域的重要性,同样,方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
第五块:运行时常量池(Runtime Constant Pool)
类似C中的符号表,存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。
第六块:本地方法堆栈(Native Method Stacks)
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
JVM垃圾回收:JVM中自动的对象内存回收机制称为GC
1.GC的原理:
将内存中不在使用的对象进行回收,GC中用于回收内存中不被使用的对象的方法称为收集器。由于GC需要消耗一些时间和资源,java在对对象的生命周期特征进行分析后,在现有的JDK基本上可以采用分代的方式来进行对象的收集,即按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。
(1)对新生代的对象的收集称为minor GC,
(2)对旧生代的对象的收集称为Full GC,
(3)程序中主动调用System.gc()强制执行的GC为Full GC,
二、 JVM中自动内存回收机制
(1)引用计数收集器
原理:
引用计数是标识Heap中对象状态最明显的一种方法,引用计数的方法简单来说就是对每一个对象都提供一个关联的引用计数,以此来标识该对象是否被使用,当这个计数为零时,说明这个对象已经不再被使用了。
优点:
引用计数的好处是可以不用暂停应用,当计数变为零时,即可将此对象的内存空间回收,但它需要给每个对象附加一个关联引用计数
缺点:
引用计数无法解决循环引用的问题,因此JVM并没有采用引用计数。
(2)跟踪收集器
原理:
跟踪收集器的方法为停止应用的工作,然后开始跟踪对象,跟踪时从对象根开始沿着引用跟踪,直到检查完所有的对象。
根对象的来源主要有三种:
1.被加载的类的常量池中的对象引用
2.传到本地方法中,没有被本地方法“释放”的对象引用
3.虚拟机运行时数据区中从垃圾收集器的堆中分配的部分
存在问题:
跟踪收集器采用的均为扫描的方法,但JVM将Heap分为了新生代和旧生代,在进行minor GC时需要扫描是否有旧生代引用了新生代中的对象,但又不可能每次minor GC都扫描整个旧生代中的对象,因此JVM采用了一种称为卡片标记(Card Marking)的算法来避免这种现象。
卡片标记算法:
卡片标记算法为将就生代以某个大小进行划分,划分出来的每个区域称为卡片,JVM采用卡表维护卡的状态,每张卡片在卡表中占用一个字节的标识,当Java代码执行过程中发现旧生代的对象引用或者释放了对对于新生代的引用时,就相应的修改卡表中卡的状态,每次Minor GC只需要扫描卡表中标识为脏状态的卡中对象即可。
1、跟踪收集器在扫描时最重要的是要根据这些对象是否被引用来标识其状态
2、JVM中将对象的引用分为了四种类型,不同的对象引用类型会造成GC采用不同的方法进行回收:
(1)强引用:默认情况下,对象采用的均为强引用
(这个对象的实例没有其他对象引用,GC时才会被回收)
(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用
(只有在内存不够用的情况下才会被GC)
(3)弱引用:在GC时一定会被GC回收
4)虚引用:由于虚引用只是用来得知对象是否被GC