IT序号网

Java底层知识JVM、GC知识解答

leader 2021年06月03日 编程语言 141 0

1、JVM如何加载.class文件?

答:Java虚拟机,最值的学习的两点,JVM内存结构模型以及GC。JVM是一个内存中的虚拟机,JVM的存储就是内存,例如类、常量、变量、方法都是在内存中。Java虚拟机是一种抽象化的虚拟机,在实际的计算机上仿真模拟各种计算机功能来实现,JVM有自己完善的硬件架构,如处理器,堆栈,寄存器等等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码即字节码,就可以在多种平台上不加修改的运行,一次编译到处运行。

  1)、JVM主要由Runtime Data Area 、Class Loader、Execution Engine、Native Interface四部分组成。主要通过Class Loader将符合格式的class文件进行加载到内存里面,并通过Execution Engine去解析class文件里面的字节码并提交给操作系统去执行等等。
  2)、Class Loader,依据特定格式,加载class文件到内存,加载javac编译好的class文件到内存中,不是自己随意创建的class文件,加载的文件是有格式要求的,符合格式就可以加载。
  3)、Execution Engine,加载的文件可以不可以运行,是由Execution Engine进行负责,Execution Engine叫做解释器,对命令进行解析,解析完成后提交给真正的操作系统进行执行。
  4)、Native Interface,融合不同开发语言的原生库为Java所用,Native Interface称为本地接口。
  5)、Runtime Data Area,JVM内存空间结构模型,所写的程序都会加载到这里,之后开始运行。

2、谈谈Java反射。

答:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

 1 package com.reflect; 
 2  
 3 import java.lang.reflect.Field; 
 4 import java.lang.reflect.Method; 
 5  
 6 /** 
 7  * 
 8  */ 
 9 public class Student { 
10  
11     private String name;// 姓名 
12  
13     /** 
14      * 公有的方法 
15      * 
16      * @param hello 
17      */ 
18     public void sayHi(String hello) { 
19         System.out.println(hello + " " + name); 
20     } 
21  
22     /** 
23      * 私有的方法 
24      * 
25      * @param tag 
26      * @return 
27      */ 
28     private String sayHello(String tag) { 
29         return "hello " + tag; 
30     } 
31  
32     // 反射就是类中的各种成分映射成一个个Java对象 
33     public static void main(String[] args) throws Exception { 
34         // 获取到类的对象,必须是全路径加类名,才可以获取成功 
35         Class clazz = Class.forName("com.reflect.Student"); 
36         // 创建类实例,返回的是泛型,需要强转 
37         Student student = (Student) clazz.newInstance(); 
38         // 打印类的名称 
39         System.out.println("Class name is " + clazz.getName()); 
40         // 首先获取到类,再对类的实例进行调用,调用类里面的方法 
41         // 如果直接根据创建的实例调用公有的方法,就不是反射的用法了 
42         // student.sayHi("张三"); 
43  
44         // 反射可以直接调用公有的,私有的方法的。获取声明的方法。 
45         // getDeclaredMethod可以获取到包括公共的,保护的,私有,包的方法,但是不能获取到继承的方法和实现接口的方法。 
46         Method sayHello = clazz.getDeclaredMethod("sayHello", String.class); 
47         // 将获取到的私有的方法,设置为true,默认是false,不设置报错。 
48         sayHello.setAccessible(true); 
49         // 此时,开始调用这个私有的方法,参数一是实例名称,参数二是参数名称。 
50         Object invoke = sayHello.invoke(student, "张三三"); 
51         // 打印私有的方法 
52         System.out.println("sayHello result is : " + invoke); 
53  
54         // 通过反射获取到公共的方法 
55         // getMethod方法可以获取公共的方法,获取到继承的方法和实现接口的方法。 
56         Method sayHi = clazz.getMethod("sayHi", String.class); 
57         // 通过invoke反射获取到公共的方法 
58         sayHi.invoke(student, "hello"); 
59  
60         // 获取私有类型的字段 
61         Field name = clazz.getDeclaredField("name"); 
62         // 设置为ture,因为属性name是私有的 
63         name.setAccessible(true); 
64         // 通过set方法进行设置,参数一是实例,参数二名称 
65         name.set(student, "李四四"); 
66         sayHi.invoke(student, "hello"); 
67  
68     } 
69  
70 }

3、类从编译到执行的过程。

答:之所以可以获取到类的属性或者方法,都是先获取到Class对象,而获取到该类的Class对象,必须先要获取到该类的字节码文件的对象。

1)、编译器将类源文件编译为class字节码文件。
2)、ClassLoader类加载器将字节码转换为JVM中的Class 类对象。
3)、JVM利用Class类对象实例化为类对象。

4、谈谈ClassLoader。

  答:ClassLoader在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接,初始化等操作。

