Java面试题 - JVM相关(下)

简介: Java面试题 - JVM相关(下)
10 java对象创建时机
  1. 使⽤new关键字创建对象
  2. 使⽤Class类的newInstance⽅法(反射机制)
  3. 使⽤Constructor类的newInstance⽅法(反射机制)
  4. 使⽤Clone⽅法创建对象
  5. 使⽤(反)序列化机制创建对象
11 JVM调优

调优时机:

  • heap 内存(⽼年代)持续上涨达到设置的最⼤内存值;
  • Full GC 次数频繁;
  • GC 停顿时间过⻓(超过1秒);
  • 应⽤出现OutOfMemory 等内存异常;
  • 应⽤中有使⽤本地缓存且占⽤⼤量内存空间;
  • 系统吞吐量与响应性能不⾼或下降。

调优原则:

  • 多数的Java应⽤不需要在服务器上进⾏JVM优化;
  • 多数导致GC问题的Java应⽤,都不是因为我们参数设置错误,⽽是代码问题;
  • 在应⽤上线之前,先考虑将机器的JVM参数设置到最优(最适合);
  • 减少创建对象的数量;
  • 减少使⽤全局变量和⼤对象;
  • JVM优化是到最后不得已才采⽤的⼿段;
  • 在实际使⽤中,分析GC情况优化代码⽐优化JVM参数更好;

调优⽬标:

  • GC低停顿;
  • GC低频率;
  • 低内存占⽤;
  • ⾼吞吐量;

调优步骤:

  • 分析GC⽇志及dump⽂件,判断是否需要优化,确定瓶颈问题点;
  • 确定jvm调优量化⽬标;
  • 确定jvm调优参数(根据历史jvm参数来调整);
  • 调优⼀台服务器,对⽐观察调优前后的差异;
  • 不断的分析和调整,知道找到合适的jvm参数配置;
  • 找到最合适的参数,将这些参数应⽤到所有服务器,并进⾏后续跟踪。
12 jvm调优参数
  1. 设定堆内存⼤⼩,这是最基本的。
  2. -Xms:启动JVM时的堆内存空间。
  3. -Xmx:堆内存最⼤限制。
  4. 设定新⽣代⼤⼩。
  5. 新⽣代不宜太⼩,否则会有⼤量对象涌⼊⽼年代。
  6. -XX:NewRatio:新⽣代和⽼年代的占⽐。
  7. -XX:NewSize:新⽣代空间。
  8. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占⽐。
  9. -XX:MaxTenuringThreshold:对象进⼊⽼年代的年龄阈值。
  10. 设定垃圾回收器
  • ----年轻代:-XX:+UseParNewGC。
  • ----⽼年代:-XX:+UseConcMarkSweepGC。
  • ----CMS可以将STW时间降到最低,但是不对内存进⾏压缩,有可能出现“并⾏模式失败”。⽐如⽼年代空间还有300MB空间,但是⼀些10MB的对象⽆法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要⽐Parallel GC⻓很多。
  • ----G1采⽤”标记-整理“算法,解决了内存碎⽚问题,建⽴了可预测的停顿时间类型,能让使⽤者指定在⼀个⻓度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
13 触发full gc的场景及应对策略
  1. System.gc()⽅法的调⽤,应对策略:通过-XX:+DisableExplicitGC来禁⽌调⽤System.gc ;
  2. ⽼年代代空间不⾜,应对策略:让对象在Minor GC阶段被回收,让对象在新⽣代多存活⼀段时间,不要创建过⼤的对象及数组;
  3. 永⽣区空间不⾜,应对策略:增⼤PermGen空间;
  4. GC时出现promotionfailed和concurrent mode failure,应对策略:增⼤survivor space;
  5. Minor GC后晋升到旧⽣代的对象⼤⼩⼤于⽼年代的剩余空间,应对策略:增⼤Tenured space 或下CMSInitiatingOccupancyFraction=60;
  6. 内存持续增涨达到上限导致Full GC,应对策略:通过dumpheap 分析是否存在内存泄漏。
