Java的JVM内存模型全面知识点总结

Java内存 应该是Java面试中问得最多的问题,今天写一篇彻底终结面试。(* ̄︶ ̄)

首先先摆上Java内存模型图:
 

JVM内存布局规定了java在运行过程中内存申请、分配、管理的策略

1、Heap堆内存区域: heap是0OM故障的主要发源地,存储几乎所有的实例对象 ,堆由C自动回收.通常其占用的内存也是最大的。
通常情况下堆内存的空间既可以固定大小也可以在运行时动态进行调整。常见的调优参数例如: .Xms256M -Xmx256M
-X是VM运行参数,ms是memory start简称,mx是memory max简称,通常情况下服务在运行的过程中,堆空间不停在扩容与回缩,会造成不必要的系统压力,所以生产环境我们通常将这两个参数设置成大小-样,避免后续调整对大小带来压力。
堆又分成两大块,新生代和老年代。
如图所示: 1个新生代= 1个Eden区+ 2个Sunvivor
绝大部分对象在Eden区域生成,当Eden区域满的时候,触发YGC . Eden区域实现清除策略,没有被引用的对象则直接回收。依然存活的对象,则被移送到Sunivor区域 . surivor区域又分为s0+s1两个区域,每次YGC时,将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,两块空间交换使用状态。如果YGC要移送的对象大于survivor区域容量, 则移送到老年区域。
提问:那么是不是会有一些对象-直在新生代的survivor区域内交换来交换去 ?
错,每个对象都有一个计数器,每次YGC都会增加1 ,-XX : MaxTenuringThreshold参数能配置计数器的值,直到到达某个阈值时,对象从新生代晋升到老年代默认值是15,可以在survivor区域交换14次,然后晋升老年代。如果该参数配置为1,则直接晋升到老年代。当老年代无法放下超大对象时,触发FGC,如果依然无法放下则抛出OOM
出错时,堆内存的信息对解决问题非常重要,所以JVM可以设置上运行参数: -XX: +HeapDumpOnOutOfMemoryError

2、Metaspace区域 :这里描述的大前提JVM是hotSpot, JDK8之前的版本中元空间的前身perm永久代.因为永久代启动的时候是固定大小所以很难进行调优,所以移除了这块。所以呢,如果是JDK8之前的版本,我们通常设置-XX:MaxPermSize =1280m,如果我们在JDK8之后的版本中也增加了该参数设置,启动时不会报错。但是会有提示信息,区别于永久代,元空间在本地内存中分配。JDK8中,Perm区域中的所有内容中字符串常量移动到堆内存中,其他内容,包括类元信息字段、静态属性、方法、常等都移动到了无空间内。例如: Object类元信心、静态属性System.out.整型常量100000等

3、JVM Stack虚拟机栈:栈是先进后出的结构,JVM中的虚拟机栈是描述java方法执行的内存区域,它是线程私有的,栈中的元素用于支持虚拟机进行方法的调用,每个方法从开始调用到执行完成的过程就是栈帧从入栈到出栈的过程。StackOverflowError 表示请求的栈溢出,导致内存耗尽。

4、Native Method Stacks本地方法栈:在JM中,本地方法栈是线程私有的。虚拟机栈主内,本地方法栈主外,这个内外相对于IVM,线程开始调用本地方法时.会进入到一个不再受JVM约束的世界,本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至可能是寄存器,具有和VM相同的能力和权限,当大的本地方法出现时,内存不足时,还是会抛出native heap OutOfMemory,最著名的本地方法是: System.currentTimeMillis()

5、Program Counter Register程序计数器:CPU必须把数据加载到寄存器才能运行,计算机的时间轮询的机制,在众多线程并发执行的过程中。任何一个确定的时刻,一个处理器或者多核处理器的一个内核,只会执行某个线程中的一条指令,这样必回导致经常中断或恢复,如何保证分毫不差?每个线程在创建后,都会产生自己的程序计数器和栈帧。程序计数器用来存放指令的偏移量和信号指示器,程序计数器在各个线程之间互相不影响。

JVM面试常见问答:

1、你在项目中都使用了哪些参数打印GC?

答:般在项目中输出详细的 GC 日志,并加上可读性强的 GC 日志的时间戳。特别情况下我还会追加一些反映对象晋升情况和堆详细信息的日志,这些会单独打到gc.log文件中用来排查问题

 

2、常用的调优工具有哪些?

答:JDK内置的命令行:jps(查看jvm进程信息)、jstat(监视jvm运行状态的,比如gc情况、jvm内存情况、类加载情况等)、jinfo(查看jvm参数的,也可动态调整)、jmap(生成dump文件的,在dump的时候会影响线上服务)、jhat(分析dump的,但是一般都将dump导出放到mat上分析)、jstack(查看线程的)。JDK内置的可视化界面:JConsole、VisualVM,这两个在QA环境压测的时候很有用。阿里巴巴开源的arthas:神器,线上调优很方便,安装和显示效果都很友好。

 