5、ClassLoader的种类。

  1)、BootStrapClassLoader,C++编写,加载核心库java.*,加载Java自带的核心类。
  2)、ExtClassLoader,Java编写,加载扩展库javax.*,是用户可见的ClassLoader,用于加载位于jre/lib/exe下面的jar包,可以将自定义的包放到该目录下,通过ClassLoader进行加载。
  3)、AppClassLoader,Java编写,加载程序所在的目录,是用户可见的ClassLoader,通过代码可以看得到的,加载ClassPath下面的内容的。
  4)、自定义ClassLoader,Java编写,定制化加载。
 

6、类的加载方式。

  1)、第一种,隐式加载,通过new关键字,程序在运行过程中,遇到通过new方式来生成对象的时候,饮食调用类加载器,加载对应的类到JVM中,new创建对象最常用。支持向构造器传入参数创建实例对象。
  2)、第二种,显示加载,loadClass,forName等等,对于显示加载来讲,当我们取到class对象之后,需要调用class对象的newInstance方法来生成对象的实例。


7、loadClass,forName的区别。

  1)、首先都可以在运行时对任意一个类,都能够知道该类的所有属性和方法,对于任意一个对象都能够调用它的任意方法和属性。
  2)、Class.forName得到的class是已经初始化完成的。已经完成了第三步了初始化了。
  3)、Classloder.loadClass得到的class是还没有链接的。只完成了第一步加载,第二步链接和第三步初始化都没有执行。


8、类的装载(加载是装载的一个过程)过程,装载是Class类生成的过程。

  1)、第一步,加载,通过ClassLoader加载class文件字节码,生成Class对象。。
  2)、第二步,链接,校验是检查加载的class的正确性和安全性,准备是为类变量分配存储空间并设置类变量初始值,解析是Jvm将常量池内的符号引用转换为直接引用,解析是可选的。
  3)、第三步,初始化,执行类变量赋值和静态代码块。

9、Java的内存模型,JVM的内存模型-JDK1.8。

答:可以从线程的角度(线程角度:那些区是线程私有的,那些区是线程共享的)和存储的角度进行观察。

1)、线程私有:程序计数器,虚拟机栈,本地方法栈。
2)、线程共享:MetaSpace,Java堆。

10、程序计数器(Program Counter Register)。

答:程序计数器是一块较小的内存空间,作用是当前线程所执行的字节码行号指示器。

  1)、当前线程所指向的字节码行号指示器(逻辑),程序计数器是逻辑计数器,而非物理计数器。
  2)、在虚拟机的概念模型里面,字节码解释器工作时,就是改变计数器的值来选取下一条需要执行的字节码指令,包括分支,循环,异常,跳转,异常处理,线程恢复等计数功能。
  3)、和线程是一对一的关心即"线程私有"。由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令,因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,称这类内存区域为线程私有的内存。
  4)、对Java方法计数,如果是Native方法则计数器值为Underfined。如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行是Native方法,这个计数器值为Underfined。
  5)、不会发生内存泄漏。由于只是记录行号,程序计数器不用担心内存泄露的问题。

11、Java虚拟机栈(Stack)。

答:1)、Java方法执行的内存模型。Java虚拟机栈也是线程私有的,可以说是Java方法执行的内存模型。

  2)、包含多个栈帧。每个方法在执行的时候,都会创建一个栈帧,即方法运行期间的基础数据结构,栈帧用于存储局部变量表、操作栈、动态连接、返回地址,每个方法执行中对应虚拟机栈帧从入栈到出栈的过程,Java虚拟机栈用来存储栈帧,而栈帧持有局部变量和部分结果,以及参与方法的调用与返回,当方法调用结束时,帧才会被销毁。这里的关键信息是虚拟机栈包含了单个线程每个方法执行的栈帧,而栈帧则存储了局部变量表,操作栈、动态连接、返回地址等信息。


12、局部变量表和操作数栈的区别。

答:1)、局部变量表,包含方法执行过程中的所有变量。

  2)、操作数栈,入栈、出栈、复制、交换、产生消费变量。

13、本地方法栈。

答:与虚拟机栈相似,主要作用于标注了native的方法。

14、JVM三大性能调优参数,-Xms、-Xmx、-Xss的含义。

答:1)、-Xms,堆的初始值,表示的是初始的Java堆的大小,即该进程刚创建出来的时候它的专属Java堆的大小,一旦对象容量超过了Java堆的初始容量,Java堆将会自动扩容,扩容到-Xmx大小。
  2)、-Xmx,堆能达到的最大值。通常情况下,将-Xmx和-Xms设置成一样的,因为当heap不够用而发生扩容的时候,会发生内存抖动,影响程序运行时的稳定性。
  3)、-Xss,规定了每个线程虚拟机栈(堆栈)的大小。一般情况下256k就足够了,此配置将影响此进程中并发线程数的大小。