14 jvm堆的基本结构

  1. JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代
  2. 新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8:1
15 如何查看jvm内存使⽤情况

可以使⽤JDK⾃带的JConsole、JVisualVM、JMap、JHat等⼯具,或者使⽤第三⽅⼯具,⽐如 Eclipse Memory Analyzer。

16 jvm内存溢出例⼦
  1. 内存溢出,⽐如给JVM分配的内存不够⼤,或者程序中存在死循环⼀直申请内存。
  2. 内存泄露,⽐如下⾯这段代码,list持有o的引⽤,o暂时是⽆法被JVM垃圾回收的,只有当list被垃圾回收或者o从对象list删除掉后,o才能被JVM垃圾回收。

17 常⽤的GC策略,什么时候会触发YGC,什么时候触发FGC?

YGC(Young GC):

  • 概念:对新⽣代堆进⾏GC。频率⽐较⾼,因为⼤部分对象的存活寿命较短,在新⽣代⾥被回收。性能耗费较⼩。
  • 触发时机:Eden区空间不⾜。

FGC(Full GC):

  • 概念:全堆范围的GC。默认堆空间使⽤到达80%(可调整)的时候会触发FGC。以我们⽣产环境为例,⼀般⽐较少会触发FGC,有时10天或⼀周左右会有⼀次。
  • 触发时机:
  • ------ Old空间不⾜;
  • ------ Perm空间不⾜;
  • ------ 显示调⽤System.gc() ,包括RMI等的定时触发;
  • ------ YGC时的悲观策略;
  • ------ dump live的内存信息时(jmap –dump:live)。
