1、一般什么情况会发生栈溢出、堆溢出
栈溢出(StackOverflowError)
1、栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)
所以我们可以理解为栈溢出就是方法执行是创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调用产生这种结果。
2、由于分配了过大的局部变量引起
堆溢出(OutOfMemoryError:java heap space)
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2、集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3、代码中存在死循环或循环产生过多重复的对象实体;
4、使用的第三方软件中的BUG;
5、启动参数内存值设定的过小;
2、讲一下JVM的作用?
JVM存在的目的和意义是什么?
一句话,实现跨平台。
JVM 整体组成可分为以下四个部分:
- 类加载器(ClassLoader)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
- 本地库接口(Native Interface)
3、讲一下JVM内存区域
- Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的
- 堆这部分空间是唯一一个程序员可以管理的内存区域。程序员可以通过malloc函数和free函数在堆上申请和释放空间。
- 在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等
生命周期
4、泛型和类型擦除的关系
Java泛型的实现方法:类型擦除
Java的泛型是伪泛型。因为,在编译期间,所有的泛型信息都会被擦除掉。
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型。
类型擦除引起的问题:
- 1、先检查,在编译,以及检查编译的对象和引用传递的问题,java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的
- 2、类型擦除与多态的冲突和解决方法:桥方法
- 3、泛型类型变量不能是基本数据类型
- 4、泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
5、如何进行JVM调优?
何时进行JVM调优
- Heap内存(老年代)持续上涨达到设置的最大内存值;
- Full GC 次数频繁;
- GC 停顿时间过长(超过1秒);
- 应用出现OutOfMemory 等内存异常;
- 应用中有使用本地缓存且占用大量内存空间;
- 系统吞吐量与响应性能不高或下降。
JVM调优目标:
- 延迟:GC低停顿和GC低频率;
- 低内存占用;
- 高吞吐量;
JVM调优的步骤:
- 分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;
- 确定JVM调优量化目标;
- 确定JVM调优参数(根据历史JVM参数来调整);
- 依次调优内存、延迟、吞吐量等指标;
- 对比观察调优前后的差异;
- 不断的分析和调整,直到找到合适的JVM参数配置;
- 找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
6、如何查看Dump日志?怎么产生的?命令有哪些
Dump文件是进程的内存镜像,可以把程序的执行状态通过调试器保存到dump文件中。主要是用来在系统中出现异常或者崩溃的时候来生成dump文件,然后用调试器进行调试。
如何dump出jvm日志。
在jvm启动的参数中,新增-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/logs/java.hprof jvm参数。这样在发生jvm 内存溢出时,就会直接dump出java.hprof 文件了。
直接导出jvm内存信息。
jmap -dump:format=b,file=/home/admin/logs/heap.hprof javapid
推荐使用Eclipse插件Memory Analyzer Tool来打开heap.hprof文件。
7、JVM 内存布局
8、 如何生成java dump文件
1、JVM的配置文件中配置:
在应用启动时配置相关的参数 -XX:+HeapDumpOnOutOfMemoryError,当应用抛出OutOfMemoryError时生成dump文件
2、通过jmap执行指令,直接生成当前JVM的dmp文件
jmap -dump:file=文件名.dump [pid]
9、方法区和元空间是什么关系?
- 首先,方法区是JVM规范的一个概念定义,并不是一个具体的实现,每一个JVM的实现都可以有各自的实现;
- 然后,在Java官方的HotSpot 虚拟机中,Java8版本以后,是用元空间来实现的方法区;在Java8之前的版本,则是用永久代实现的方法区;
- 也就是说,“元空间” 和 “方法区”,一个是HotSpot 的具体实现技术,一个是JVM规范的抽象定义;
然后多说一句,这个元空间是使用本地内存(Native Memory)实现的,也就是说它的内存是不在虚拟机内的,所以可以理论上物理机器还有多个内存就可以分配,而不用再受限于JVM本身分配的内存了。
为什么用元空间代替永久代?
类的元数据信息(metadata)转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。
由于类的元数据可以在本地内存(native memory)之外分配,所以其最大可利用空间是整个系统内存的可用空间。这样,你将不再会遇到OOM错误,溢出的内存会涌入到交换空间。最终用户可以为类元数据指定最大可利用的本地内存空间,JVM也可以增加本地内存空间来满足类元数据信息的存储。
10、 JVM
11、 JVM参数
12、年轻代和老年代
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。
默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为9/10 ( 即90% )的新生代空间。
13、 什么情况对象直接在老年代分配:
1、分配的对象大小大于eden space。适合所有收集器。
2、eden space剩余空间不足分配,且需要分配对象内存大小不小于eden space总空间的一半,直接分配到老年代,不触发Minor GC。适合-XX:+UseParallelGC、-XX:+UseParallelOldGC,即适合Parallel Scavenge。
3、大对象直接进入老年代,使用-XX:PretenureSizeThreshold参数控制,适合-XX:+UseSerialGC、-XX:+UseParNewGC、-XX:+UseConcMarkSweepGC,即适合Serial和ParNew收集器。
14、 动态对象年龄判定
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold(jdk1.7默认是15)中要求的年龄。
15、 根搜索算法 GC Roots Tracing
以一系列叫“GC Roots”的对象为起点开始向下搜索,走过的路径称为引用链(Reference Chain),当一个对象没有和任何引用链相连时,证明此对象是不可用的,用图论的说法是不可达的。那么它就会被判定为是可回收的对象。
JAVA里可作为GC Roots的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(即Native方法)的引用的对象
16、 JVM性能调优的6大步骤
1、监控GC的状态
2、生成堆的dump文件
3、分析dump文件
4、分析结果,判断是否需要优化
5、调整GC类型和内存分配
6、不断的分析和调整
17、 高吞吐量的话用哪种gc算法
复制清除
18、 Java 源代码是怎么被机器识别并执行的呢?
是目前 OpenJDK 使用的主流 NM , 它采用解释与编译混合执行的模式 , 其 JIT 技术采用分层编译 , 极大地提升了 Java的执行速度。
19、 机器码和字节码区别
高级程序代码 ---(编译器)---> 字节码 ---(解释器)---> 机器码
机器码:是电脑CPU直接读取运行的机器指令,运行速度最快。
字节码:是一种中间状态(中间码)的二进制代码(文件)。需解释器(也叫直译器)转译后才能成为机器码
字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成机器指令,也就是说虚拟机执行字节码指令时是通过生成机器码交付给硬件执行
Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令(机器码)执行。这就是Java的能够“一次编译,到处运行”的原因。
字节码必须通过类加载过程加载到 JVM 环境后,才可以执行。执行有三种模式 第一,解释执行,第二, JIT编译执行 第三, JIT编译与解释混合执行(主流JVM 默认执行模式)
20、 在什么情况下需要自定义类加载器呢?
1、隔离加载类。 在某些框架内进行中间件与应用的模块隔离 , 把类加载到不同的环境。比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的 jar包不会影响到中间件运行时使用的 jar 包。
2、修改类加载方式。 类的加载模型并非强制 ,除Bootstrap 外 , 其他的加载并非定要引入,或者根据实际情况在某个时间点进行按需进行动态加载。
3、扩展加载源。 比如从数据库、网络,甚至是电视机机顶盒进行加载。
4、防止源码泄露。 Java代码容易被编译和篡改,可以进行编译加密。 那么类加载器也需要自定义,还原加密的字节码。
21、GC分代年龄为什么最大为15?
因为Object Header采用4个bit位来保存年龄,4个bit位能表示的最大数就是15!
22、Minor GC ,Full GC 触发条件是什么?
Minor GC ,Full GC 触发条件
- 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC;
- 对老年代GC称为Major GC;
- 而Full GC是对整个堆来说的;
在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上。下边看看有那种情况触发JVM进行Full GC及应对策略。
Minor GC触发条件:
- 当Eden区满时,触发Minor GC。
Full GC触发条件:
(1) System.gc()方法的调用
此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。
(2) 老年代空间不足
旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
(3) 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC
(4) 对象太大,年轻代容不下
23、请谈谈OOM可能发生在哪,怎么查看,怎么调优
原因不外乎有两点:
- 分配的少了:比如虚拟机本身可使用的内存太少。
- 应用用的太多,并且用完没释放
处理方法
1.先把内存镜像dump出来,有两种方法
- 设置JVM参数-XX:+HeapDumpOnOutOfMemoryError,设定当发生OOM时自动dump出堆信息
- 使用jmap命令。"jmap -dump:format=b,file=heap.bin <pid>" 其中pid可以通过jps获取
2.得到内存信息文件后就使用工具去分析,也有两个工具
- mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具
- jhat:JDK自带的java heap analyze tool,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等
24、阐述GC算法
- 标记清除算法:首先先标记,然后统一把标记的对象依次清除,缺点是CPU消耗大,极易出现内存碎片,所以一般用于老年代。
- 复制算法:把内存区域分成俩块,每次只使用其中一块,然后把还存活的对象放在另一块中,清空原先的块,这样的话不会出现内存碎片。新生代常用的。
- 标记整理算法:它的标记阶段和标记-清除算法中的一样。整理阶段是将所有存活的对象压缩到内存的一端。
25、什么时候会发送young gc?什么时候会发生full gc?
Young GC其实一般就是在新生代的Eden区域满了之后就会触发,采用复制算法来回收新生代的垃圾。
什么时候会发生full gc?
- System.gc()方法的调用
- 老年代空间不足,老年代内存使用率超过了92%,也要直接触发Old GC,当然这个比例是可以通过参数调整的
- 永生区空间不足
- CMS GC时出现promotion failed和concurrent mode failure
- HandlePromotionFailure
概括成一句话,就是老年代空间也不够了,没法放入更多对象了。