15、Java内存模型中堆和栈的区别,内存分配策略。

答:如果向弄清楚堆和栈的区别,需要先弄清楚内存分配策略。内存分配策略分为静态的、堆式的、栈式的。

  1)、静态存储,编译时确定每个数据目标在运行时的存储空间需求。因而在编译时就可以给它们分配固定的内存空间,这种分配策略要求程序代码不允许有可变数据结构的存在,也不允许有嵌套或者递归的j结构出现,因为它们会导致编译程序无法计算准备的存储空间。
  2)、栈式存储,数据区需求在编译时未知,运行时模块入口前确定。可以称为动态的存储分配,是由一个类似于堆栈的运行栈来实现的,和静态存储的分配相反,栈式存储方案中,程序堆数据区的要求在编译时是完全未知的,只有到了运行时才能知道,但是规定在运行中进入一个程序模块的时候,必须知道该程序模块所需要的数据区的大小,才能分配其内存,栈式存储按照先进后出的分配原则。
  3)、堆式存储,编译的时候或者运行的时候模块入口都无法确定,动态分配。比如可变长度串,对象实例,堆由大片的可利用块和空闲块组成,堆中的内存可以按照任意顺序分配和释放。

16、Java内存模型中的堆和栈的联系。

答:引用对象,数组的时候,栈里面定义变量保存堆中目标的首地址。

  创建好的数组、对象实例,都会被保存到堆中,想要引用堆中的某个对象或者数组,咱们可以在栈中定义一个特殊的变量,这种栈中的这个变量的取值等于数组或者对象,在堆内存中的首地址。栈中的这个变量就成了数组或者对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象。引用变量就相当于是为数组或者对象起的一个名称,引用变量是普通的变量,定义的时候在栈中分配,引用变量在程序运行到其作用域之外后呢,就会被释放掉了,而数组和对象本身在堆中分配,即使程序运行到使用new产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,在没有引用变量指向的时候,才会变为垃圾,需要等待随后的一个b不确定的时间被垃圾回收器释放掉。
 

17、Java内存模型中堆和栈的区别。

答:1)、管理方式,栈自动释放,堆需要GC。JVM可以针对n内存栈进行管理操作,而且该内存空间的释放是编译器就可以操作的内容,而堆空间在java中,JVM执行引擎不会对其进行释放,而是让垃圾回收器进行自动回收。
  2)、空间大小,栈比堆小。这是栈空间里面存储的数据以及本身需要的数据特性来决定的,而堆空间在JVM堆实例进行分配的时候,一般大小都比较大,因为堆空间在一个Java程序中需要存储比较多的java对象数据。
  3)、碎片相关,栈产生的碎片远小于堆。针对堆空间而言,即使垃圾回收器能够进行自动堆内存回收,但是堆空间的活动量相对栈空间而言比较大,很有可能存在长期的堆空间分配和释放操作,而且垃圾回收器b不是实时的,它有可能使得堆空间的内存碎片逐渐累积起来。针对栈空间而言,因为它本身就是栈的数据结构,它的操作都是一一对应的,而且每一个最小单位的结构栈帧和堆空间内复杂的n内存结构不一样,所以它一般在使用过程中很少出现内存碎片。
  4)、分配方式,栈支持静态和动态分配,而堆仅支持动态分配。堆在有垃圾回收器的前提下,还是需要考虑其释放的问题的。
  5)、效率,栈的效率比堆高。因为内存块的排列本身就是一个堆栈结构,所以栈空间的效率比堆空间的效率高很多。计算机底层内存结构本身就是使用了堆栈结构,使得栈空间和底层结构更加符合。栈的操作简单,只设计到了入栈和出栈。栈空间相对堆空间是灵活程度不够,特别是在动态管理的时候,而堆空间最大的优点是动态分配,因为它在计算机底层可能是一个双向链表的结构。


18、Java垃圾回收机制。

答:对象被判定为垃圾的标准,没有被其它对象引用的时候,就被判定为垃圾了。对于系统而言,它就是垃圾,占据的内存就要被释放。



19、判定对象是否为垃圾的算法。

答:1)、引用计数算法。
  2)、可达性分析算法。   


20、引用计数算法。

答:判断对象的引用数量。
  1)、通过判断对象的引用数量来决定对象是否可以被回收。
  2)、堆中的每个对象实例都有一个引用计数器,被引用则加一,完成引用则减一。当一个对象被创建的时候,若该对象实例分配一个引用变量,该对象实例的引用计数就设置为1,若该对象又被另外一个对象所引用,则该对象的引用计数器继续加一,而当该对象实例的某个引用超过了生命周期,或者被设置为一个新值的时候,该对象实例的引用计算便会减一。
  3)、任何引用计数为0的对象实例可以被当作垃圾收集。
  4)、优点是执行效率高,程序执行受影响较小。
  5)、缺点,实现过于简单,无法检测出循环引用的情况,导致内存泄漏。如父对象引用子对象,子对象引用父对象。