18 获得Class对象的方式
  1. 静态类的.class语法:GuideUtil.class
  2. 普通类对象的getClass()⽅法:new Test().getClass()
  3. 通过Class对象的forName()⽅法:
    Class.forName(“com.zhenai.modules.guide.utils.GuideUtil");
  4. 对于包装类,可以通过.TYPE语法⽅式:Integer.TYPE
19 内存溢出的可能原因和解决⽅法
  1. 数据加载过多,如1次从数据库中取出过多数据。
  2. 集合类中有对对象的引⽤,⽤完后没有清空或者集合对象未置空导致引⽤存在等,是的JVM⽆法回收。
  3. 死循环,过多重复对象。
  4. 第三⽅软件的bug。
  5. 启动参数内存值设定的过⼩。

解决⽅法:修改JVM启动参数,加内存(-Xms,-Xmx);错误⽇志,是否还有其他错误;代码⾛查。

20 内存泄漏的原因
  1. 未对作废数据内存单元置为null,尽早释放⽆⽤对象的引⽤,使⽤临时变量时,让引⽤变量在推出活动域后⾃动设置为null。

垃圾收集器收集;

  1. 程序避免⽤String拼接,⽤StringBuffer,因为每个String会占⽤内存⼀块区域;
  2. 尽量少⽤静态变量(全局不会回收);
  3. 不要集中创建对象尤其⼤对象,可以使⽤流操作;
  4. 尽量使⽤对象池,不再循环中创建对象,优化配置;
  5. 创建对象到单例getInstance中,对象⽆法回收被单例引⽤;
  6. 服务器session时间设置过⻓也会引起内存泄漏。
21 ⽅法区oom
  1. ⽅法区⽤于存放Class的相关信息,如:类名,访问修饰符,常量池,字符描述,⽅法描述等。
  2. 原因:运⾏时产⽣⼤量的类去填满⽅法区,直到溢出。
22 哪些情况下对象会进⼊⽼年代?
  1. 新⽣代对象每次经历⼀次minor gc,年龄会加1,当达到年龄阈值(默认为15岁)会直接进⼊⽼年代;
  2. ⼤对象直接进⼊⽼年代;
  3. 新⽣代复制算法需要⼀个survivor区进⾏轮换备份,如果出现⼤量对象在minor gc后仍然存活的情况时,就需要⽼年代进⾏分配担保,让survivor⽆法容纳的对象直接进⼊⽼年代;
  4. 如果在Survivor空间中相同年龄所有对象⼤⼩的总和⼤于Survivor空间的⼀半,年龄⼤于或等于该年龄的对象就可以直接进⼊年⽼代。
23 jvm中哪些地⽅会出现oom?分别说说oom的可能原因?

java堆溢出(heap):

  • Java堆内存主要⽤来存放运⾏过程中所以的对象,该区域OOM异常⼀般会有如下错误信息: java.lang.OutofMemoryError:Java heap space
  • 此类错误⼀般通过Eclipse Memory Analyzer分析OOM时dump的内存快照就能分析出来,到底是由于程序原因导致的内存泄露,还是由于没有估计好JVM内存的⼤⼩⽽导致的内存溢出。
  • 另外,Java堆常⽤的JVM参数:

-Xms:初始堆⼤⼩,默认值为物理内存的1/64(<1GB),默认(MinHeapFreeRatio参数可以调整)空余堆内存⼩于40%时,JVM就会增⼤堆直到

-Xmx:最⼤堆⼤⼩,默认值为物理内存的1/4(<1GB),默认(MaxHeapFreeRatio参数可以调整)空余堆内存⼤于70%时,JVM会减少堆直到

-Xmn:年轻代⼤⼩(1.4or lator),此处的⼤⼩是(eden + 2 survivor space),与jmap -heap中显示的New gen是不同的。

栈溢出(stack):

  • 栈⽤来存储线程的局部变量表、操作数栈、动态链接、⽅法出⼝等信息。如果请求栈的深度不⾜时抛出的错误会包含类似下⾯的信息:java.lang.StackOverflowError。
  • 另外,由于每个线程占的内存⼤概为1M,因此线程的创建也需要内存空间。操作系统可⽤内存-Xmx-MaxPermSize即是栈可⽤的内存,如果申请创建的线程⽐较多超过剩余内存的时候,也会抛出如下类似错误:java.lang.OutofMemoryError: unable to create new native thread
  • 相关的JVM参数有:

-Xss: 每个线程的堆栈⼤⼩,JDK5.0以后每个线程堆栈⼤⼩为1M,以前每个线程堆栈⼤⼩为256K.

在相同物理内存下,减⼩这个值能⽣成更多的线程.但是操作系统对⼀个进程内的线程数还是有限制的,不能⽆限⽣成,经验值在3000~5000

  • 可能原因:

递归:递归⾥⽤到的局部变量存储在堆栈中,堆栈的访问效率⾼,速度快,但空间有限,递归太多变量需要⼀直⼊栈⽽不出栈,导致需要的内存空间⼤于堆栈的空间,栈空间是2M,堆空间内存空间。

运⾏时常量溢出(constant):

  • 运⾏时常量保存在⽅法区,存放的主要是编译器⽣成的各种字⾯量和符号引⽤,但是运⾏期间也可能将新的常量放⼊池中,⽐如String类的intern⽅法。如果该区域OOM,错误结果会包含类似下⾯的信息:java.lang.OutofMemoryError: PermGen space
  • 相关的JVM参数有:

-XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64

-XX:MaxPermSize:设置持久代最⼤值,默认为物理内存的1/4

⽅法区溢出:

  • ⽅法区主要存储被虚拟机加载的类信息,如类名、访问修饰符、常量池、字段描述、⽅法描述等。理论上在JVM启动后该区域⼤⼩应该⽐较稳定,但是⽬前很多框架,⽐如Spring和Hibernate等在运⾏过程中都会动态⽣成类,因此也存在OOM的⻛险。如果该区域OOM,错误结果会包含类似下⾯的信息:java.lang.OutofMemoryError: PermGen space
  • 相关的JVM参数有:

-XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64

-XX:MaxPermSize:设置持久代最⼤值,默认为物理内存的1/4

24 如何定位jvm内存信息?

方法一:打印⽇志

-XX:+PrintGC:输出形式: 
[GC 118250K->113543K(130112K), 0.0094143 secs] 
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:输出形式: 
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] 
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs
-XX:+PrintGCTimeStamps:打印GC停顿耗时
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间. 
-XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
-Xloggc:filename:把相关⽇志信息记录到⽂件以便分析.

