JVM堆的基本结构

堆里面存放了对象的实例和数组,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么绝对了。
堆是垃圾收集器管理的主要区域,由于垃圾收集器采用分代收集算法,可以细分为Eden区,Form Surivor区、To Surivor区。
新建对象分配在eden区,大对象会直接进入老年代(jdk8实现为MetaSpace),这个取决于使用的哪种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置,当eden区没有足够的空间进行分配时,虚拟机会发起一次minor GC(minor GC发生在新生代,会将eden区和from区的存活对象复制到to区,eden区和from区清空,from和to的角色互换)。
长期存活对象进入老年代,每个对象都有年龄计数器,没熬过一次minor GC,年龄会增加1岁,当增长到一定程度(默认15岁)就会晋升到老年代,可以通过参数设置这个值,当survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到参数设置的年龄。
在发生minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次minorGC,尽管是有风险的(由于参考的经验值,当出现极端情况,新生代对象都存活,那么老年代无法容纳全部对象,造成分配失败);如果小于,进行Full GC

JVM垃圾收集算法有哪几种?CMS垃圾收集算法的基本流程?

标记-清除算法:首先标记需要清除的对象,然后统一回收所有被标记的对象,会产生内存碎片
复制算法:将内存分为相等的两块,每次只使用其中一块,当一块的内存用完了,就将还存活的对象复制到另一块上面,然后将使用过的内存空间一次清理掉,空间利用率低,适合用于新生代
标记-整理算法:复制算法在对象存活率高的时候,需要复制较多对象,效率会变低,首先将可回收对象标记出来,然后将所有存活对象都向一端移动,然后将剩余空间全部清空。
分代收集算法:分代收集算法只是根据对象存活周期的不同,将内存划分为几块。一般是划分为新生代和老年代,然后采用合适的算法,新生代采用复制算法,老年代采用标记-清理或者标记-整理算法。

HotSpot虚拟机实现的一些垃圾收集器

JVM有哪些常用的参数可以调整?

-Xms3550m:设置JVM初始内存为3550m,此值可以设置-Xmx相同,以避免每次垃圾回收完成以后JVM重新分配内存
-Xmx3550m:设置JVM最大可用内存为3550m
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一般固定为64M,所以增大年轻代后,将会减少年老代大小,此值对系统性能影响比较大,Sun官方推荐配置为整个堆的3/8
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程堆栈大小为256k。根据应用的线程所需要内存大小进行调整。在相同物理内存下,减少这个值能够生成更多的线程。但是操作系统对一个进程内的线程还是有限制的,不能无限生成,经验值在3000-5000左右。
-Xss1024k:设置每个线程的堆栈大小

如何查看JVM的内存使用情况?

jstat命令可以查询各部分内存的使用情况

内存溢出和内存泄露?

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。

内存泄露的几种情况
长生命周期的对象持有短生命周期对象的引用
修改hashset中对象的参数值,且参数是计算哈希值的字段
内存溢出的几种情况:
堆内存溢出(outOfMemoryError:java heap space)
方法区内存溢出(outOfMemoryError:permgem space)
线程栈溢出(java.lang.StackOverflowError)

你常用的JVM配置和调优参数都有哪些?分别什么作用?

JVM的内存结构?

常用的GC策略,什么时候触发YGC,什么时候触发FGC?

1 串行&并行
串行:单线程执行内存回收工作。十分简单,无需考虑同步等问题,但耗时较长,不适合多cpu。
并行:多线程并发进行回收工作。适合多CPU,效率高。
2 并发& stop the world
stop the world:jvm里的应用线程会挂起,只有垃圾回收线程在工作进行垃圾清理工作。简单,无需考虑回收不干净等问题。
并发:在垃圾回收的同时,应用也在跑。保证应用的响应时间。会存在回收不干净需要二次回收的情况。
3 压缩&非压缩
压缩:在进行垃圾回收后,会通过滑动,把存活对象滑动到连续的空间里,清理碎片,保证剩余的空间是连续的。
非压缩:保留碎片,不进行压缩。
YGC的 触发时机,相当的显而易见,就是eden空间不足, 这时候就肯定会触发ygc

对于FGC的触发时机, old空间不足, 和perm的空间不足, 调用system.gc()这几个都比较显而易见,就是在这种情况下, 一般都会触发GC。

内存管理机制,如何回收过期对象,判断依据,内存溢出场景,如何排查等等(jvm的内存结构,回收过程,及虚拟机相关的回收算法及设计初衷)

jvm的内存分为几个区域:
1)方法区,用来存储已加载的类信息和常量,静态变量,即时编译器编译后的代码;
2)虚拟机栈,是描述方法执行的内存模型,每个方法执行都会创建一个栈帧,用来存储局部变量,操作数栈,动态链接,方法出口等信息;
3)本地方法栈,是为本地方法服务的;
4)堆,所有实例空间的分配都是在堆上完成的;
5)程序计数器,用来存放当前线程指令的地址,如果是native方法则为空;
6)直接内存,是直接从堆外申请的内存,不受虚拟机参数影响。
java通过可达性分析算法来确定哪些对象需要回收,通过一系列的GC Roots对象作为起点,不可达的对象可以回收,可以作为GC Roots的对象包括:1)虚拟机栈(栈帧中的本地变量表)中引用的对象;2)方法区中类静态属性引用的对象;3)方法区中常量引用的对象;本地方法栈中JNI(native方法)引用的对象。
申请内存时会遇到无法申请内存的情况,这时会抛出异常,stackoverflowError和OutOfMemoryError错误,可以使用dumpOutOfMemoryError参数,当虚拟机出现内存溢出时将当期堆栈信息输出到文件中,以便后面通过工具进行分析,可以使用可视化分析工具 visual vm 查找大对象,确定引用路径,是否必要去判断是内存设置太小还是没有及时释放对象引起的内存溢出。

JVM的内存模型是怎么样的?应用突然变得很慢,如何定位?

  1. jvm内存模型分为:1)程序计数器:用来存放当前指令的地址,如果是本地方法则为空,线程私有;2java虚拟机栈:是描述方法执行的模型,方法执行时会创建栈帧,保存局部变量表,操作数栈,动态链接,方法出口等信息,线程私有。3)方法区:用来存放加载的类信息、常量、静态变量、即时编译器编译后的代码等。4)本地方法栈:为本地方法服务。5java堆:所有对象都在堆上分配空间(对象实例和数组)。6)运行时常量池:用来存放编译期生成的各种字面量和符号引用。7)直接内存,这一部分不是虚拟机规定的,但是也是用的比较多的,只受系统内存的限制。



频繁出现fullGC会导致应用停顿时间过长,应用变的很慢甚至无反应,这个查看GC日志,dump当前的堆栈信息,分析是否频繁fullGC,以及引起fullGC的原因

Java的类加载器的机制(利用classloader特性的一些场景应用 )

  1. java类加载机制是使用的双亲委派模型,引导类加载器(bootstarp ClassLoader)是有平台语言实现,负责加载JRE_HOME/lib目录下的jar包,然后是扩展类加载器(Extension ClassLoader)是由java实现,负责加载JRE_HOME/lib/ext目录下jar包,还有个应用类加载器(application ClassLoader),负责加载用户classPath目录下的类;类加载时首先会委托父类加载器加载如果找不到才会自己加载。


有特殊加载要求时可以自定义ClassLoader,例如需要校验class文件,加解密class文件等。