21、可达性分析算法。

答:通过判断对象的引用链是否可达来决定对象是否可以被回收。可达性算法是从离散数学的图论引入的,程序把所有的引入关系看作一张图,通过一系列名为GC root作为起始点,从这些节点开始向下搜索,搜索所走过的路径就被称为引用链,当一个对象从GC root没有任何引用链相连,从图论上来说就是从GC root到这个对象是不可达的,这个时候就证明了这个对象是不可用的,它就被标记为垃圾了。



22、可达性分析算法,什么可以作为GC Root的对象。

  1)、虚拟机栈中引用的对象(栈帧中的本地变量表中引用的对象),比如,定义的局部变量。
  2)、方法区中的常量引用的对象,比如,类里面定义的常量,而该常量保存的某个对象的地址,那么被保存的对象也称为GC的根对象,当别的对象引用到它的时候就会形成关系链。
  3)、方法区中的类静态属性引用的对象。
  4)、本地方法栈中JNI(Native方法)的引用对象。
  5)、活跃线程的引用对象。

23、谈谈你了解的垃圾回收算法。

  1)、标记-清除算法(Mark and Sweep)。
    a)、标记就是从根集合进行扫描,对存活的对象进行标记,使用的是可达性算法来找到垃圾对象。
    b)、清除,标记完成后,对堆内存从头到尾进行线性遍历,如果发现对象m没有被标识为可达对象,就将此对象占用的内存回收了,并且将之前标识为可达的标识清除掉以便进行下一次垃圾回收,回收不可达对象内存。
    c)、标记-清除算法缺点,就是产生大量碎片化,由于标记清除不需要对象的移动,并且仅对b不存活的对象进行处理,因此标记清除之后呢,会产生大量不连续的内存碎片,空间碎片太多,可能会导致以后在程序运行过程中,需要分配较大的对象时,无法找到足够的连续内存,而不得不提前触发另一次垃圾收集动作。
  2)、复制算法(Copying),这种算法适用于对象存活率低的场景,采用此算法回收年轻代。
    a)、分为对象面和空闲面。复制算法将可用的内存按照容量和一定比例划分为两块或者多个块,并选择其中一块或者两块作为对象面,其他的就作为空闲面。
    b)、对象则是在对象面上创建的。当被定义为对象面的块的内存使用完之后,就将还存活着的对象复制到其中一块空闲面上。
    c)、存活的对象被从对象面复制到空闲面。就是还不是垃圾的对象复制到空闲面上。
    d)、将对象面所有对象内存清除,然后再将已使用过的内存空间一次清理掉。
    e)、复制算法解决碎片化的问题。顺序分配内存,简单高效。适用于对象存活率低的场景。
  3)、标记-整理算法(Compacting),适用于老年代的对象回收。标记-整理算法是在标记-清除算法的基础上又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
    a)、标记,从根集合进行扫描,对存活的对象进行标记。
    b)、清除,移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
    c)、标记-整理算法优点,避免内存的不连续性。不用设置两块内存互换。适用于对象存活率极高的场景。
  4)、分代收集算法(Generational Collector),主流的垃圾回收算法。
    a)、垃圾回收算法的组合拳。
    b)、按照对象声明周期的不同划分区域以采用不同的垃圾回收算法。将堆内存进行进一步划分,不同的对象的声明周期以及存活情况是不一样的,将不同生命周期的对象分配到堆中不同的区域,并对堆内存不同区域采用不同的策略进行回收。
    c)、分代收集算法目的,提高JVM的回收效率。

24、分代收集算法(Generational Collector)。

  1)、JDK6、JDK7的堆内存,分为年轻代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation)。
  2)、JDK8以及其以后的版本,的堆内存,分为年轻代(Young Generation)、老年代(Old Generation)。年轻代的对象存活率低就会采用复制算法,老年代对象存活率高,就会采用标记-清除算法或者标记整理算法。
 

25、分代收集算法(Generational Collector),GC的分类。

  1)、Minor GC,发生在年轻代中的l垃圾收集动作,所采用的复制算法,年轻代是几乎所有Java对象出生的地方,即Java对象申请的内存以及存放都是在年轻代的,Java中的大部分对象不需要长久的存活,具有朝生熄灭的性质。当一个对象被判断为死亡的时候,GC就有责任回收掉这部分对象的内存空间,新生代是GC收集垃圾的频繁区域。
  2)、Full GC,这种GC和老年代相关,由于对老年代的回收一般会伴随着年轻代的垃圾收集,因此,此种方式称为Full GC。


26、分代收集算法(Generational Collector),年轻代的介绍。

