jvm 基础
1.jvm 类加载机制
Java采用的事Hotspot 虚拟机,按需加载 ,懒加载的方式。
2.类的加载时机
1.使用new实例化对象时,读取或设置一个类的静态字段或方法时;
2.反射调用时,如Class.forName("com.xx.MyTest");
3.初始化一个类的子类,会首先初始化子类的父类;
4.jvm启动时标明的启动类
5.jdk8,接口存在default方法,接口的实现类初始化时,接口会先初始化。
3.类的加载过程
.java --->通过javac命令--->.class,classLoader 将.class文件中的二进制数据读入到内存中,
并将其放在元空间,然后在堆区创建一个java.lang.Class对象,用来封装元空间的数据结构,类加
载的最终产物是Class对象,他封装了类在方法区的数据结构,并向程序员提供了访问方法区数据
结构的接口。
4.类的初始化的过程:
1.类变量赋值(真正的值),执行静态代码块;普通代码块在实例化时执行;
2.实例化时,静态代码,变量,普通代码块,构造器。
3.父子类的初始化关系
父静态变量/代码块->子类静态变量/静态代码块->父类变量/代码块/构造器 -> 子类变量/
代码块/构造器
5.类的生命周期
加载->验证->准备->解析->初始化->使用->卸载
- 加载:读取二进制字节码,静态存储结构转化为方法区的数据结构,在堆中生成java.lang.Class对象
- 验证:文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备:静态变量(类变量、static)分配内存,初始化默认值(0\null\false)。同时被final和static修饰,初始化值。
- 解析:符号引用(类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用限定符)字面量 转变为直接引用(指针、相对偏移量、间接定位到目标的句柄)。
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
- 使用:
- 卸载:所有的实例已销毁、该类的ClassLoader已被GC,类对应的java.lang.class没有被引用
6.类加载器
- 启动类加载器:Bootstrap ClassLoader----> /jre/lib
- 扩展类加载器:Extention ClassLoader---->/jre/lib/ext
- 应用程序类加载器:AppClassLoader(系统类加载器)---->classpath
- 自定义加载器:负责加载用户自定义路径下的类名,继承java.lang.ClassLoader
7.双亲委派:
双亲委派机制是指如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个
类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到
顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加
载,子加载器才会尝试自己去加载该类。
7.1双亲委派机制好处:
- 沙箱安全机制:保护java自己的类库,如String
- 避免类的重复加载(相同包名类名只加载一份)findLoadClass()
- 保证类的唯一性
7.2 如何打破双亲委派模型?
自定义一个类加载器,重写其中loadClass方法,使其不适用双亲委派模型
7.3 如何自定义类加载器
- 继承ClassLoad类,覆盖findClass(String name)(不打破双亲委派) 或者 loadClass()
- defineClass()----->C++实现,把字节码转化为java.lang.Class
- class.forName()(会初始化)和loadClass()(不会初始化)
7.4jvm 参数:
-XX:TraceClassLoding
java 内存模型
1.虚拟机栈
- 存放的数据包括:基本数据类型、对象的引用、方法帧、运行数据、指令、返回地址。(先进后出)
- 局部变量表:方法参数和局部变量
- 操作数栈:临时存放执行方法时要操作的数据
- 动态链表:
- 方法出口:
- jvm 参数:-Xss:每个线程栈内存大小
2.本地方法栈:native方法
3.程序计数器:
记录字节码的行号,也就是下一条指令
4.虚拟机堆:
对象、数组,静态变量(jdk1.8)gc操作对象,(新生代(1/3,Eden 8/10,from 1/10,to 1/10)、老年代)tlab
jvm参数:
- -Xms:初始化堆大小
- -Xmx:堆的最大值
- -Xmn:新生代大小
- -XX:NewSize 新生代的初始值
- -XX:MaxNewSize 新生代的最大值
5.元空间(方法区):
类信息(类全称、父类全程),域信息(域名称、域修饰服等),方法信息(名称、修饰符、返回类型等),运行时常量池,final,(持久代)
jvm参数:
- -XX:MetaspaceSize;
- -XX:MaxMetaspaceSize
- 1.Java代码是如何运行起来的?.java->javac class java java -jar tomcat startup.sh->catalina.sh-> Bootstrap中的main
- jvm运行原理图,内存结构图?
- 请介绍一下JVM的内存结构的划分?
- JVM哪些区域事线程私有(线程安全)的?哪些区域是线程共有的?
jvm 垃圾回收机制
1.jvm 如何判断对象是否可以回收?
引用计数法(老)
可达性分析算法,gc roots 引用链,(栈中的引用的对象、元空间区静态变量、元空间常
量、jni、基本数据类型对应的class、常驻异常、synchronization对象)
2.java的引用类型?
- 强引用:new 内存溢出也不回收
- 软引用:SoftReference 内存充足不回收,内存不足回收。适合做缓存,mybatis框架
- 弱引用:WeakReference gc运行回收 jdk中有ThreadLocal
- 虚引用:PhantomReference gc回收时发送系统通知
3.jvm垃圾回收算法?
- 标记-清除法 效率不高,内存碎片
- 标记-复制算法 内存利用率低
- 标记-压缩(整理)算法 性能低
- 分代收集算法:新生代采用复制算法,老年代采用标记-压缩算法
4.jvm堆中新生代的垃圾回收过程?
- Eden满了->Minor GC -> from Surviver,清空Eden --> Eden满了 ---> Minor GC -> to Surviver,清空Eden、from Surviver ..... -> gc 年龄=15 -> 老年代
- 15次 -XX:MaxTenuringThreshold=10
- CMS gc 6次
- gc 默认为 并行gc
- jvm参数
查看当前gc java -XX:+PrintFlagsFinal
java -XX:+PrintCommandLineFlags
5.JVM动态年龄判断是怎么回事?
Survivor区的对象年龄从小到大进行累加,当累加的x年龄时综合大于50%,那么比x大的都会晋升到老年代50% 可以通过jvm参数: -XX:TargetSurvivorRatio=? 设置
6.JVM 空间分配担保机制?
Minor GC ---->检查老年代可用空间是否大于新生代对象总大小?大于Minor gc 小于----> 老年代可用空间大小,是否大于历史对象均值?小于 Full gc 大于---->Minor gc 对象还是放不下 Full GC
目的:减少Full GC的次数
7.什么情况下对象进入老年代?
1)gc年龄大于15 ,cms gc 默认为6
2)动态年龄判断
3)jvm老年代空间分配担保机制。
4)大对象直接进入老年代,通过参数 -XX:PretenureSizeThreshold 设置
8.JVM垃圾收集器
新生代收集:Minor GC / Yong GC
老年代收集:Major GC /Old GC
整堆收集: Full GC
混合收集: Mixed GC
Minor GC
Serial :标记-复制算法,串行,暂停用户工作 -XX:+UseSerialGC
ParNew :是Serial的多线程版本 -XX:+UseParNewGC -XX:+ParallelGCThreads=2
Parallel Scavenge:复制算法,并行多线程,侧重吞吐量,jvm默认 -XX:MaxGCPauseMillis=50 毫秒,暂停时间 -XX:+UseAdaptiveSizePolicy 自适应新生代大小策略
Major GC
SerialOld : 标记-整理算法
ParallelOld:jdk1.8 默认
CMS ConcurrentMarkSweepGC 最少停顿时间 标记清除算法,多线程,和应用程序并行 -XX:+UseConcMarkSweepGC 此时新生代 使用的ParNewGC
jvm 性能调优
1.原则
减少Full GC的频率,或者避免FullGC,FullGC耗时
jvm常用参数:
- -Xms2048m:初始堆大小,建议<物理内存的1/4,默认值为物理内存的1/64
- -Xmx2048m:最大堆大小,建议与-Xms保持一致(因为如果不一致,会扩容,降低性能),默认值为物理内存的1/4
- -Xmn512m:新生代大小,建议不超过堆内存的1/2
- -XX:MetaspaceSize 元空间
- -XX:MaxMetaspaceSize 最大元空间
- -Xss256k,线程栈大小,建议256k(java8建议1m)
- -XX:PermSize=256m(java8后写成-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=64):永久代初始值,默认值为物理内存的1/64
- -XX:MaxPermSize=256m:永久代最大值,默认值为物理内存的1/4
- -XX:SurvivorRatio=8:年轻带中Eden区和Survivor区的比例,默认为8:1,即Eden(8),From Space(1),ToSpace(1)
- -XX:MaxTenuringThreshold=15:晋升到老年代的对象年龄,每个对象坚持过一次MinorGC后对象年龄+1,默认值是15,年龄超过15进入到老年代,该参数在串行GC时有效
- -XX:PretenureSizeThreshold=3145728:单位字节,只对Serial和ParNew两款收集器有效,新生代采用Parallel Scavenge GC时无效,大于这个值的对象直接在老年代进行分配
- -XX:+UseConcMarkSweepGC:开启CMS垃圾回收器
2.日志参数:
- -XX:+HeapDumpOnOutOfMemoryError:当发生内存溢出时,进行堆内存dump
- -XX:+PrintGCDetails:打印GC的详细信息
3.查看默认的堆大小及默认的垃圾收集器
java -XX:+PrintCommandLineFlags -version
内存溢出如何分析
1.堆内存溢出,OutOfMemoryError:java heap space
原因:
Java堆用于存储对象实例,只要不断创建对象,并保证GC Roots到对象间有可达路径避免这些对象的GC,当对象数量达到堆的最大容量限制后就会产生OOM
解决方法:
通过参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath= 可以让虚拟机在内存溢出异常时Dump当前内存堆转储快照
通过内存映像分析工具(如:Eclipse Memory Analyzer,jvisualvm)对Dump出的堆转储快照分析,判断是内存泄露还是内存溢出
如果是内存泄露:通过工具查看泄露对象的类型信息和它们到 GC Roots 的引用链信息,分析GC收集器无法自动回收它们的原因,定位内存泄露的代码位置
如果是内存溢出:检查堆参数 -Xms和-Xmx,看是否可调大;代码上检查某些对象生命周期过长,持有时间过长的情况,尝试减少程序运行期间内存消耗
2. 栈内存溢出,StackOverflowError
原因:
StackOverFlowError异常:线程请求的栈深度大于虚拟机所允许的最大深度
OutOfMemoryError异常:虚拟机扩展栈时无法申请足够的内存空间
解决方法:
检查代码中是否有死递归;配置 -Xss 增大每个线程的栈内存容量,但会减少工作线程数,需要权衡
3.元空间内存溢出,OutOfMemoryError: Metaspace
解决方法:
-XX:MetaspaceSize 元空间
-XX:MaxMetaspaceSize 最大元空间