1、详细jvm内存模型
共享部分(每个JVM实例一份):
1)方法区:一个概念性的区域,jdk1.8之前使用永久代的方式实现,存储类信息,方法信息,常量,静态变量,JIT编译后的代码等数据。
jdk1.8之后,方法区概念还存在,永久代被替换成元数据区(MetaSpace),使用本地内存,存储类信息,代码信息。常量,静态变量被分配到heap中。虽然没有PermSpace的OOM,但是MetaSpace也有最大内存限制,也会出现OOM
原本永久代存储的数据:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
Metaspace(元空间)存储的是类的元数据信息(metadata)。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
替换的好处:一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
2)堆,所有的对象分配的内存都是从这里分配的,GC回收的也是这里的内存。
不共享部分(每个线程一份,一个JVM实例包含多个线程)
1)程序计数器,保存当前执行到哪一行代码,如果是调用native方法,计数器数值保存的是undefined。
2)Java虚拟机栈,保存的是调用链,每调用一个方法会新建一个栈帧压入栈中,栈帧中包含请求参数,局部变量,返回值地址等等。等这个方法执行完就会删除。如果调用链过长,就会抛出StackOverflowError异常,如果栈帧申请的内存过大,就会出现OutOfMemoryError异常。
3)本地方法栈,类似于虚拟机栈,虚拟机栈保存的是纯java方法,本地方法栈保存的是native方法,除了java语言编写的都是native 方法。
方法栈内:
2、permgen space错误
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar,或者使用了cglib动态代理,有很多子类class, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。修改了jvm参数,增加内存分配大小。JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)。这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用。
3、说说Java线程栈
线程堆栈也称线程调用堆栈,是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某一个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况。虽然不同的虚拟机打印出来的格式有些不同,但是线程堆栈的信息都包含:
1、线程名字,id,线程的数量等。
2、线程的运行状态,锁的状态(锁被哪个线程持有,哪个线程在等待锁等)
3、调用堆栈(即函数的调用层次关系)调用堆栈包含完整的类名,所执行的方法,源代码的行数。
借助堆栈信息可以帮助分析很多问题,如线程死锁,锁争用,死循环,识别耗时操作等等。在多线程场合下的稳定性问题分析和性能问题分析,线程堆栈分析是最有效的方法,在多数情况下,无需对系统了解就可以进行相应的分析。
由于线程堆栈是系统某个时刻的线程运行状况(即瞬间快照),对于历史痕迹无法追踪。只能结合日志分析。总的来说线程堆栈是多线程类应用程序非功能型问题定位的最有效手段,最善于分析如下类型问题:
https://blog.csdn.net/weiweicao0429/article/details/53185999
4、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式
启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher.ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,如果父加载类为null,使用启动类加载器加载,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。jvm虚拟机通过类的全路径名称+类加载器唯一确定一个类,防止其他类加载器加载混有恶意代码的同名类,同时也避免同一个类被多次重复加载
https://blog.csdn.net/u011080472/article/details/51332866
https://blog.csdn.net/huachao1001/article/details/52297075
破坏双亲委派,线程上下文件类加载器(Thread Context ClassLoader)
通过自定义ClassLoader,并重写父类的loadClass方法
https://blog.csdn.net/zhangcanyan/article/details/78993959
5、JVM 年轻代到年老代的晋升过程的判断条件是什么呢
1)部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
2)hotspot虚拟机,server模式:如果某次分配内存的时候,新生代内存不足时会进行内存分配担保:在Serial+Serial Old的GC策略下,会先进行一次minorGC(即YGC),如果还是放不下就会把新生代的存活的对象搬到年老代,然后新生代腾出来的空间用于为分配给最新的对象;在Parallel Scavenge+Serial Old的GC策略下,先判断要分配的内存是不是>=Eden区大小的一半,如果是那么直接把该对象放入老生代,否则才会做MinorGC然后判断是否移动新生代数据到老年代。
3)这里有个知识点:GC担保机制,在发生担保机制的时候,有可能出发FGC,如果之前从新生代移动到年老代的平均对象大小 > 年老代剩余内存大小,则在移动之前进行一次FGC
参考:内存分配回收策略 担保策略在最后
JVM内存分配担保机制
6、JVM 出现 fullGC 很频繁,怎么去线上排查问题
1)是不是频繁分配大对象,fullGC之后如果对象剩余不多,那么可能是eden区太小导致短生命周期的对象进入老年代,或者生成了较大但临时的对象(参考上面第5点),如果对象回收率不大,说明是老年代太小。
7、分层编译:TieredCompilation
在未启用分层编译的时候,java程序通常先采用解释执行,运行一段时间后不断采用优化策略,例如JIT,提升性能,所以就会出现应用程序刚启动的时候,CPU负载比较高,因为不仅有GC线程,业务线程,还有编译解释线程。
采用了分层编译,可以在启动后更快的让部分代码先进入编译模式,确实可以降低一部分CPU负载,但是也会带来堆外内存的使用增加,JIT编译的内容放在堆外内存。