答:尽可能快速地收集掉那些生命周期短的对象。

  1)、Eden区,伊甸园,人类的起源,是对象刚被创建出来的时候,其内存空间首先是被分配在Eden区的,如若Eden区放不下新创建的对象的时候,对象也有可能直接放到Survivor区,甚至是老年代中。
  2)、两个Survivor区(幸存者空间),分别被定义为from区和to区,那个是fron区,那个是to区也不是固定的,会随着垃圾回收的进行而相互转换,年轻代的目标就是尽可能快速收集掉那些生命周期较短的对象,一般情况下,所有新生成的对象首先都是放在年轻代的。
  3)、年轻代的内存都会按照8-1-1的比例进行划分,绝大部分对象是在Eden区生成,新生代中98%的对象都是朝生夕死的,所以不需要按照1-1-1的比例来划分内存空间,而是8Eden-1from-1to区,每次使用Eden和其中的一块Survivor区,当进行垃圾回收的时候,将Eden和Survivor区存活的对象一次性复制到另一块Survivor区,最后清理掉Eden区和用过的Survivor区,当Survivor区空间不够用的时候,则需要依赖老年代,进行分配的担保了。

27、分代收集算法(Generational Collector),对象如何晋升到老年代。

  1)、经历一定Minor次数依然存活的对象。长期存活的对象会进入老年代,默认是每经过一次长一岁,达到Minor15岁的时候就晋升到老年代。
  2)、Survivor区中存放不下的对象。如果是Edea区或者Survivor区放不下的对象会直接进入老年代,对象优先是Edea区进行分配,当Edea区没有足够的空间分配的时候,会触发一次Minor GC,每次Minor GC结束Edea区就会被清空,因为它会把Edea区还依然存活的对象放到Survivor区中,当Survivor区中放不下的时候,则有分派担保进入到老年代中,因为不能放不下了就将它干掉。
  3)、新生成的大对象直接进入到老年代当中,(-XX:+PretenuerSizeThreshold),可以通过这个参数控制大对象的大小,如果超过这个参数的对象,一经生成直接放入到老年代中。

28、分代收集算法(Generational Collector),常用性能调优参数。

答:1)、-XX:SurvivorRatio : Eden和Survivor的比值,默认是8比1。
  2)、-XX:NewRatio : 老年代和年轻代内存大小的比例。新生代和老年代的内存大小由-Xms、-Xmx参数决定的。
  3)、-XX:MaxTenuringThreshold : 对象从年轻代晋升到老生代经过GC次数的最大阈值。

29、分代收集算法(Generational Collector),老年代。

答;老年代是存放生命周期较长的对象,在年轻代中经过了n次垃圾回收依然存活的对象就会被放到老年代中。
  1)、老年代的内存比新生代的内存大,大概比例是2比1。新生代使用的是复制算法,复制成本较低。
  2)、老年代对象存活率较高,没有额外空间分配担保,使用的算法是标记-清理算法、标记-整理算法进行回收。
  3)、当触发老年代的垃圾回收的时候,通常也伴随着对新生代堆内存的回收,即对整个堆进行垃圾回收,这便是所谓的Full GC。Major GC通常是和Full GC等价的,即收集整个GC堆。Full GC比Major GC慢,一般慢十倍以上,但执行频率低。
  4)、当Edea区空间不足的时候,会触发Minor GC回收年轻代的内存空间。什么情况下会触发Full GC内。

30、分代收集算法(Generational Collector),触发Full GC的条件。

  1)、老年代空间不足。如果创建大对象直接放入到老年代,如果老年代空间不足,就会触发Full GC。
  2)、永久代空间不足。针对JDK7以及之前的版本,当系统中需要加载的类,调用的方法很多,同时持久代中没有足够的空间去存放类,信息,方法的信息,就会触发一次Full GC。
  3)、CMS GC的时候出现promotion failed,concurrent mode failure。对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed,concurrent mode failure,当这两种情况发生的时候,可能会触发Full GC,promotion failed是在进行Minor GC的时候,Survivor空间是放不下了,对象只能放入到老年代,而此时老年代也放不下了,此时就会造成promotion failed。concurrent mode failure是在执行CMS GC的时候,同时有对象要放入老年代中,而此时老年代空间不足就会造成concurrent mode failure。
  4)、Minor GC晋升到老年代的平均大小大于老年代的剩余空间。
  5)、调用System.gc()。在程序中调用该方法,显示触发Full GC,对老年代和新生代进行回收,但是此方法提醒虚拟机需要在这里进行回收,但是回收不回收还是看虚拟机。
  6)、使用RMI来进行RPC或者管理的JDK应用,每小时执行1次Full GC。

