概述
Java内存区域(运行时数据区JMM)
共享的:
方法区:类的共有属性、静态属性。 (JDK1.7存放在持久代 JDK1.8存放在元空间,放在本地内存而不是JVM内存中,这样不受GC影响 )
JVM堆:对象、数组、常量
隔离的:(双栈是隔离的)
本地方法栈:Natitve 方法
虚拟机栈(JVM方法栈):局部变量区、操作数栈、动态连接(方法调用过程的动态连接)、方法返回地址(可以理解为一个类方法的运行区域)。
程序计数寄存器(PC寄存器): 用于记录正在执行的虚拟机字节序列的行指示器。
注:每一个线程都会生成PC寄存器和虚拟机栈。
Java垃圾回收器:
可以在java虚拟机添加启动参数,从而达到选择合适的垃圾回收期
通过jmap -heap 进程数的方式可以查看当前采用的垃圾回收器类型
新生代收集器:Serial、ParNew、Parallel Scavenge( 可控制的吞吐量); (复制算法)
老年代收集器:Serial Old、Parallel Old、CMS; (1、2 采用标记整理算法 3采用标记清除算法)
整堆收集器:G1;(标记整理,分区);
应用场景:
注重吞吐量以及CPU资源敏感的场景 (1.7、1.8默认):Parallel Scavenge+Parallel Old
与用户交互较多的场景:ParNew+CMS
选择垃圾回收器考虑的因素
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:是清理新生代
MajorGC:是指清理老年代
Full GC:清理新生代和老年代GC
发生Full GC时,一般会发生至少一次的Minor GC,所以Full GC比Minor GC至少慢10倍。
注:
a.触发 Full GC的条件:老年代满了、持久代满了、System.gc()都会引起Fullgc
b.新年代满了,会放至到空闲的Survivor区,只有所有的Survivor区满了才会放到老年代。
c.持久代是方法区,Class在被加载的时候被放入永久代。 如果你的应用程序会加载很多Class的话,就很可能出现PermGen space错误。
d.Survivor区的作用是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,
只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
如何判断一个垃圾回收器的优劣
1.发生gc的停顿时间
2.产生空间碎片的大小(这里影响并发阶段的吞吐量)
垃圾回收的算法:
a.标记-清除算法:效率偏低
b.复制算法:效率高,但是占用2倍内存 (预留一块内存 将还存活的对象放到该内存)
c.标记-整理算法:效率偏低(是对标记-清除算法的改进,让存活的对象向一段移动)
d.分代收集算法:把Java堆分为新生代和老年代,根据年代将特征选择上述算法。
老年代通常使用:a、c
新年代通常使用:b
GC分为2个部分:
GC:收集新年代的区域
Full GC:收集 新年代和老年代的区域
既然有 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),与机器物理内存对比是否可以调大。同时从代码是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
最后
以上就是勤劳哈密瓜为你收集整理的面试中GC的常见问题的全部内容,希望文章能够帮你解决面试中GC的常见问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复