面试中GC的常见问题

Java垃圾回收器:

可以在java虚拟机添加启动参数,从而达到选择合适的垃圾回收期
通过jmap -heap 进程数的方式可以查看当前采用的垃圾回收器类型

新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;

选择垃圾回收器考虑的因素
1.应用程序的场景
2.硬件的制约 :
3.吞吐量的需求

a.串行垃圾回收器 -XX:+UseSerialGC:单线程 “牵一发动全身”(配置比较低机器适用)
b.并行垃圾回收器 -XX:+UseParallelGC:多线程 (64位服务器默认垃圾回收器)
c.并发标记扫描垃圾回收器(CMS) -XX:+UseConcMarkSweepGC :利用多线程对需要回收的对象进行标记并回收。是对并行垃圾回收器的优化, 它以CPU和系统资源为代价,换取GC的延迟
d.CMS的优化 -XX:ParallelCMSThreads= :并发标记垃圾回收器使用的线程数,通常是cpu个数
e…G1垃圾回收器 -XX:+UseG1GC:对比较大的进行回收,可以先压缩再回收。

并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);
用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;

Minor GC:新生代GC,指的是在新生代发生的GC
Full GC:老年代GC, 指发生在老年代的GC
发生Full GC时,一般会发生至少一次的Minor GC,所以Full GC比Minor GC至少满10倍。

如何判断一个垃圾回收器的优劣
1.发生gc的停顿时间
2.产生空间碎片的大小(这里影响并发阶段的吞吐量)

垃圾回收的算法:

a.标记-清除算法:效率偏低
b.复制算法:效率高,但是占用2倍内存 (预留一块内存 将还存活的对象放到该内存)
c.标记-整理算法:效率偏低(是对标记-清除算法的改进,让存活的对象向一段移动)
d.分代收集算法:把Java堆分为新生代和老年代,根据年代将特征选择上述算法。
老年代通常使用:a、c
新年代通常使用:b
GC分为2个部分:
GC:收集新年代的区域
Full GC:收集 新年代和老年代的区域

注:
a.触发 Full GC的条件:持久代满了、老年代满了 、System.gc()
b.新年代满了,会放至到空闲的Survivor区,只有所有的Survivor区满了才会放到老年代。

既然有 GC 机制,为什么还会有内存泄露的情况:
存在无用但可达的对象,比如Hibernate中的Session,如果不及时flush()或者close(),可能引发内存泄露。
System.gc()和Runtime.gc()会提示JVM要进行垃圾回收。但是,立即回收还是延迟回收是取决于JVM的。

介绍强引用、软引用、弱引用、虚引用:

a.强引用: A a=new A() 只要引用a存在,垃圾回收器不会回收。
b.软引用:类似于缓存的方式,不影响垃圾回收,可以提升速度,节省内存。若对象被回收,
get()为null,此时可以重新new SoftReference
c.弱引用:用于监控对象是否被垃圾回收器回收 isEnQueued方法 WeakReference
d.虚引用:每次垃圾回收的时候都会被回收。主要用于对象是否已经从内存中删除。 通过IsEnQueued方法 PhantomReference

静态类型: 编译时即知道每一个变量的类型,因此,若存在类型错误编译是无法通过的。
动态类型: 编译时不知道每一个变量的类型,因此,若存在类型错误会在运行时发生错误。
JAVA 强类型、静态类型检查 静态显式

类加载机制:

类加载器

  • 根类加载器:用来加载java的核心类
  • 扩展类加载器:用来加载jre的扩展目录
  • 系统加载器:它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径

在这里插入图片描述

用户类在加载时不会首先去加载自己,而是先去加载其父类加载器。

类加载器的任务就是.class文件加载到到JVM转换成 java.lang,class类
类加载过程:装载、链接和初始化。

双亲委托模型(确保加载的唯一性):当类收到加载请求时,它首先不会尝试加载这个类,
而是把请求委托给父类加载器执行,每个类都是如此(如果还有父类继续上交),如果父
类加载不了,子类加载才会进行加载。

JAVA类加载的行为:(不包含对象成员部分,这些部分需要依托与实例化)
1.生成Java.lang.class对象
2.加载static模块
3.类方法(使用static修饰的方法)的解析

延伸 实例方法指的是普通的依托于对象存在的方法。
实例方法中:
1.可以调用本类的类方法(static) (静态的方法 不能调用非静态的)
2.可以调用父类 非private实例方法 ,被重写的使用super进行调用
3.实例方法 不能调用其他类的实例方法 (根据双亲委托模型 其他类不一定进行了类加载)
4.实例方法可以调用 超类非private的类方法

内存泄露和内存溢出

异常堆栈信息“java.lang.OutOfMemoryError:java heap space”

内存泄漏:指的是程序在动态分片一些临时对象,但是对象不会被GC正常回收。它始终占用内存。即对象可达但已无用(诸如缓存)
内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。
所以内存泄漏只是内存溢出的一个可能诱发原因。

内存泄漏的常见场景
1.长生命周期的对象持有短生命周期对象的引用。(这是最常见的情况)
2.修改hashset对象的参数值,且参数是计算hash值得字段(这个hash对象已经不可获取,但又没正常被gc)
3.机器的连接数和关闭时间设置(可以联想平安系统在连接多个数据库连接时,包内存溢出问题)

内存溢出的常见场景
1.堆内存溢出:当频繁生产新新对象时,内存占用量超出jvm分配的内存。
2.方法区溢出:程序加载的类过多,或者使用反射等动态代理生成类的技术,就可能导致该区发生内存溢出。
3.线程栈溢出:一般线程栈溢出,是由于递归太深或者方法调用层级过多导致的。

在生产环境如何去解决内存溢出的问题
在jar的启动参数添加: -XX:+heapDumpOnOutofMemoryError
可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

手段:通过内存映射分析工具(如Eclipse Memory Analyzer)
核心:通过判断内存中的对象是否是必要来判断是不是内存泄漏(即这对象是否是可达有用的)

解决问题的思路
内存泄漏:通过内存映射分析工具,查看泄漏对象到GC Roots的引用链。从而准确地定位出泄漏代码的位置。
内存溢出:检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比是否可以调大。同时从代码是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

发布了226 篇原创文章 · 获赞 40 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_40990818/article/details/104609702
今日推荐