前言 我坚信,机会永远属于有准备的人,我们与其羡慕他人的成功,不如从此刻起,积累足够多的知识和面试经验,为将来进入更好的公司做好充分的准备!想让面试官在短短的几十分钟内认可你的能力?想在最短的时间内收获 Java 技术栈最核心的知识点?想要更全面更深入的了解 Java 技术?这篇文章给你想要的所有答案。
小编分享的这份Java后端开发面试总结包含了JavaOOP、Java集合容器、Java异常、并发编程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL数据库、消息中间件MQ、Dubbo、Linux、ZooKeeper、 分布式&数据结构与算法等25个专题技术点,都是小编在各个大厂总结出来的面试真题,已经有很多粉丝靠这份PDF拿下众多大厂的offer,今天在这里总结分享给到大家!【已完结】
完整版Java面试题地址:2021最新面试题合集集锦 。
| 序号 | 专题 | 内容 | 链接 |
|:----|:----|:----|:----|
| 1 | 中间件 | Java中间件面试题(2021最新版) | https://cloud.tencent.com/developer/article/1810657 |
| 2 | 微服务 | Java微服务面试题(2021最新版) | https://cloud.tencent.com/developer/article/1811218 |
| 3 | 并发编程 | Java并发编程面试题(2021最新版) | https://cloud.tencent.com/developer/article/1812085 |
| 4 | Java基础 | Java基础知识面试题(2021最新版) | https://cloud.tencent.com/developer/article/write/1812831 |
| 5 | Spring Boot | Spring Boot面试题(2021最新版) | https://cloud.tencent.com/developer/article/1813377 |
| 6 | Redis | Redis面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814536 |
| 7 | Spring MVC | Spring MVC面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814561 |
| 8 | Spring Cloud | Spring Cloud面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814682 |
| 9 | MySQL优化 | MySQL优化面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814683 |
| 10 | JVM | JVM性能调优面试题(2021最新版) |https://cloud.tencent.com/developer/article/1814684 |
| 11 | Linux | Linux面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814821 |
| 12 | Mybatis | Mybatis面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814879 |
| 13 | 网络编程 | TCP,UDP,Socket,Http网络编程面试题(2021最新版) | https://cloud.tencent.com/developer/article/1814881 |
| 14 | 设计模式 | 设计模式面试题(2021最新版) |https://cloud.tencent.com/developer/article/1816736 |
| 15 | 大数据 | 大数据面试题100道(2021最新版) |https://cloud.tencent.com/developer/article/1818351 |
| 16 | Tomcat | Tomcat面试题(2021最新版) | https://cloud.tencent.com/developer/article/1818417 |
| 17 | 多线程 | 多线程面试题(2021最新版) | https://cloud.tencent.com/developer/article/1818432 |
| 18 | Nginx | Nginx\_BIO\_NIO\_AIO面试题(2021最新版) | https://cloud.tencent.com/developer/article/1818676 |
| 19 | memcache | memcache面试题(2021最新版) | https://cloud.tencent.com/developer/article/1819122 |
| 20 | java异常 | java异常面试题(2021最新版) | https://cloud.tencent.com/developer/article/1819397 |
| 21 | Java虚拟机 | Java虚拟机面试题(2021最新版) | https://cloud.tencent.com/developer/article/1820065 |
| 22 | Java集合 | Java集合面试题(2021最新版) | https://cloud.tencent.com/developer/article/1820151 |
| 23 | Git常用命令 | Git常用命令(2021最新版) | https://cloud.tencent.com/developer/article/write/1820273 |
| 24 | Elasticsearch | Elasticsearch面试题(2021最新版) | https://cloud.tencent.com/developer/article/1820285 |
| 25 | Dubbo | Dubbo面试题(2021最新版) | https://cloud.tencent.com/developer/article/1820302 |
一、Java内存模型 1. 我们开发人员编写的Java代码是怎么让电脑认识的 2. 为什么说java是跨平台语言 这个夸平台是中间语言(JVM)实现的夸平台 Java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统 3. Jdk和Jre和JVM的区别 Jdk包括了Jre和Jvm,Jre包括了Jvm Jdk是我们编写代码使用的开发工具包 Jre 是Java的运行时环境,他大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库 Jvm俗称Java虚拟机,他是java运行环境的一部分,它虚构出来的一台计算机,在通过在实际的计算机上仿真模拟各种计算机功能来实现Java应用程序 看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM 4. 说一下 JVM由那些部分组成,运行流程是什么? 5. 说一下 JVM 运行时数据区 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;为什么要线程计数器?因为线程是不具备记忆功能 Java 虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;栈帧就是Java虚拟机栈中的下一个单位 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码 Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存; 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。 6. 详细的介绍下程序计数器?(重点理解) 7. 详细介绍下Java虚拟机栈?(重点理解) Java虚拟机是线程私有的,它的生命周期和线程相同。 虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行的同时 都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 解释 :虚拟机栈中是有单位的,单位就是栈帧 ,一个方法一个栈帧 。一个栈帧 中他又要存储,局部变量,操作数栈,动态链接,出口等。
8. 你能给我详细的介绍Java堆吗?(重点理解) java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。 从内存回收角度来看java堆可分为:新生代和老生代。 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。 9. 能不能解释一下本地方法栈? 本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法 native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。 同理可得,本地方法栈中就是C和C++的代码 10. 能不能解释一下方法区(重点理解) 方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。 11. 什么是JVM字节码执行引擎 虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。 “虚拟机”是一个相对于“物理机”的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎- 编译成机器码后才可在物理机上执行。 12. 你听过直接内存吗? 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。 我的理解就是直接内存是基于物理内存和Java虚拟机内存的中间内存 13. 知道垃圾收集系统吗? 程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。 垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理 有一部分原因就是因为Java垃圾回收系统的强大导致Java领先市场 14. 堆栈的区别是什么? 15. 深拷贝和浅拷贝 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址, 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存, 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。 深复制:在计算机中开辟一块新的内存地址 用于存放复制的对象。 16. Java会存在内存泄漏吗?请说明为什么? 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收 ,这就是java中内存泄露的发生场景。 二、垃圾回收机制及算法 17. 简述Java垃圾回收机制 在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。 18. GC是什么?为什么要GC GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。 19. 垃圾回收的优点和缺点 优点:JVM的垃圾回收器都不需要我们手动处理无引用的对象了,这个就是最大的优点 缺点:程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。 20. 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收? 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。 可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。 21. JVM 中都有哪些引用类型? 强引用:发生 gc 的时候不会被回收。 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。 弱引用:有用但不是必须的对象,在下一次GC时会被回收。 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。 22. 怎么判断对象是否可以被回收? 23. Full GC是什么 清理整个堆空间—包括年轻代和老年代和永久代 因为Full GC是清理整个堆空间所以Full GC执行速度非常慢,在Java开发中最好保证少触发Full GC 24. 对象什么时候可以被垃圾器回收 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。 25. JVM 垃圾回收算法有哪些? 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。 26. JVM中的永久代中会发生垃圾回收吗 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
三、垃圾收集器以及新生代、老年代、永久代 27. 讲一下新生代、老年代、永久代的区别 28. Minor GC、Major GC、Full GC是什么 Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾) Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清楚法和标记整理法) Full GC是清理整个堆空间,包括年轻代和老年代 29. Minor GC、Major GC、Full GC区别及触发条件 30. 为什么新生代要分Eden和两个 Survivor 区域? 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。 Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次MinorGC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor spaceS1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生) 31. Java堆老年代( Old ) 和新生代 ( Young ) 的默认比例? 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。 其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域 ,Edem 和俩个Survivor 区域比例是 = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ), 但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。 32.为什么要这样分代: 33. 什么是垃圾回收器他和垃圾算法有什么区别 垃圾收集器是垃圾回收算法(标记清楚法、标记整理法、复制算法、分代算法)的具体实现,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。 34. 说一下 JVM 有哪些垃圾回收器? 如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。 Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效; ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现; Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景; Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本; Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本; CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。 G1(Garbage First)收集器 ( 标记整理 + 复制算法来回收垃圾 ): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。 35. 收集器可以这么分配?(了解就好了) Serial / Serial Old
Serial / CMS
ParNew / Serial Old
ParNew / CMS
Parallel Scavenge / Serial Old
Parallel Scavenge / Parallel Old
G1
36. 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别? 新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器:G1 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。 37. 简述分代垃圾回收器是怎么工作的? 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下: 把 Eden + From Survivor 存活的对象放入 To Survivor 区; 清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。 四、内存分配策略 38. 简述java内存分配与回收策率以及Minor GC和Major GC 五、虚拟机类加载机制 42. 简述java类加载机制? 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。 43.类加载的机制及过程 程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 44. 描述一下JVM加载Class文件的原理机制 45. 什么是类加载器,类加载器有哪些? 46. 说一下类装载的执行过程? 47. 什么是双亲委派模型? 六、JVM调优 48. JVM 调优的参数可以在那设置参数值 49. 说一下 JVM 调优的工具? 50. 常用的 JVM 调优的参数都有哪些? #常用的设置
-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。 -Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。 -Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。 -XX:NewSize=n 设置年轻代初始化大小大小 -XX:MaxNewSize=n 设置年轻代最大值 -XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4 -XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8 -Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。 -XX:ThreadStackSize=n 线程堆栈大小 -XX:PermSize=n 设置持久代初始值 -XX:MaxPermSize=n 设置持久代大小 -XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。 #下面是一些不常用的 -XX:LargePageSizeInBytes=n 设置堆内存的内存页大小 -XX:+UseFastAccessorMethods 优化原始类型的getter方法性能 -XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用 -XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动 -XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用 -Xnoclassgc 是否禁用垃圾回收 -XX:+UseThreadPriorities 使用本地线程的优先级,默认启用 51. JVM的GC收集器设置 -xx:+Use xxx GC xxx 代表垃圾收集器名称 -XX:+UseSerialGC:设置串行收集器,年轻带收集器 -XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统 配置自行设置,所以无需再设置此值。 -XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量 -XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0 支持对年老代并行收集。 -XX:+UseConcMarkSweepGC:设置年老代并发收集器 -XX:+UseG1GC:设置 G1 收集器,JDK1.9默认垃圾收集器