Java JVM

JVM整体工作流程

  Java源文件.java 文件通过javac命令编译成.class文件,编译的文件也可以从网络上下载的jar、zip包等,通过java命令进行运行。

  

一个Java类的生命周期:

加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载 

(1)加载 

  首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。 

(2)链接: 

  验证:确保被加载类的正确性; 

  准备:为类的静态变量分配内存,并将其初始化为默认值; 

  解析:把类中的符号引用转换为直接引用; 

(3)为类的静态变量赋予正确的初始值 

3、类的初始化 

(1)类什么时候才被初始化 (类初始化不一定是通过new)

  1)创建类的实例,也就是new一个对象 

  2)访问某个类或接口的静态变量,或者对该静态变量赋值 

  3)调用类的静态方法 

  4)反射(Class.forName(“com.lyj.load”)) 

  5)初始化一个类的子类(会首先初始化子类的父类) 

  6)JVM启动时标明的启动类,即文件名和类名相同的那个类 

(2)类的初始化顺序 

  1)如果这个类还没有被加载和链接,那先进行加载和链接 

  2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口) 

  3)假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。 

  4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法 

Java虚拟机的体系结构

每个jvm都有两种机制:

  1.类装载子系统:装载具有适合名称的类或接口

  2.执行引擎:负责执行包含在已装载的类或接口中的指令

每个jvm都包含:方法区、Java堆、Java栈、指令计数器和其他隐含寄存器

jvm内存管理

(1)堆内存(heap)

所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。 

(2)栈内存(stack)

在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 由系统自动分配,速度较快。但程序员是无法控制的。

堆内存与栈内存说明:

  1、基础数据类型(如int)直接在栈空间分配,方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。

  2、引用数据类型,需要用new创建的,在栈空间分配一个地址空间,在堆空间分配对象的类变量。

  3、方法的引用参数,在占空间分配一个地址空间,并只想堆空间的对象区,当方法调用完成后从栈空间回收

  4、局部变量new出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收

  5、字符串常量、static在DATA区域分配,this在堆空间分配。数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小

JVM GC

1)GC什么时候回回收?

 对象没有引用

 作用域发生未捕获异常

 程序在作用域正常执行完毕

 程序执行了System.exit()

 程序发生意外终止(被杀进程等)

2)如何减少GC开销?

不要显式调用System.gc()。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。

尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

对象不用时最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

能用基本类型如Int,long,就不用Integer,Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

3)JVM垃圾回收算法

标记-清除算法:

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。


复制算法:

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,将复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。


标记-整理算法:

标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让索引端移动,然后直接清理掉端边界以外的内存。


分代收集算法:

一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。新生代都发现有大批对象死去,选用复制算法。老年代中因为对象存活率高,必须使用“标记-清理”或“标记-整理”算法来进行回收。

原文地址:https://www.cnblogs.com/yanyouqiang/p/9435834.html