1. 什么情况下会发生栈内存溢出/h2>
-
栈是线程私有的,栈的生命周期和线程是一样的,每一个方法执行的时候,都会创建一个栈帧,它包含了局部变量表,操作数栈,动态链接方法的出口等等。局部变量表又包含基本数据类型和对象的引用。
-
当线程请求超过虚拟机允许的最大深度的时候,就会 栈内存溢出。
栈是线程私有的,栈的生命周期和线程是一样的,每一个方法执行的时候,都会创建一个栈帧,它包含了局部变量表,操作数栈,动态链接方法的出口等等。局部变量表又包含基本数据类型和对象的引用。
当线程请求超过虚拟机允许的最大深度的时候,就会 栈内存溢出。
? 比如:方法递归调用的时候就会触发栈内存溢出。
? 解决:
? 可以通过调整参数来调整JVM的栈的大小。
2.说说JVM的内存模型/h2>
本地方法栈的线程私有的,保存的是native的方法的相关信息。当JVM创建一个线程,调用一个native的方法之后,JVM不会为该方法在虚拟机中创建栈帧的,而是简单的动态链接,并直接调用这个方法。
堆是所有线程共享的最大块的内存,几乎所有对象的实例和数组都在堆上分配内存,所以这个区域会经常发生一些垃圾回收的操作。
方法区存放的是已被加载的类信息(类名等),常量、静态变量、即时编译器编译之后的代码数据。在JDK1.8中就不存在方法区了,而是叫元数据区,元数据区被分为两个部分,第一部分是加载类的信息,第二部分是运行时的一些常量池;加载类的信息被保存在元数据区中,运行中的常量池被保存在堆中。
3.JVM中一次完整的GC是什么样子的象如何晋升到老年代/h2>
堆分为新生代和老年代,新生代又分为Eden区和幸存者区(Survivor),幸存者区(Survivor)又分为Survivor0(to)区和Survivor1(from)区,Survivor0:Survivor1:Eden = 1:1:8 ; 当Eden区空间满的时候,就会触发一次Minor GC(轻量级的GC)来收集新生代的垃圾,存活的对象就会被分配到幸存者(Survivor)区,幸存者区之间是采用复制算法来进行复制对象的。对于大对象来说,需要大量连续的内存空间,会直接被分配到老年区。如果对象在Eden区出生,并且经过一次?Minor GC之后,如果仍然存活,被分配到Survivor区的话,那么它的年龄会进行加一,按照计算年龄的方式,来确定对象存活的周期。此后每经过一次minor GC,并且存活下来,年龄就进行加一,当年龄达到15的时候,就会晋升到老年代,大对象除外,直接进入到老年代。当老年代满的时候,无法容纳更多对象,这个时候就会触发FULL GC,Major GC是发生在老年代的GC,用来清理老年代区的,经常会伴随着Minor GC进行垃圾回收。
4.聊一聊Java中的回收算法/h2>
Java中有四种垃圾回收算法:
-
标记清除法
- 标记整理法
- 复制算法
- 分代收集算法
1.标记清除法
- 标记:利用可达性分析来遍历内存,把存活的对象和垃圾对象进行标记;
2.标记整理法
- 标记:利用可达性分析来遍历内存,把存活的对象和垃圾对象进行标记;
6.垃圾回收之新生代垃圾收集器
JVM的运行模式:Client模式和Server模式
1.Serial收集器
? 采用的是复制算法-单线程-Client模式
? Serial收集器(-XX:+UseSerialGC,复制算法)
? 单线程收集,进行垃圾收集时,必须暂停所有工作线程
? 简单高效,Client模式下默认的年轻代收集器
3.Parallel Scavenge收集器
采用的是复制算法-多线程-Server模式
Parallel Scavenge收集器(-XX:+UseParallelGC,复制算法)
比起关注用户线程停顿时间,更关注系统的吞吐量
在多核下执行才有优势,Server模式下默认的年轻代收集器
2.Parallel Old收集器
? 采用的是标记整理算法-多线程
? Parallel Old收集器(-XX:+UseParallelOldGC,标记-整理算法)
? 多线程,吞吐量优先
注意:
? CMS 收集器是以低停顿为目标,而 Parallel Scavenge 收集器收集器是以吞吐量为目标,两个垃圾收集器是不同的架构,所以两个不能够并用,在多 CPU 环境中,CMS 收集器一般与 ParNew 收集器一起使用的场景比较多。
4.G1垃圾收集器
G1垃圾收集器(-XX:+UseG1GC , 复制+标记-整理算法)
G1 (Garbage-First)是一款面向服务端应用的垃圾收集器,主要使命是代替 CMS 收集器。G1 将 Java 堆划分为多个大小相等独立的区域 (Region)。
G1收集器详解
? G1 还保留着新生代和老年代的概念,但是新生代和老年代不再是物理隔离了,它们都是一部分 Region (不需要连续)的集合。
? 运行效果图
8.如何判断一个对象是否存活/h2>
-
引用计数法
比较老的判断对象是否存活的一种方式,给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,计数器就会加一,引用失效就会减一,当引用计数器为0的时候,这个对象就是垃圾对象。
缺点:没有办法解决循环引用的问题。
-
可达性分析
从一个GCROOT(是一个集合),把对象进行标记,往下搜索,如果一个对象到GCROOT没有任何引用链的链接的时候,说明对象不可用。
可以作为GCROOT的对象:
满足上述条件就可以被回收吗是的,还需要进行两次的标记,
? 第一次:判断当前对象是否有finalize方法,并且该方法没有被执行过,如果不存在则标记为垃圾对象,等待回收。如果有的话,则进行第二次标记。
? 第二次:当前对象放入一个Finalize-Queue的队列之中,并生成一个finalize线程去执行这个方法,但是虚拟机不保证这个方法一定被执行到,因为如果线程执行缓慢,或者是进入死锁,就会导致回收系统崩溃。如果执行过finalize方法之后,没有与GCROOT有直接或者间接的引用,就被标记为垃圾对象。
? 可达性分析:采用的是标记清除算法。
9.项目中怎么优化JVM的持续更新)
a.线上启动时内存过小问题
引用计数法
比较老的判断对象是否存活的一种方式,给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,计数器就会加一,引用失效就会减一,当引用计数器为0的时候,这个对象就是垃圾对象。
缺点:没有办法解决循环引用的问题。
可达性分析
从一个GCROOT(是一个集合),把对象进行标记,往下搜索,如果一个对象到GCROOT没有任何引用链的链接的时候,说明对象不可用。
可以作为GCROOT的对象:
满足上述条件就可以被回收吗是的,还需要进行两次的标记,
? 第一次:判断当前对象是否有finalize方法,并且该方法没有被执行过,如果不存在则标记为垃圾对象,等待回收。如果有的话,则进行第二次标记。
? 第二次:当前对象放入一个Finalize-Queue的队列之中,并生成一个finalize线程去执行这个方法,但是虚拟机不保证这个方法一定被执行到,因为如果线程执行缓慢,或者是进入死锁,就会导致回收系统崩溃。如果执行过finalize方法之后,没有与GCROOT有直接或者间接的引用,就被标记为垃圾对象。
? 可达性分析:采用的是标记清除算法。
项目在线上进行启动的时候, 错java.lang.OutOfMemoryError: Java heap space ,
在JVM中如果98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息。
JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.
JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。
解决办法:
tomcat_home/bin下catalina.bat(win)或catalina.sh(linux)执行代码前加上:
set JAVA_OPTS=%JAVA_OPTS% -Xms128m -Xmx512m
10.class类文件如何加载到虚拟机/h2>
答:一个java文件经过编译之后生成一个class文件,想要把class文件加载到虚拟机
第一步:需要先进行类的装载,通过双亲委派机制获得一个需要加载的类,先把类的信息(名称,创建时间等)、常量,静态变量及编译器即时编译之后的代码加载到jvm的方法区;然后把class类的信息加载到jvm的堆内存中。
第二步:需要进行链接,链接就是先进行验证class类的正确性。然后为类的静态变量分配内存,将其初始化为默认值,将class类中的符 引用转化为直接引用。
第三步:初始化操作,对类的静态变量,静态代码块执行初始化操作。
11.双亲委派机制
? 当一个类加载器在接到加载类的请求时,他首先不会自己去尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有在父类加载器无法完成加载任务时,才自己去加载。
? 加载器自下而上有四种:Custom ClassLoader、App Classloader、Extension ClassLoader、Bootstrap ClassLoader.
12.方法区、堆、虚拟机栈、程序计数器、本地方法栈都是什么,各有什么特点/h2>
方法区:
方法区是各个线程共享的内存区域,在虚拟机启动的时候就创建了。
存储内容:已被虚拟机加载的类信息,常量、静态变量、即时编译器编译后的代码等
堆:
堆也是被所有线程所共享,在虚拟机启动的时候就创建了。
存储内容:对象实例以及数组都在堆上分配。
虚拟机栈:
虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。虚拟机栈是线程私有的,随着线程的创建而创建。
每一个被线程执行的方法,为该栈中的栈帧,即每一个方法对应着每一个栈帧。
调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
程序计数器:
程序计数器占用的内存空间很小,由于java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的。在任意时刻,一个处理器只会执行一条线程中的指令。因此为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器来记录正在执行的虚拟机字节码指令的地址。
如果正在执行的是Native方法,则这个计数器为空。
本地方法栈:
如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中进行执行。
13.Young区为什么还需要Survivor区有Eden区不行吗/h2>
如果没有Survivor,Eden区每进行一次Minor GC,并且没有年龄限制的话,存活的对象就会被送到老年代。
这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。
老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。
执行时间长有什么坏处/strong>
? 频发的Full GC消耗的时间很长,会影响大型程序的执行和响应速度。
可能你会说,那就对老年代的空间进行增加或者较少咯。
? 假如增加老年代空间,更多存活对象才能填满老年代。虽然降低Full GC频率,但是随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长。
? 假如减少老年代空间,虽然Full GC所需时间减少,但是老年代很快被存活对象填满,Full GC频率增加。
所以Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
14.为什么需要两个Survivor区/h2>
最大的好处就是解决了碎片化。
也就是说为什么一个Survivor区不行一部分中,我们知道了必须设置Survivor区。假设 现在只有一个Survivor区,我们来模拟一下流程:
? 刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循 环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的 存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。 永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。
15.新生代中Eden:S1:S2为什么是8:1:1/h2>
新生代中的可用内存:复制算法用来担保的内存为9:1
可用内存中Eden:S1区默认为8:1
即新生代中Eden:S1:S2 = 8:1:1
16.垃圾收集发生的时机
(1)当Eden区或者S区不够用了
(2)老年代空间不够用了
(3)方法区空间不够用了
(4)System.gc()
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。 当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。 但是不建议手动调用该方法,因为消耗的资源比较大。
17.JVM性能优化
26.简述 Java 垃圾回收机制
在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低 优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当 前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象, 并将它们添加到要回收的集合中进行回收。
27.垃圾回收的优点和原理。并考虑 2 种回收机制。
? Java 语言中一个显著的特点就是引入了垃圾回收机制,使 C++ 程序员最头疼的内存管理的问题迎刃而解,它使得 Java 程序员在 编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制, Java 中的对象不再有“作用域”的概念,只有对象的引用才有” 作用域”。
? 垃圾回收可以有效的防止内存泄露,有效的使用可以使 用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行, 不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的 对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对 象或所有对象进行垃圾回收。 回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
28. 垃圾回收器的基本原理是什么圾回收器可以马上回收内存吗有什么办法主动通知虚拟机进行垃圾回收/h2>
对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象 的地址、大小以及使用情况。通常,GC 采用有向图的方式记录和 管理堆(heap)中的所有对象。通过这种方式确定哪些对象是” 可达的”,哪些对象是”不可达的”。当 GC 确定一些对象为“不 可达”时,GC 就有责任回收这些内存空间。可以。程序员可以手 动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不 保证 GC 一定会执行。
29.Java 中会存在内存泄漏吗,请简单描述。
? 所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占 据在内存中。Java 中有垃圾回收机制,它可以保证一对象不再被 引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器 从内存中清除掉。由于 Java 使用有向图的方式进行垃圾回收管理, 可以消除引用循环的问题,例如有两个对象,相互引用,只要它们 和根进程不可达的,那么 GC 也是可以回收它们的,例如下面的 代码可以看到这种情况的内存回收:
package com.journaldev.examples;import java.io.IOException;声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!