方法二:错误调试

-XX:ErrorFile=./hs_err_pid<pid>.log:如果JVM crashed,将错误⽇志输出到指定⽂件路径。
-XX:HeapDumpPath=./java_pid<pid>.hprof:堆内存快照的存储⽂件路径。
-XX:-HeapDumpOnOutOfMemoryError:在OOM时,输出⼀个dump.core⽂件,记录当时的堆内存快照

方法三:类状态器相关

-XX:-TraceClassLoading:打印class装载信息到stdout。记Loaded状态。
-XX:-TraceClassUnloading:打印class的卸载信息到stdout。记Unloaded状态。
25 当对象A创建之后,对象A在各个区之间的流转过程

jvm堆结构图:

  • 新⽣代通常占JVM堆内存的1/3,因为新⽣代存储都是新创建的对象,⽐较⼩的对象,⽽⽼年代存的都是⽐较⼤的,活的久的对象,所以⽼年代占JVM堆内存较⼤;
  • 新⽣代⾥的Eden区通常占年轻代的4/5,两个Survivor分别占新⽣代的1/10。因为Survivor中存储的是GC之后幸存的对象,实际上只有很少⼀部分会幸存,所以Survivor占的⽐例⽐较⼩。

对象流转流程(新⽣代复制算法,可以减少内存碎⽚)

  • 对象A被new出来之后,是被存放在Eden(伊甸园)区的。
  • 当发⽣⼀次GC后,Eden区存活下来的对象A会被复制到s1区,s0中存活的对象也会被复制到s1中。

如果对象年龄超过阈值年龄(默认15岁),会被复制到⽼年区。部分对象也需要⽼年代分担。

  • GC会清空Eden和s0中存储的所有对象;
  • 交换s0和s1的⻆⾊;
  • 重复上⾯的步骤。
26 jvm堆持久代

⽤于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应⽤可能动态⽣成或调⽤⼀些Class,例如 Hibernate CGLib 等,在这种时候往往需要设置⼀个⽐较⼤的持久代空间来存放这些运⾏过程中动态增加的类型。

27 synchronized和ReentrantLock

synchronized⽤的锁是存在Java对象头⾥的

目录
相关文章
|
3天前
|
存储 Java
深入理解Java虚拟机:JVM内存模型
【4月更文挑战第30天】本文将详细解析Java虚拟机(JVM)的内存模型,包括堆、栈、方法区等部分,并探讨它们在Java程序运行过程中的作用。通过对JVM内存模型的深入理解,可以帮助我们更好地编写高效的Java代码,避免内存溢出等问题。
|
5天前
|
Java
Java面向对象实践小结(含面试题)(下)
Java面向对象实践小结(含面试题)(下)
15 1
|
4天前
|
存储 机器学习/深度学习 Java
【Java探索之旅】数组使用 初探JVM内存布局
【Java探索之旅】数组使用 初探JVM内存布局
13 0
|
4天前
|
小程序 Java 程序员
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
15 0
|
5天前
|
存储 算法 Java
JVM常见面试题
JVM常见面试题
8 0
|
5天前
|
存储 缓存 开发框架
Java基础面试题小结(下)
Java基础面试题小结(下)
12 0
|
5天前
|
存储 安全 Java
Java基础面试题小结(上)
Java基础面试题小结(上)
13 0
|
5天前
|
设计模式 Java
Java面向对象实践小结(含面试题)(上)
Java面向对象实践小结(含面试题)
12 1
|
7天前
|
Java
【JAVA面试题】static的作用是什么?详细介绍
【JAVA面试题】static的作用是什么?详细介绍
|
7天前
|
Java
【JAVA面试题】final关键字的作用有哪些
【JAVA面试题】final关键字的作用有哪些