这是一篇看了能说的出来的jvm面试;个人能力有限,文中描述难免有错误,请指正;
jvm会将运行程序所管理的空间分为若干部分,每个部分都起到至关重要的部分;jdk1.8java运行时数据区如下:
程序计数器
:当前线程执行字节码的行号指示器;字节码解析器通过改变计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能;
Java 虚拟机栈
: java方法执行的内存模型,每个方法被执行的时候都会创建帧栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
本地方法栈
: 执行java的native方法服务,每个方法被执行的时候都会创建帧栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
Java 堆
: 储存对象的实例和数组;
方法区
: 非堆,储存类的结构信息;例如运行时常量池的字段和方法数据,构造函数和普通方法的字节码内容等;
科普运行时常用池:运行时常用池是class文件中每一个类或者接口的常量池表的运行表现形式,包括若干种常量,如字段和方法的引用;在类加载至虚拟机的时候就会创建运行时常量池;
科普帧栈:帧栈用于储存数据和部分过程结果的数据,同时也会处理动态链接,方法返回值和分派;帧栈随着方法的创建而创建,随着方法的销毁而销毁;帧栈中维护着本地变量表,操作数栈,和指向当前方法所属类的运行常量池的引用;
注意:**jdk1.8已经使用元空间替代了jdk1.7方法区中的永久代,元空间存在于native内存中,其大小根据本地内存而定,没有限制;**详细看官网地址
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref66
类的加载过程分为 加载,链接,初始化,使用,和卸载5个阶段,其中连接阶段又分为验证 准备和解析阶段;
内存溢出:申请了8个字节的空间,但是你在这个空间写入9或以上字节的数据,出现内存溢出;
在java内存模型中只有程序计数器不会发生OOM(out of memory),其余地区都会发生OOM;
当一个类收到了类加载请求时,自己不会先去加载这个类,而是将其委派给父类去加载,如果父类不能加载,反馈给子类,由子类去完成类的加载;
这边有可能会问,你都知道哪些类加载器:
如何打破双亲委派模型?
双亲委派模型都依靠loadClass()
,重写loaderClass()
即可;
新生代分为 3 个分区:Eden(伊甸园)、Survivor1、Survivor2;其中Survivor1、 Survivor2 合起来成为Survivor(幸存区); 如果没有Survivor,Eden区每进行一次Minor GC
,存活的对象都会被送到老年代。老年代将很快被填满,老年代每发生一次Full GC
的速度比 Minor GC
慢10倍;
Survivor 的作用就是减少老年代
Full GC
的次数,相当于缓冲带;Eden和Survivor的比例分配8:1;
官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html#sthref16
大多数情况下对象都优先直接分配在Eden ,当Eden 空间不足时会发生Minor GC, 每经过一次Minor GC,年龄+1,若年龄超过15,则被进入到老年态。当老年代空间不足时就会发生 Full GC;
长期存活对象将进入老年代
如果是大对象(需要大量连续内存空间的对象)直接进入老年代 ;
会出现的小问题就是:Minor GC和Full GC触发条件,答默认情况下发生15次Minor GC之后就会触发一次Full GC
GC 是垃圾收集(GabageCollection);Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,而不需要人为手动释放内存;主要调用的是 System.gc() 和 Runtime.getRuntime().gc();
CMS(Concurrent Mark Sweep)收集器基于标记—清除算法
实现的收集器,是一种以获取最短回收停顿时间为目标的收集器。主要优点是并发收集,低停顿,在cpu多核的情况下性能较好。在启动 JVM 的参数加上
“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器;其使用在老年代 可以配合新生代的Serial和ParNew收集器一起使用;由于 CMS 使用 标记—清除算法
GC时会产生大量碎片,有可能提前触发Full GC;如果在老年代充满之前无法回收不可达对象,或者没有足够的空间满足分配就会导致Concurrent Mode Failure(并发模式故障);
官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html
G1(Garbage-First)从整体来看是基于标记—整理
算法实现的收集器,能够实现并发并行,对cpu利用率较高,减少停顿时间。目标是取代jdk1.5发布的CMS收集器。G1收集器收集范围是老年代和新生代
,不需要结合其他收集器使用,G1收集器可预测垃圾回收的停顿时间,对空间进行整合;由于G1是基于复制算法实现,当没有足够的空间(region)分配存活的对象就会导致Allocation (Evacuation) Failure(分配失败);
官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection
官网:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref27
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。Stop-The-World对系统性能存在影响,因此垃圾回收的一个原则是尽量减少“Stop-The-World”的时间;
标记-清除法
:标记出没有用的对象,之后一个一个回收掉
复制算法
: 按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉
标记-整理法
:标记出没有用的对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的对象
分代回收
:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法
收集器 | 收集范围 | 算法 | 执行类型 |
---|---|---|---|
Serial | 新生代 | 复制 | 单线程 |
ParNew | 新生代 | 复制 | 多线程并行 |
Parallel Scavenge | 新生代 | 复制 | 多线程并行 |
Serial Old | 老年代 | 标记整理 | 单线程 |
Parallel Old | 老年代 | 标记整理 | 多线程 |
CMS | 老年代 | 标记清除 | 多线程并发 |
G1 | 新生代、老年代 | 复制,标记整理 | 多线程 |
内存泄漏:new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实例所持有导致 GC 不能回收;
经典案例
void test(){
Vector vector = new Vector();
for (int i = 1; i<100; i++)
{
Object object = new Object();
vector.add(object);
object = null;
}
//...对vector的操作
//...与vector无关的其他操作
}
手动解决赋值null即可
void test(){
Vector vector = new Vector();
for (int i = 1; i<100; i++)
{
Object object = new Object();
vector.add(object);
object = null;
}
//...对vector的操作
vector = null;
//...与vector无关的其他操作
}
还有各种流和socket的close方法未被调用也会发生内存泄漏问题;
不会,在下一个垃圾回收周期中回收对象。
如果你对文中的知识点不太理解 推荐阅读周志明先生《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》和作者给出的官网链接!!!!