以下是笔者总结的Java笔试面试题中JVM与内存部分,答案为笔者自己总结得出,未必完全正确,仅供参考
用于运行Java程序,进行垃圾回收和内存分配
JRE是Java运行环境,由JVM,核心内库和支持文件组成。JDK是Java开发工具包,由JRE、编译器和其它工具组成,可以让开发者开发、编译、执行Java应用程序
(1) 共享区域:堆,方法区(又称永久代或元数据区)
(2) 独享区域:本地方法栈,虚拟机栈,程序计数器
堆和栈都是内存中用于存放数据的地方
(1) 堆中存储的是运行时的对象。栈存储的是基本数据类型变量和对象引用
(2) 堆主要用来存放对象。栈主要用来执行程序
(3) 栈的存取速度比较快,但是大小和生存周期是确定的。堆可以在运行时动态分配内存,但是存取速度慢
GC叫垃圾回收,主要作用是回收程序中不再使用的内存
(1) 完成内存的分配
(2) 确保被引用对象内存不会被错误回收
(3) 回收不再被引用对象占用的空间
(1) 标记-清除算法。先标记所有需要回收的对象,然后统一进行清除。该算法简单,但是效率太低,并且容易产生大量不连续的碎片
(2) 复制算法。先将内存划分为大小相同的两块,只使用其中的一块,当这一块用完了,就把存活的对象复制到另一块上,已使用的一次清理。需要占用两倍空间
(3) 标记-整理(标记-清除-压缩)算法。跟标记-清除过程类似,只是增加了一步,清理完垃圾后,对存活的对象进行整理,使得空间连续。要做三步操作,比较耗时
Java的垃圾回收机制可以不用关心内存动态分配和垃圾回收的问题,垃圾回收就是释放“垃圾”占用的空间。不能实时的调用垃圾回收器进行垃圾回收,可以手动调用System.gc(),但不保证一定执行
(1) 引用计数法。这种方法是给对象添加引用计数器,有一个地方引用则加1,失效则减1,计数器为0时就回收,这种方法虽然简单,而且效率高,但是无法解决无限循环的问题
(2) 可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行深度搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的
(1) 虚拟机栈中引用的对象
(2) 方法区中类静态属性引用的对象
(3) 方法区中常量引用的对象
(4) 本地方法栈中JNI引用的对象
(1) 新生代。分为一个Eden区和两个Survivor区,默认情况下比例是8:1:1,两个Survivor区每次只使用一块,垃圾收集采用复制算法,默认占堆区1/3
(2) 老年代。垃圾收集采用标记-整理算法,默认占堆区2/3
(1) 当Eden区满了,新的对象没有可分配空间,即触发Minor GC。经过一次Minor GC后,存活的对象将被复制到其中一个Survivor区。下一次Minor GC到来时,Survivor区中存活的对象将被复制到另一个Survivor区。如果超过一定次数(默认16次)Minor GC后对象仍然存活,那么将被复制到老年代
(2) 当老年代或者永久代的空间满了,就会触发Full GC,这时会对整个堆区进行清理
(1) 对象优先在Eden区分配
(2) 大对象直接进入老年代
(3) 经过一次Minor GC没有被回收的进入Survovir区,长期存活对象进入老年代
(1) Serial收集器(串行收集器),最基本、最古老的收集器,是单线程的收集器,在使用它进行垃圾收集时,要先暂停所有工作线程,直到它收集结束
(2) ParNew收集器(并发收集器),是Serial收集器的多线程版本
(3) Parallel Scavenge收集器(并行收集器),是一个新生代收集器,使用复制算法的收集器,以获取最大吞吐量为目标的收集器
(4) Serial Old收集器,是Serial收集器的老年代版本,使用标记-整理算法
(5) Parallel Old收集器,是Parallel Scavenge收集器的老年代版本,使用“标记-整理”算法
(6) CMS收集器,一种以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法,CMS收集器的内存回收过程与用户线程一起并发执行
(7) G1收集器,是一种面向服务端应用的收集器,分代收集
重点掌握Parallel Scavenge收集器,CMS收集器和G1收集器
(1) 隐式加载。通过new关键字创建对象时,会隐式调用类加载器把类加载到JVM中
(2) 显式加载。通过反射调用则是显式加载
(1) 加载。①通过全类名获取class文件二进制字节流 ②把静态存储结构转化为方法区运行时数据结构 ③在内存中生成类的java.lang.Class对象
(2) 验证。对文件格式,元数据,字节码,符号引用进行验证,确保Class文件没问题,不危害到虚拟机
(3) 准备。为类变量分配内存并设置类变量初始值
(4) 解析。将常量池内符号引用替换为直接引用
(5) 初始化。开始执行程序的字节码
(1) 遇到new、getstatic、putstatic、invokestatic字节码指令时,类还没初始化,则进行初始化
(2) 类在反射调用时
(3) 初始化一个类时,父类还没初始化,则初始化父类
(1) 强引用。垃圾收集器永远不会回收。内存不足抛出OutOfMemoryError
(2) 软引用。内存足够时不会回收,当内存不足时才会回收。软引用可以用来实现缓存
(3) 弱引用。无论内存是否足够都会进行回收。第一次GC进行标记,第二次GC就回收。被标记后变为虚引用
(4) 虚引用。只要进行GC就回收
(1) 堆内存溢出。新创建的对象在堆中没有内存可以分配了,比如集合中存储太多的数据
(2) 栈内存溢出。虚拟机在拓展栈时无法申请到足够的内存,比如递归没有控制好出口
(3) 方法区溢出。定义太多的静态变量或者太多的字符串常量
(4) 直接内存溢出。比如定义一个int类型数据用来存储long型的数据
(1) 内存中加载的数据量太大,比如从数据读取太多的数据
(2) 集合中的对象用完后未及时清空
(3) 存在死循环或者循环过程产生过多的对象
(4) 启动参数内存值太小
(1) 修改JVM参数,-Xmx和-Xms
(2) 检查错误日志
(3) 使用内存查看工具(如Jconsole)观察内存的变化
(1) 静态集合类不能及时释放内存
(2) 各种连接不能及时释放。比如数据库连接、网络连接、IO连接
(3) 使用太多的监听器
(4) 变量不合理的作用域
(5) 使用单例模式可能造成内存泄漏
(1) 避免循环语句中创建对象
(2) 尽量少用静态变量
(3) 对字符串操作尽量使用StringBuffer或StringBuilder
内存溢出是对象申请内存的时候发现没有足够的空间
内存泄漏是不再使用的对象无法释放已占有的内存空间
(1) 对象不再使用时最好显式置null
(2) 尽量少用System.gc()
(3) 尽量少用静态变量
(4) 尽量使用StringBuffer或StringBuilder的append()方法来添加字符串,不要直接使用+拼接
(5) 分散对象的创建或删除时间
(6) 尽量使用基本数据类型,少用包装类型
(7) 增大-Xmx的值
Java内存分为主内存和工作内存
所有变量存储在主内存,每条线程还有自己的工作内存,工作内存中保存了主内存的变量副本的拷贝。线程所有操作都在工作内存进行,不能直接读取主内存中的变量,不同线程中也不能直接访问对方工作内存中的变量。可以使用volatile关键字强制从主内存中读取变量
STW(Stop The World)是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互。这些现象是由于gc引起,只要发生GC就会STW
-Xms:设置堆的初始值
-Xmx:设置堆的最大值
-Xmn:设置新生代的大小
-Xss:设置每个线程的栈大小
-XX:NewSize:设置新生代的初始值
-XX:MaxNewSize:设置新生代的最大值
-XX:PermSize:设置永久代的初始值
-XX:MaxPermSize:设置永久代的最大值
-XX:SurvivorRation:设置新生代中Eden区与Survivor区的比值