类加载器:加载.class文件 -> 运行时数据区
运行时数据区:
堆:内存空间最大,new 出来的对象均存在此处;新生代(Eden ToSurvior FromSurvior 8:1:1) 老年代
栈:线程栈;线程独享
局部变量表
操作数栈
动态链接:引入的方法等,这些需要获得直接引用(线程中 调用某一方法,只存储方法名,真正运行时需要解析成方法的直接引用处)
方法出口
程序计数器:线程独享,标识当前程序运行位置
方法区(元空间):类元信息、常量池、静态变量
本地方法栈:其它编程语言交互接口
字节码执行引擎:1 调度class文件中的指令 2 调用垃圾收集器
本地接口:其它编程语言交互接口
创建对象的几种方式:
new关键字
Class newInstance
Constructor newInstance
clone
反序列化
流程:
类加载检查:使用new关键创建对象时,jvm先判断该类是否被加载,没有被加载则调用类加载器加载当前类
分配内存:给当前对象分配内存(内存规整:指针碰撞 内存不规整:空闲列表)
初始化:静态变量、String常量存储在元空间,并对属性赋初值
设置对象头:每个对象都会有monitor监视(锁状态:无锁、偏向锁、轻量锁、重量锁),hashCode,分代年龄,线程id;并且保有指向类元信息的指针(类加载时,属性类型、size,动态链接等信息是保存在元空间的)
执行init:对属性赋值(程序员自己设置的值)
本质原因:长生命周期的对象一直存着短生命周期对象的引用(导致短生命周期的对象一直无法被GC回收)
ThreadLocal为什么会发生内存泄漏:
使用ThreadLocal:为了让每个线程的变量是独享的(线程栈创建ThreadLocal对象,堆内存存储,而每个Thread对象有个成员变量=ThreadLocalMap
单个线程若执行结束后,数据会被清空,但一般是使用线程池,让线程得到循环利用,若ThreadLocal使用后没进行remove,就会堆积很多无法访问的区域,导致内存泄漏
1 在JVM中,释放内存的操作由垃圾回收程序实现(低优先级)
2 垃圾回收原理:可达性分析,创建对象时,就将引用对象串联起来,标记哪些对象被使用,释放不被使用的垃圾;程序员本身不好把控内存的释放,使用Java提供的GC
3 java中的引用类型
强引用:new 对象,只有当对象置空时才回收
软引用:有用非必须对象,内存不够会回收(浏览器缓存)
弱引用:下次GC会回收
虚引用:
4 如何判断对象可以被回收:引用计数法(无法解决循环引用)、可达性分析GC Roots向下串联引用的对象(当对象没被GC Roots引用时,可以被回收)
5 JVM垃圾回收算法
标记-清除:标记无用对象并回收,会产生内存碎片、效率低
标记-整理:在标记清除的基础上,对存活对象进行整理;解决内存碎片问题,但需要复制对象,存在效率问题
复制:内存划分为两块,存货对象复制到另一块区域;可用内存减半
分代:目前市面上JVM选择,将内存区域划分为新生代和老年代
6 JVM垃圾收集器
新生代:
Serial:串行,简单高效;复制算法
Parallel:追求高吞吐量=用户线程时间/(用户线程时间+垃圾回收时间);适用于交互不高的场景
ParNew:Serial的并行版本;为了配合老年代的CMS;复制算法
G1:并行收集器;标记整理
老年代:
Serial Old:串行;标记整理算法
Parallel Old:追求高吞吐;标记整理
CMS:高并发、低停顿;标记清除
G1:并行收集器;标记整理
7 分代回收理论
新生代(占比1/3)(Eden FromSurvivor ToSurvivor 8:1:1):对象一开始分配在Eden区,当对象被调用时,分代年龄+1(对象头标识的分代年龄),触发MinorGC时,将Eden FromSurvivor存活的对象移动到ToSurvivor区;当分代年龄达到15时,移动到老年代
老年代(占比2/3):当老年代内存不够时,触发FullGC,Stop The World;比MinorGC慢10倍,要避免经常触发FullGC
新生代->老年代的潜规则:
大对象直接进入老年代:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC;新生代内存远小于老年代,为了避免大对象占用新生代内存,可以设置大的对象直接进入老年代
长期存活对象进入老年代:默认15,可以修改
对象动态年龄判断:Survivor区一批对象的总大小>50%(-XX:TargetSurvivorRatio)时,会将分代年龄为n的直接移动到老年代
老年代空间担保机制:在接收年轻代对象时,会对自己的内存进行判断(-XX:-HandlePromotionFailure),若没有足够空间,老年代会进行FullGC,若FullGC后还没内存,则会OOM
1 类装载方式
隐式装载:碰到new关键字
显示装载:class.forname()
2 类加载器
启动类加载器:java核心类库
扩展类加载器:java的扩展库(导入的包等)
系统类加载器:根据类路径加载类,应用类均是由其加载
用户自定义加载器:继承ClassLoader
3 类装载步骤
加载:根据路径找到相应的class文件后导入
验证:检查class文件的正确性
准备:静态变量分配内存空间并赋初值
解析:常量引用替换为直接引用,如引用了其它类时需要直到其引用地址
初始化:静态变量和静态代码块赋予期望值
4 双亲委派机制
一个类加载器收到了类加载请求,自己不会加载该类,会交给父加载器区完成,只有当父加载器无法加载类时,才交给子加载器加载
收到类加载请求时会遵循:自定义类加载器->应用程序类加载器->扩展类加载器->启动类加载器,上层类加载的优先级更高
String、List等均属于高优先级的类,若我们想让类加载器加载我们自己定义的String类,必须打破双亲委派机制
1 什么情况下需要进行JVM调优
应用程序响应时间过长,无法满足业务需求
应用程序的吞吐量下降,无法满足业务需求(垃圾收集时间过长)
应用程序内存使用率过高,甚至出现OOM(老年代内存不合理、分代回收时各参数设置不合理)
应用程序频繁GC或GC时间过长,影响了程序的性能
2 JVM调优准备工作
足够的测试和验证(分析内存占用情况、GC表现)