31、分代收集算法(Generational Collector)关键词。

  1)、Stop-the-World,JVM由于要执行GC而停止了应用程序的执行。在任何一种GC算法中都会发生,当Stop-the-World发生时,除了GC所需的线程,所有线程都处于等待状态,直到GC任务完成。多数GC优化通过减少Stop-the-world发生的时间来提高程序性能,从而使系统具有高吞吐,低停顿的效果。
  2)、Safepoint,垃圾收集器里面的安全点。分析过程中对象引用关系不会发生变化的点,在可达性分析中,要分析那个对象没有引用的时候,必须在一个快照的状态点进行,在这个点所有的线程都被冻结了,不可以出现分析过程中对象引用关系还在不停变化的情况,因此分析结果需要在某个节点具备确定性,该节点便叫做安全点,程序不是那个点就停顿下来的,而是到达安全点才会停顿下来。产生Safepoint的地方是方法调用,循环跳转,异常跳转等等,一旦GC发生,所有的线程都跑到最新的安全点才会停顿下来,如果发现线程不在安全点,就恢复线程,等其跑到安全点再说。安全点数量得适中,安全点的数量不能太少,太少就会让GC等待太长时间,也不能太多,因为太多会增加程序运行的负荷。
 

32、常见的垃圾收集器,JVM的运行模式。

答:第一种是Server,第二种是Client。
  1)、Client启动速度较快,采用的是轻量级的虚拟机。
  2)、Server启动速度较慢,启动进入稳定器,长期运行之后,Server模式程序运行比Client快,这是因为Server模式采用的是重量级的虚拟机,对程序采用了更多的优化。
  3)、java -version可以查看是使用的那种运行模式。


33、常见的垃圾收集器,垃圾收集器之间的联系。

答:垃圾收集器和JVM实现紧密相关的,虚拟机所处的区域,说明它是属于新生代的收集器还是老年代的收集器,如果两个收集器之间有连线,就说明它们可以搭配使用。

34、年轻代常见的垃圾收集器,Serial收集器(-XX:+UseSerialGC,复制算法)。

答:1)、在程序启动的时候,通过设置UseSerialGC参数使得年轻代使用该垃圾收集器回收。Serial收集器是java最基本,历史最悠久的收集器,jdk1.3版本之前,年轻代收集器的唯一选择。
  2)、单线程收集,进行垃圾收集时,必须暂停所有工作线程。单线程的意义不仅仅是说明只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是,在它进行垃圾收集的时候,必须暂停其它所有工作线程,直到它收集结束。
  3)、简单高效,Client模式下默认的年轻代收集器。用户桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆,年轻代停顿时间会在几十毫秒到最多一百毫秒之间。

35、年轻代常见的垃圾收集器,ParNew收集器(-XX:+UseParNewGC,复制算法)。

答:1)、在程序启动的时候,通过设置UseParNewGC参数使得年轻代使用该垃圾收集器回收。
  2)、多线程收集,其余的行为,特点和Serial收集器一样。是Server模式下虚拟机年轻代首选的收集器。
  3)、单核执行效率不如Serial,因为存在线程交互开销,在多核下执行才有优势。默认开启的收集线程数和CPU数量相同,在CPU数量非常多的情况下,可以使用参数限制垃圾收集的线程数。
  4)、在Server模式下ParNew收集器是一个非常重要的收集器,因为除Serial收集器外,目前只有它能与CMS收集器配合工作。

36、年轻代常见的垃圾收集器,Parallel Scavenge收集器(-XX:+UseParallelGC,复制算法)。

答:1)、在程序启动的时候,通过设置UseParallelGC参数使得年轻代使用该垃圾收集器回收。
  2)、系统的吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。系统的吞吐量等于运行用户代码时间除以CPU总消耗时间的比值。
  3)、比起关注用户线程停顿时间,更关注系统的吞吐量。Parallel Scavenge收集器类似,ParNew收集器,使用多线程进行垃圾回收。停顿时间断适合用于用户交互的程序,良好的相应速度,可以提升用户的体验,高吞吐量则可以高效率利用CPU时间,尽可能快的完成运算任务,主要适合在后台运算,而不需要太多交互任务的这种情况。
  4)、在多核下执行才有优势,Server模式下默认的年轻代收集器。如果对垃圾收集器运作原理不熟悉,在优化过程中遇到困难了,可以使用-XX:+UseAdaptiveSizePolicy参数,配合自适应调节策略,即在启动参数中加入这个参数会把内存管理调优任务交给虚拟机去完成。

 

37、老年代常见的垃圾收集器,Serial Old(MSC)收集器(-XX:+UseSerialOldGC,标记-整理算法)。

答:1)、在程序启动的时候,通过设置UseSerialOldGC参数使得年轻代使用该垃圾收集器回收。
  2)、单线程收集,进行垃圾收集的适合,必须暂停所有工作线程。
  3)、简单高效,Client模式下默认的老年代收集器。