3、如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?

答:检查下系统是否存在System.gc() ;

 

4、线上一个系统跑一段时间就栈溢出了,怎么办 ?

答:1.首先检查下是否有死归这种无限递归的程序或者递归方法太多。2.可以看下栈大小,若太小则可以指定-Xss参数设置栈大小

 

5、系统CPU经常100%,如何调优?

答:

找出哪个进程cpu占用高(top命令)

该进程中的哪个线程cpu占用高(top -Hp $pid命令)

将十进制的tid转化为十六进制(printf %x $tid命令)

导出该线程的堆栈 (jstack $pid >$pid.log命令)

查找哪个方法(栈帧)消耗时间 (less $pid.log)

可以确认工作线程占比高还是垃圾回收线程占比高

修改代码

 

6、系统内存飙高,如何查找问题?

找出哪个进程内存占用高(top命令)

查看jvm进程号(jps命令)

导出堆内存 (jmap命令生成dump文件,注意:线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿,所以操作前最好先从负载均衡里摘掉。)

分析dump文件 (比如mat软件)

 

7、大型项目如何进行性能瓶颈调优

答:

1.数据库与SQL优化:一般dba负责数据库优化,比如集群主从等。研发负责SQL优化,比如索引、分库分表等。

2.集群优化:一般OP负责,让整个集群可以很容易的水平扩容,再比如tomcat/nginx的一些配置优化等。

3.硬件升级:选择最合适的硬件,充分利用资源。

4.代码优化:很多细节,可以参照阿里巴巴规范手册和安装sonar插件这种检测代码质量的工具。也可以适当的运用并行,比如CountDownLatch等工具。

5.jvm优化:内存区域大小设置、对象年龄达到次数晋升老年代参数的调整、选择合适的垃圾收集器以及合适的垃圾收集器参数、打印详细的GC日志和oom的时候自动生成dump。

6.操作系统优化

 

常用参数:

GC常用参数

  • -Xmn:年轻代
  • -Xms:最小堆
  • -Xmx :最大堆
  • -Xss:栈空间
  • -XX:+UseTLAB:使用TLAB,默认打开
  • -XX:+PrintTLAB:打印TLAB的使用情况
  • -XX:TLABSize:设置TLAB大小
  • -XX:+DisableExplictGC:禁用System.gc()不管用 ,防止FGC
  • -XX:+PrintGC:打印GC日志
  • -XX:+PrintGCDetails:打印GC详细日志信息
  • -XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
  • -XX:+PrintGCTimeStamps:打印时间戳
  • -XX:+PrintGCApplicationConcurrentTime:打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime:打印暂停时长
  • -XX:+PrintReferenceGC:记录回收了多少种不同引用类型的引用
  • -verbose:class:类加载详细过程
  • -XX:+PrintVMOptions:jvm参数
  • -XX:+PrintFlagsFinal:-XX:+PrintFlagsInitial 必须会用
  • -Xloggc:opt/log/gc.log:gc日志的路径以及文件名称
  • -XX:MaxTenuringThreshold:升代年龄,最大值15

Parallel常用参数

  • -XX:SurvivorRatio:年轻代中eden和from/to的比值。比如设置3就是eden:survivor=3:2,也就是from和to各占1,eden占用3
  • -XX:PreTenureSizeThreshold:大对象到底多大
  • -XX:MaxTenuringThreshold:升代年龄,最大值15
  • -XX:+ParallelGCThreads:并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy:自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC:设置年老代为并发收集
  • -XX:ParallelCMSThreads:CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction:使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection:在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction:多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled:年老代启用CMS,但默认是不会回收永久代(Perm)的。此处对Perm区启用类回收,防止Perm区内存满。
  • -XX:CMSInitiatingPermOccupancyFraction:达到什么比例时进行Perm回收
  • GCTimeRatio:设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis:停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC:开启G1
  • -XX:MaxGCPauseMillis:建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis:GC的间隔时间
  • -XX:+G1HeapRegionSize:分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
  • G1NewSizePercent:新生代最小比例,默认为5%
  • G1MaxNewSizePercent:新生代最大比例,默认为60%
  • GCTimeRatio:GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads:线程数量
  • InitiatingHeapOccupancyPercent:启动G1的堆空间占用比例

PS: 博客中截图摘自阿里巴巴《码出高效-Java开发手册》,问答提问来自网络总结,后续会持续更新。非盈利,为了知识共享。


 

猜你喜欢

转载自blog.csdn.net/LB_Captain/article/details/113091624