Java虚拟机知识点总结

Posted Linyb极客之路

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java虚拟机知识点总结相关的知识,希望对你有一定的参考价值。

1. 内存模型以及分区,需要详细到每个区放什么。

  • 运行时数据区域:虚拟机栈,本地方法栈,程序计数器,堆,方法区,具体如图所示:


  • 程序计数器: 

  • java虚拟机栈: 

  • 本地方法栈: 

    • 线程私有,本地方法指的是那种不是用java语言写的方法,java虚拟机栈只针java方法,而不是本地方法。hotspot虚拟机支持别的语言写的方法在虚拟机上运行,本法方法栈和java虚拟机栈一样。只是他们服务的对象不一样而已,一个为java方法服务,一个为native方法服务。

  • Java堆: 

    • 线程共享的,不过也可能为多个线程分配私有的buffer,也就是每个线程有自己的缓存器,java堆可以是物理上连续的,也可以是不连续的。java堆是垃圾回收器管理的主要区域,所以也叫gc堆。java堆可以分为:新生代和老年代

  • 方法区: 

    • 线程共享的,可以理解为gcc中所所说的静态区,不过也不是确切的准确,因为在hotspot虚拟机中他存放的是类中静态变量和常量(注意是常量哦)。因为他能存储常量,所以还有存储常量的区域有一个特别的名称,叫做常量池(包括引用和基本数据类型的常量),方法区并不是堆,这一点和静态区很相似。所以别名叫non-heap,java堆中可以选择不实现gc回收,但是实际上呢还是会的,只能说垃圾回收器在这个区域不活跃而已,但是回收都是回收常量池中的常量,而不是静态变量。可以称为永久代。

  • 运行时常量池: 

    • 他是方法区的一部分,但是和方法区的常量池有区别,他存放的常量是在运行时产生的,而不是编译时产生的。注意与普通方法区的区别



2.堆里面的分区:Eden,survival from to,老年代,各自的特点。

  • Eden区的对象都是朝生夕死,发生minor gc的时候会清除eden区和survival区的,把存活的对象移到另一个Survival区,该survial区由老年代保证。当在年轻代中对象经过多次minor gc以后还存活,达到老年代的年纪,就会移动到老年代,还有就是大对象在年轻代无法存储,直接转到老年代,还有可能因为担保而进入老年代的


3.对象创建方法,对象的内存分配,对象的访问定位

  • 1对象的创建包括三步骤: 

    • ①当遇到new命令的时候,会在常量池中检查该对象的符号引用是否存在,不存在则进行类的加载,否则执行下一步

    • ②分配内存,将将要分配的内存都清零。

    • ③虚拟机进行必要的设置,如设置hashcode,gc的分代年龄等,此时会执行命令在执行之前所有的字段都为0,执行指令以后,安装程序的意愿进行初始化字段。

  • 2:对象的内存分配:包括对象头,实例数据,对齐填充 

    • ①对象头:包括对象的hascode,gc分代年龄,锁状态标等。

    • ②实例数据:也就是初始化以后的对象的字段的内容,包括父类中的字段等

  • 3:对象的访问: 


4.GC的两种判别方法:引用计数于引用链

  • 引用计数 

    • 给一个对象设置一个计数器,当被引用一次就加1,当引用失效的时候就减1,如果该对象长时间保持为0值,则该对象将被标记为回收。优点:算法简单,效率高,缺点:很难解决对象之间的相互循环引用问题。

  • 引用链: 

    • 现在主流的gc都采用可达性分析算法来判断对象是否已经死亡。可达性分析:通过一系列成为GC Roots的对象作为起点,从这些起点向下搜索,搜索所走过的路径成为引用链,当一个对象到引用链没有相连时,则判断该对象已经死亡。

  • 可作为gc roots的对象: 

    • 虚拟机栈(本地方法表)中引用的对象(因为在栈内,被线程引用),方法区中类静态属性引用的对象,方法区中常量引用的(常量存放在常量池中,常量池是方法区的一部分)对象,native方法引用的对象

  • 引用计数和引用链是只是用来标记,判断一个对象是否失效,而不是用来清除


5.GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?

  • 标记清除: 

    • 直接将要回收的对象标记,发送gc的时候直接回收:特点回收特别快,但是回收以后会造成很多不连续的内存空间,因此适合在老年代进行回收,CMS(current mark-sweep),就是采用这种方法来会后老年代的。

  • 标记整理: 

    • 就是将要回收的对象移动到一端,然后再进行回收,特点:回收以后的空间连续,缺点:整理要花一定的时间,适合老年代进行会后,parallel Old(针对parallel scanvange gc的) gc和Serial old就是采用该算法进行回收的。

  • 复制算法: 

    • 将内存划分成原始的是相等的两部分,每次只使用一部分,这部分用完了,就将还存活的对象复制到另一块内存,将要回收的内存全部清除。这样只要进行少量的赋值就能够完成收集。比较适合很多对象的回收,同时还有老年代对其进行担保。(serial new和parallel new和parallel scanvage)

  • 优化手机方法: 

    • 优化收集方法:对复制算法的优化:并不是将两块内存分配同等大小,可以将存活率低的区域大一些,而让回收后存活的对象所占的区域小一些,不够的内存由老年代的内存来保证,这样复制算法的空闲的空间减少了。 
      两个survival区域的是为了减少风险率,有一个survivor区要参与回收,也要参与存储,只要只有10%的空间浪费,同时也减少对老年代的依赖。