38、老年代常见的垃圾收集器,Parallel Old收集器(-XX:+UseParallelOldGC,标记-整理算法)。

答:1)、在程序启动的时候,通过设置UseParallelOldGC参数使得年轻代使用该垃圾收集器回收。
  2)、多线程,吞吐量优先,在jdk1.6之后才开始提供的。
  3)、在注重吞吐量和CPU资源敏感的场合,都可以优先考虑Parallel Old收集器 加Parallel Scavenge收集器。

39、老年代常见的垃圾收集器,CMS收集器(-XX:+UseConcMarkSweepGC,标记-清除算法)。

答:1)、在程序启动的时候,通过设置UseConcMarkSweepGC参数使得年轻代使用该垃圾收集器回收。
  2)、CMS收集器占据了老年代垃圾收集器的半壁江山,划时代的意义就是几乎可以做到垃圾回收线程集合可以和用户线程做到同时工作,几乎是因为还不能做到完全不需要停止用户线程的,只是尽可能的缩短了停顿时间,如果应用程序对停顿比较敏感,并且在应用程序运行的时候,可以提供更大的内存和更多的CPU,也就是更厉害的硬件,使用CMS来收集会带来好处,如果在JVM中有相对较多存活时间较长的对象,会更适合使用CMS。
  3)、CMS整个垃圾收集器的整个过程可以分为六步。

    a)、初始化标记,stop-the-world。在这个阶段需要虚拟机停顿正在执行的任务,这个过程从垃圾收集器的根对象开始,只扫描到能和根对象关联的对象并做标记,所以这个过程虽然暂停了整个JVM,但是很快就完成了
    b)、并发标记,并发追溯标记,程序不会停顿。紧随初始标记阶段,在初始标记的基础上继续向下追溯标记,并发标记阶段,并发标记的线程和用户执行的线程并发执行,所以程序不会停顿。
    c)、并发预清理,查找执行并并发标记阶段从年轻代晋升到老年代的对象。通过重新扫描,减少下个阶段重新标记的工作,因为下个阶段会stop-the-world。
    d)、重新标记,暂停虚拟机,扫描CMS堆中的剩余对象。这个过程从垃圾收集器的根对象开始向下追溯,并处理对象关联。
    e)、并发清理,清除垃圾对象,程序不会停顿。
    f)、并发重置,重置CMS收集器的数据结构。等待下一次垃圾回收。

  并发标记,也就是和用户线程同时工作,就是一边丢垃圾,一边打扫,这样就会带来如果垃圾的产生是在标记后发生的,那么这次垃圾就只能等待下次再回收了,当然等待垃圾标记了过后呢,垃圾自然不会和用户线程产生冲突,而清理过程就能和用户线程同时处理了,对于此类垃圾回收器,有一个比较显著不可避免的一个问题,就是它所采用的是标记-清除算法,也就是说它不会压缩存活的对象,这样就会带来内存空间碎片化的问题,如果出现需要分配一个连续的较大的n内存空间,则只能触发一次GC。

40、老年代常见的垃圾收集器,G1收集器(-XX:+UseG1GC,复制 + 标记-整理算法)。

答:1)、在程序启动的时候,通过设置UseG1GC参数使得年轻代使用该垃圾收集器回收。
  2)、即用于年轻代,又用于老年代的收集器。G1收集器的使命是未来替换掉JDK1.5发布的CMS收集器。
  3)、Garbage First收集器的特点。
    a)、并行和并发,使用多个CPU来缩短stop-the-world的停顿时间,与用户线程b并发执行。
    b)、分代收集,独立管理整个堆,能够采用不同的方式,去处理新创建的对象,和以及存在一段时间熬过多次GC旧对象,以获得更好的收集效果。
    c)、空间整合,基于标记-整理算法,这样就解决了内存碎片的问题。
    d)、可预测的停顿,可以建立可以预测的停顿时间模型,能让使用者明确指定在一个长度为m毫秒时间片段内,消耗在垃圾收集器上的时间不得超过m毫秒。
  4)、Garbage First收集器之前收集器收集的范围都是整个年轻代的,或者老年代的,Garbage First收集器Java堆的内存布局与其他收集器有很大的差别,将整个Java堆内存划分成多个大小相等的独立区域Region。
  5)、年轻代和老年代不再是物理隔离了,虽然保留了年轻代和老年代的概念。它们是一部分不再连续的Region的集合,这就意味着在分配空间的时候不需要连续的内存空间,即不需要再JVM启动的时候决定那些Region是属于老年代,那些属于年轻代。随着时间推移,年轻代Region被回收以后,就会变为可用状态,这个时候可以把它分配成老年代,Garbage First年轻代收集器是并行stop-the-world收集器,和其它的GC一样,当一次年轻代GC发生的时候,整个年轻代会被回收。G1的老年代收集器有所不同,它在老年代不需要整个老年代进行回收,只有一部分Region被调用,Garbage First GC的年轻代Edea Region、Survivor Region组成,JVM分配Edea Region失败之后就会触发一个年轻代回收,这意味着Edea 区间满了,GC开始释放空间,第一个年轻代收集器会移动所有的存储对象,从Edea Region到Survivor Region,这就是Copy-on-Survivor的过程。

