- 元空间会产生内存溢出吗?在什么情况下会产生内存溢出
既然说的是元空间,那么讲的就是jdk1.8的方法区内存。我们前面的阐述中说到jdk1.8使用的元空间不再是JVM分配的内存空间,是JVM的进程空间。
JAVA启动时 默认Metaspace设置的值为-1,就是可以无限大的(受限于机器的物理内存)。
如果设置了Metaspace空间的大小,在超过设置的内存大小时会产生内存溢出。主要原因是加载到内存中的class文件体积太大造成的。
我们为了模拟元空间溢出,编写如下代码:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Mytest {
public static void main(String[] args) {
int i=0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DummyClass.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
//@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
System.out.println(i);
}
} catch (Exception e) {
System.out.println("=================多少次后发生异常?"+i);
e.printStackTrace();
}
}
static class DummyClass {
}
}
并在启动时设置元空间的大小,结果如下图所示:
2 简单说说java的运行机制
2.1 首先通过JRE把java源文件编译成.class文件
2.2 java命令启动,分配好JVM的内存空间并把class文件加载到元空间
2.3 不同的JVM具有同一套解释执行的指令集,解释执行加载到元空间的命令
3 hotspot方法区的变迁
jdk1.6及之前方法区的实现为永久代,静态变量存放在永久代中,字符串常量池(StringTable)位于运行时常量池中。
jdk1.7方法区的实现为永久代,但已经逐步“去永久代”,静态变量、字符串常量池移除,保存在堆中
jdk1.8方法区的实现为本地内存的元空间,字符串常量池、静态变量仍在堆中
4 为什么用元空间取代永久代
因为永久代设置的最大空间很难界定,设置过大会浪费内存。放在元空间用的本地内存,占用的内存空间就是实际使用的内存空间
在某些场景下,如果动态加载类过多,容易产生Perm区的OOM:比如某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误;而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的最大大小仅受本地内存限制。
堆永久代的调优比较困难
5 为什么调整字符串常量池的位置
JDK7中将字符串常量池放到了堆空间中:因为永久代的回收效率很低,在Full GC时才会触发,而FullGC在老年代的空间不足、永久代不足时才会触发,这就导致字符串常量池回收效率不高;而我们开发中会有大量的字符串被创建,回收效率低会导致永久代内存不足。将字符串常量池放到堆里,能及时回收内存。
6 什么是执行引擎
执行引擎是 java 虚拟机的最核心组件之一,它负责执行虚拟机的字节码,有即时编译和解释执行,通常采用解释执行方式。解释执行是指解释器通过每次解释并执行一小段代码来完成.class程序的所有操作。即时编译则是将.class文件翻译成机器码在执行(比如:经常多次访问的代码可以全部编译)垃圾回收系统是 java 虚拟机的重要组成部分,垃圾回收器可以对 栈 堆进行回收。其中, java 堆是垃圾收集器的工作重点。有三类:增量垃圾回收,分代复制垃圾回收,标记垃圾回收和 C/C++不同, java 中所有的对象空间释放都是隐式的,也就是说, java 中没有类似 free()或者delete()这样的函数释放指定的内存区域。对于不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括 java 堆、方法区和直接内存中的全自动化管理。
7 java的深拷贝和浅拷贝
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
8 说下堆栈的区别
物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存分别
堆因为是不连续的,所以分配的内存是在 运行期 确认的,因此大小不固定。一般堆大小远远大于栈。
栈是连续的,所以分配的内存大小要在 编译期 就确认,大小是固定的。
存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
1. 静态变量放在方法区
2. 静态的对象还是放在堆。
程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同
9 java会产生内存泄漏吗?
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。
但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
10 什么时候会产生栈内存溢出
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)
参数 -Xss 去调整JVM栈的大小
这里我们先分享面试中遇到的这些问题 ,后面继续分享
本文暂时没有评论,来添加一个吧(●'◡'●)