6.GC收集器有哪些?CMS收集器与G1收集器的特点。

  • 串行的,也就是采用单线程(比较老了),分类:serial new(收集年轻代,复制算法)和serial old(收集老年代,标记整理),缺点:单线程,进行垃圾回收时暂时所有的用户线程。优点:实现简单。

  • 并行的,采用多线程,对于年轻代有两个: parallel new(简称ParNew)(参考serial new的多线程版本)和parallel scavenge;parallel scavenge是一个针对年轻代的垃圾回收器,采用复制算法,主要的优点是进行垃圾回收时不会停止用户线程(不会发生stop all world) 
    老年代回收器也有两种:Parallel old是parallel scavenge的我老年代设计的。CMS(并发标记清除),他采用标记清除算法,采用这种的优点就是快咯,因此会尽快的进行回收,减少停顿时间。

  • 高级杀手:G1收集器,年轻代和老年代通吃,最新一代的技术。面向服务器端的垃圾收集器(并行+并发的垃圾收集器)。


7.Minor GC与Full GC分别在什么时候发生?

  • Minor GC发生:当jvm无法为新的对象分配空间的时候就会发生minor gc,所以分配对象的频率越高,也就越容易发生minor gc。

  • Full GC:发生GC有两种情况,①当老年代无法分配内存的时候,会导致MinorGC,②当发生Minor GC的时候可能触发Full GC,由于老年代要对年轻代进行担保,由于进行一次垃圾回收之前是无法确定有多少对象存活,因此老年代并不能清除自己要担保多少空间,因此采取采用动态估算的方法:也就是上一次回收发送时晋升到老年代的对象容量的平均值作为经验值,这样就会有一个问题,当发生一次Minor GC以后,存活的对象剧增(假设小对象),此时老年代并没有满,但是此时平均值增加了,会造成发生Full GC


8.类加载的五个过程:加载、验证、准备、解析、初始化。

  • 加载:

    • 加载有两种情况,①当遇到new关键字,或者static关键字的时候就会发生(他们对应着对应的指令)如果在常量池中找不到对应符号引用时,就会发生加载 ,②动态加载,当用反射方法(如class.forName(“类名”)),如果发现没有初始化,则要进行初始化。(注:加载的时候发现父类没有被加载,则要先加载父类)

  • 验证:

    • 这一阶段的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全(虽然编译器会严格的检查java代码并生成class文件,但是class文件不一定都是通过编译器编译,然后加载进来的,因为虚拟机获取class文件字节流的方式有可能是从网络上来的,者难免不会存在有人恶意修改而造成系统崩溃的问题,class文件其实也可以手写16进制,因此这是必要的)

  • 准备:

    • 该阶段就是为对象分派内存空间,然后初始化类中的属性变量,但是该初始化只是按照系统的意愿进行初始化,也就是初始化时都为0或者为null。因此该阶段的初始化和我们常说初始化阶段的初始化时不一样的

  • 解析:

  • 初始化:

    • 简单讲就是执行对象的构造函数,给类的静态字段按照程序的意愿进行初始化,注意初始化的顺序。(此处的初始化由两个函数完成,一个是,初始化所有的类变量(静态变量),该函数不会初始化父类变量,还有一个是实例初始化函数,对类中实例对象进行初始化,此时要如果有需要,是要初始化父类的)


9.双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。

  • 类 加载器的工作过程:如果一个类加载器收到类类加载的请求,他首先不会自己去加载这个类,而是把类委派个父类加载器去完成,因此所有的请求最终都会传达到顶 层的启动类加载器中,只有父类反馈无法加载该类的请求(在自己的搜索范围类没有找到要加载的类)时候,子类才会试图去加载该类。


10.分派:静态分派与动态分派:

  • 静态分派和动态分派都是多态的内容,多态的实现依赖于编译阶段和运行时阶段:在编译阶段主要表现在静态分派,

  • 静态分派就是通过静态类型和方法参数个数来选择哪一个方法版本,这就是主要体现了方法的重载;因为他在编译的时候就能确定调用哪一个函数,所以叫静态分派。

  • 在运行时阶段体现在动态分派(动态绑定),也就是当一个父类引用指向子类对象,通过该父类引用去调用一个该方法,由于在编译阶段生产的调用函数代码的字节码指向的是父类(静态类型)被调用方法,并不知道具体要去调用哪一个实际类型的方法,因 此会发生这样一个过程,虚拟机找到操作数栈中位于栈顶获取该操作数的指所指向的类,然后到常量池中去搜索与被调用的方法匹配的方法名和描述符,如果找到, 就进行权限校验(校验失败就抛出异常),如果可以访问,则返回该方法的符号引用,并转换成直接引用,调用该执行,如果找不到就到父类中去找,然后重复上面 动作,最后找不到就抛出异常。

以上是关于Java虚拟机知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

看完《深入理解 Java 虚拟机》后的一些总结

Java基础学习JVM知识点总结面试题形式

java虚拟机和内存优化总结

Java~阅读《深入理解Java虚拟机》知识点总结

JAVA虚拟机知识总结

Java内存与垃圾收集知识总结