41、Java中的强引用,软引用,弱引用,虚引用有什么用?

答:1)、强引用(Strong Reference),最普遍的引用,例如Object obj = new Object();这里new一个对象实例来,这里面的obj就是一个强引用。

    a)、如果一个对象具有强引用,当内存空间不足的时候,Java虚拟机宁可抛出OutOfMemoryError终止应用程序,也不会回收具有强引用的对象。
    b)、通过将对象设置为null来弱化引用,使其被回收。如果我们不使用这个对象了,需要通过将对象的引用设置为null方法来弱化引用,使其被回收,即将刚才的obj设置为null,或者等待它超过对象的生命周期范围,这个时候GC就认为该对象不存在引用了,就可以回收这个对象了具体什么时候收集,取决于系统了。
  2)、软引用(Soft Reference),表示一个对象处在有用但非必须的状态。

    a)、只有当内存空间不足的时候,GC会回收该引用的对象的内存。当内存空间充足的时候,GC就不会回收该对象。
    b)、软引用可以用来实现告诉缓存,可以实现内存敏感的高速缓存。不用太担心OutOfMemoryError的问题,因为软引用的对象内存会在内存不足的时候进行回收,同时由于一般情况下内存空间是充足的,相关对象就一直存在便于复用。软引用也可以和引用队列配合使用。

1 例如,String str = new String("abc");// 强引用,创建的对象实例赋值给强引用str 
2 SoftReference<String> softReference = new SoftReference<String>(str);// 软引用,使用SoftReference类型,泛型类型是String的,然后将强引用str包装起来,此时softReference就是软引用了。

  3)、弱引用(Weak Reference),用来描述非必须的对象,类似软引用,强度比软引用更弱一些。

    a)、弱引用具有更短的生命,GC时会被回收,GC在扫描的过程中,一旦发现有被弱引用关联的对象,就会将它回收了。换言之,无论此时内存是否紧缺,GC都将回收被弱引用关联的对象。
    b)、被回收的概率也不大,因为GC线程优先级比较低。由于垃圾回收是一个优先级很低的线程,因为不一定回很快发现那些子句有弱引用的对象。
    c)、适用于引用偶尔被使用且不影响垃圾收集的对象。用法和软引用一样,弱引用也可以和引用队列配合使用。

1 例如,String str = new String("abc");// 强引用,创建的对象实例赋值给强引用str 
2 WeakReference<String> weakReference = new WeakReference<String>(str);// 软引用,使用WeakReference类型,泛型类型是String的,然后将强引用str包装起来,此时weakReference就是弱引用了。

  4)、虚引用(Phantom Reference),顾名思义就是形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期。

    a)、任何时候都可能被垃圾收集器回收。如果一个对象仅持有虚引用,那么它就和没有任何引用一样。
    b)、跟踪对象被垃圾收集器回收的活动,起哨兵作用。
    c)、虚引用和软引用和弱引用的一个区别,就是必须和引用队列ReferenceQueue联合使用。GC在回收一个对象的时候,如果发现该对象具有虚引用,那么在回收之前会首先将该对象的虚引用加入到与之关联的引用队列当中,程序可以通过判断引用队列是否以及加入虚引用来了解被引用的对象是否被GC回收,因此起到一个哨兵的作用。

1 例如,String str = new String("abc");// 强引用,创建的对象实例赋值给强引用str 
2 ReferenceQueue queue = new ReferenceQueue();// ReferenceQueue对象 
3 PhantomReference weakReference = new PhantomReference(str,queue);// 虚引用

42、Java中的强引用,软引用,弱引用,虚引用的等级。

答:强引用 > 软引用 > 弱引用 > 虚引用。

引用类型 被垃圾回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时终止
软引用 在内存不足时 对象缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 GC运行后终止
虚引用 Unknown 标记、哨兵 Unknown

43、引用队列(ReferenceQueue)。

  1)、ReferenceQueue名义上是一个队列,但是无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达,Queue类似于一个链表的结构,这里的节点其实就是Reference本身,链表的容器,其自己只存储当前的head节点,而后面的节点由每个Reference节点自己通过next来保存即可。
  2)、存储关联的且被GC的软引用,弱引用以及虚引用,这三个引用都可以保存到引用队列里面,如果在创建一个引用对象的时候,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态的时候,GC会把引用对象本身添加到这个队列里面,方便我们处理它。


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

基于jsp + SSM的跳蚤管理系统知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。