jvm的内存模型以及分区情况和作用
堆、栈、方法区、本地方法栈、程序计数器。
其中堆区和方法区是线程共有区域,其他三个区域是线程私有区域。
方法区
用于存储虚拟机加载的类信息,常量,静态变量等数据,
堆
存放对象实例,所有的对象和数组都要在堆上分配。是JVM 所管理的内存中最大的一块区域。
栈
Java方法执行的内存模型: 存储局部变量表,操作数栈,动态链接,方法出口等信息生命周期与线程相同。
本地方法栈
native修饰的方法在这块区域执行。
程序计数器
程序计数器用来选取下一条需要执行的字节码指令。
jvm对象创建步骤流程是什么?
1.首先进行类加载检查,如果没有加载过该类,则必须先执行类的加载。
2.类加载之后,jvm会为该新生对象分配内存。
3.内存分配完成之后,虚拟机将分配到的内存空间都初始化默认值。
4.初始化默认值之后,jvm执行设置对象头的操作。
5.最后执行jvm的init方法,进行属性赋值操作和执行该类的构造方法。
垃圾回收算法有几种类型?他们对应的优缺点又是什么?
标记-清除算法、复制算法、标记-整理算法、分代收集算法
标记-清除算法
标记一清除算法包括两个阶段:“标记”和“清除”
标记阶段:确定所有要回收的对象,并做标记
清除阶段:将标记阶段确定不可用的对象清除。
缺点:
1.标记和清除的效率都不高。
2.会产生大量的碎片,而导致频繁的回收。
复制算法
内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候把存活的对象复制到另一块上,然后把这块内存整个清理掉。
缺点:
1.需要浪费额外的内存作为复制区
2.当存活率较高时,复制算法效率会下降。
标记-整理算法
标记-整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。
缺点:
算法复杂度大,执行步骤较多
分代收集算法
目前大部分 JVM 的垃圾收集器采用的算法。根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为新生代(YoungGeneration和老年代(TenuredGeneration),永久代(PermanetGeneration)。
老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最合适的垃圾收集算法。
年轻代:存放新创建的对象,对象生命周期非常短,几乎用完可以立即回收,也叫 Eden区。
老年代: young区多次回收后存活下来的对象将被移到 tenured 区,也叫 old 区。
Perm:永久带,主要存加载的类信息,生命周期长,几乎不会被回收。
缺点:
算法复杂度大,执行步骤较多。
类加载的过程是什么?简单描述一下每个过程都干了什么?
加载、验证、准备、解析、初始化
jvm预定义的类加载器有几种?分别有什么作用?
启动(Bootstrap)类加载器、标准扩展(Extension)类加载器、应用程序类加载器(Application)
启动(Bootstrap)类加载器:主要用来加载JAVA_HOME/jre/lib/下的jar包。比如rt.jar。
标准扩展(Extension)类加载器:主要用来加载JAVA_HOME/lib/ext/包下的jar包。
应用程序类加载器(Application):主要用来加载用户自己写的类。
什么是双亲委派模式?有什么作用?
基本定义:
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器没有找到所需的类时子加载器才会尝试去加载该类。
双亲委派的作用:
通过带有优先级的层级关系可以避免类的重复加载。
保证java程序安全稳定运行,java核心API定义类型不会被随意替换。
介绍一下jvm中的垃圾收集器有哪些? 他们的特点分别是什么?
新生代垃圾收集器
Serial收集器
特点:
Serial收集器只能使用一条线程进行垃圾收集工作,并且在进行垃圾收集的时候,所有的工作线程都需要停止工作,等待垃圾收集线程完成以后,其他线程才可以继续工作。
使用算法:复制算法
ParNew 收集器
特点:
ParNew 垃圾收集器是Serial收集器的多线程版本。为了利用 CPU 多核多线程的优势ParNew 收集器可以运行多个收集线程来进行垃圾收集工作。这样可以提高垃圾收集过程的效率。
使用算法:复制算法
Parallel Scavenge 收集器
特点:
Parallel Scavenge 收集器是一款多线程的垃圾收集器,但是它又和 ParNew 有很大的不同点。
Parallel Scavenge 收集器和其他收集器的关注点不同。其他收集器,比如 ParNew和CMS这些收集器,它们主要关注的是如何缩短垃圾收集的时间。而 Parallel Scavenge 收集器关注的是如何控制系统运行的吞吐量。这里说的吞吐量,指的是 CPU用于运行应用程序的时间和 CPU 总时间的占比,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间)。如果虚拟机运行的总的 CPU 时间是 100分钟,而用于执行垃圾收集的时间为1分钟,那么吞吐量就是 99%。
使用算法:复制算法
老年代垃圾收集器
Serial old 收集器
特点:
Serial old 收集器是 Serial收集器的老年代版本。这款收集器主要用于客户端应用程序中作为老年代的垃圾收集器,也可以作为服务端应用程序的垃圾收集器。
使用算法:标记-整理
Parallel old 收集器
特点:
Parallel 0ld 收集器是 Parallel Scavenge 收集器的老年代版本这个收集器是在 JDK1.6 版本中出现的,所以在 JDK1.6之前,新生代的 Parallel Scavenge 只能和 Serial old 这款单线程的老年代收集器配合使用。Parallel 0ld垃圾收集器和 Parallel Scavenge 收集器一样,也是一款关注吞吐量的垃圾收集器,和 Parallel Scavenge 收集器一起配合,可以实现对Java 堆内存的吞吐量优先的垃圾收集策略。
使用算法:标记-整理
CMS 收集器
特点:
CMS 收集器是目前老年代收集器中比较优秀的垃圾收集器。CMS是 Concurrent MarkSweep,从名字可以看出,这是一款使用”标记-清除”算法的并发收集器。
CMS 垃圾收集器是一款以获取最短停顿时间为目标的收集器。如下图所示
使用算法:复制+标记清除
其他
G1垃圾收集器
G1只有逻辑上的分代概念,使用复制算法将存活的对象复制到另一个空闲的区域。
特点:
主要步骤:初始标记,并发标记,重新标记,复制清除。
使用算法:复制+标记整理
对象“对象已死”是什么概念?
对象不可能再被任何途径使用,称为对象已死。
判断对象已死的方法有:引用计数法与可达性分析算法。
JVM 数据运行区,哪些会造成 OOM 的情况?
除了数据运行区,其他区域均有可能造成 O0M 的情况:
堆溢出:java.lang.0utOfMemoryError:Java heap space
栈溢出:java.lang.StackOverflowError
永久代溢出:java.lang.0utOfMemoryError:PermGen space
详细介绍一下对象在分带内存区域的分配过程?
- JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域。
- 当 Eden 空间足够时,内存申请结束;否则到下一步。
- JVM 试图释放在 Eden 中所有不活跃的对象(这属于1或更高级的垃圾回收)。释放后若 Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入Survivor x。
GitChat 用户专享,请尊重版权 - Survivor 区被用来作为 Eden 及 0ld 的中间交换区域,当 0ld 区空间足够时Survivor 区的对象会被移到 0ld 区,否则会被保留在 Survivor 区.
- 当 Old 区空间不够时,JVM 会在 Old 区进行完全的垃圾收集。
- 完全垃圾收集后,若 Survivor 及 0ld 区仍然无法存放从 Eden 复制过来的部分对象,导致 JVM 无法在 Eden 区为新对象创建内存区域,则出现“out of memory”错误。
G1与 CMS 两个垃圾收集器的对比
细节方面不同
1.G1 在压缩空间方面有优势,
2.G1 通过将内存空间分成区域(Region)的方式避免内存碎片问题。
3.Eden,survivor, Old 区不再固定、在内存使用效率上来说更灵活,
4.G1 可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
5.G1在回收内存后会马上同时做合并空闲内存的工作、而 CMS默认是在 STW(stopthe world)的时候做。
6.G1 会在 Young Gc 中使用、而 CMS 只能在old区使用。
线上常用的 JM 参数有哪些?
数据区设置
Xms:初始堆大小
Xmx:最大堆大小
Xss:Java每个线程的Stack大小
XX:NewSize=n:设置年轻代大小
XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4。
XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor区有两个。如:3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的1/5。
XX:MaxPermSize=n:设置持久代大小。
收集器设置
XX:+UseSerialGc:设置串行收集器·
XX:+UseParallelGc::设置并行收集器。
XX:+UseParalledloldGc:设置并行年老代收集器
XX:+UseConcMarkSweepGc:设置并发收集器
GC日志打印设置
XX:+PrintGc:打印 GC的简要信息XX:+PrintGCDetails:打印GC详细信息
XX:+PrintGCTimeStamps:输出GC的时间戳
对象什么时候进入老年代?
对象优先在 Eden 区分配内存
当对象首次创建时,会放在新生代的 eden 区,若没有 Gc的介入,会一直在 eden区,GC后,是可能进入survivor 区或者年老代
大对象直接进入老年代
所谓的大对象是指需要大量连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。
长期存活的对象进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器,对象在 Survivor 区中每熬过一次Minor Gc,年龄就增加 1,当他的年龄增加到一定程度(默认是 15岁),就将会被晋升到老年代中。
什么是内存溢出,内存泄露?他们的区别是什么?
内存溢出 out ofmemory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
内存泄露 memoryleak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出.
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
Full GC、MajorGc、MinorGc之间区别?
Minor GC:
从新生代空间(包括 Eden和 Survivor 区域)回收内存被称为 Minor Gc。
Major GC:
清理 Tenured 区,用于回收老年代,出现 Major Gc通常会出现至少一次 Minor Gc.
Full GC:
Full GC 是针对整个新生代、老年代、元空间(metaspace,java8 以上版本取代 permgen)的全局范围的 GC。
什么时候触发 Full GC?
1.调用 System.gc时,系统建议执行 FullGC,但是不必然执行,
2.老年代空间不足。
3.方法区空间不足。
4.通过 Minor Gc后进入老年代的平均大小大于老年代的可用内存。
5.由Eden 区、survivorspace1(FromSpace)区向survivorspace2(To Space)区复制时,对象大小大于Two Space 可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
什么情况下会出现栈溢出
1.方法创建了一个很大的对象,如 List,Array。
2.是否产生了循环调用、死循环。
3.是否引用了较大的全局变量。
说一下强引用、软引用、弱引用、虚引用以及他们之间和 gc的关系
1.强引用:new 出的对象之类的引用,只要强引用还在,永远不会回收,
2.软引用:引用但非必须的对象,内存溢出异常之前,回收。
3.弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。
4.虚引用:对生存时间无影响,在垃圾回收时得到通知。
Eden 和 Survivor的比例分配是什么情况?为什么?
默认比例 8:1。
大部分对象都是朝生夕死。
复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
CPU 资源占用过高
1.top 査看当前 CPU 情况,找到占用 CPU 过高的进程 PID=123。
2.top -H -p 123 找出两个 CPU 占用较高的线程,记录下来 PID=2345,3456转换为十六进制。(强调一下:jstack打印出来的线程的pid是用十六进制表示的,我们可以使用命令将这个十进制的pid转换成16进制的pid,可以使用命令:printf "%x" 2345 比方说得到的结果是1ea6) .
3.jstack 123 > temp.txt打印出当前进程的线程栈。
4.然后我们查看temp.txt文件,查找到对应于第二步的两个线程运行栈(搜索1ea6),里面会有代码的错误信息,如果有死锁相关的文件也是会在文件的最下面进行展示的。
5.最后分析代码(看看有没有出现死锁等问题)。
补充:jstack用于生成java虚拟机当前时刻的进程的线程快照。
什么情况下会产生OOM?
1.一次性申请的对象太多了。比方说select * from 表没有加where条件。
2.内存资源耗尽没有得到释放。比方说我们频繁的创建线程不释放,进行IO操作的时候不关流,connection连接对象不关闭等等。
3.老板给的硬件资源不给力。
OOM 异常排查
使用命令: jmap -heap 进程号(你部署的应用程序)// 我们就能够看出当前进程的占用的内存情况,比方说eden区、surviror区老年代的内存占用情况。
如果你的系统已经因为oom挂掉了。解决办法:
提前设置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=
也就是说你提前把这个jvm参数设置了,系统挂掉了之后,直接查看这个堆的dump文件就可以了。这个文件的后缀是.hprof,然后我们可以使用java visualvm工具将这个文件导入进来,进行分析了。
查看最多跟业务有关对象->找到GCRoot ->查看线程栈
如果你的系统因为oom还没有挂掉。解决办法:
我们可以导出dump文件件: jmap -dump:format=b,file=xushu.hprof 14660,
缺点是会进行一次fullgc,还有会造成一次STW.
当然也可以使用arthas工具进行故障排查。
- 使用 top 指令查询服务器系统状态。
- ps -aux|grep java 找出当前 Java 进程的 PID。
- jstat -gcutil pid interval査看当前 GC的状态。
- jmap -histo:live pid 可用统计存活对象的分布情况,从高到低查看占据内存最多的对象。
- jmap -dump:format=b,file=文件名[pid]利用 Jmap dump。
- 使用性能分析工具对上一步 dump 出来的文件进行分析,工具有 MAT 等。