类加载
什么是类加载,发生在什么时期(编译/运行)
加载
1.1 通过一个类的全限定名来获取定义此类的二进制字节流。
1.2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
1.3 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
准备
正式为类变量(静态变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。--默认值
解析
虚拟机将常量池内的符号引用替换为直接引用的过程(静态链接,对应的有动态链接)
直接引用:直接引用可以是直接指向目标的指针、相对偏移量、或一个能间接定位到目标的句柄
初始化
在准备阶段,变量已经赋值过一次系统初始化值。在初始化阶段,则是根据程序中的赋值语句主动为类变量和其他资源赋值,或者说初始化阶段是在执行类构造器<clinit>()方法的过程。
加载-连接(验证-准备-解析)-初始化
初始化-clinit解析
static变量代码块顺序执行,子类->触发父类,父类静态代码块优先于子类执行,接口不会触发父接口静态变量,实现类不会触发接口静态变量
什么时候会立即触发初始化/主动引用一个类
new初始化,static方法调用,反射调用,子类->触发父类,main方法主类
静态变量,系统初始化值与赋值是在什么时候
准备-初始化
什么是类加载器
类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序可以自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”
什么是双亲委派机制?双亲委派机制时机?好处?
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader中。当父类加载器反馈无法处理这个加载请求,子加载器才会尝试自己去加载。
时机:加载阶段
好处:Java类具备了一种带有优先级的层级关系
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
自己实现双亲委派
实现逻辑:jdk1.2后已不提倡用户去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass方法的逻辑里如果父类加载失败,则调用自己的findClass的方法来完成加载。这样可以保证新写出来的类加载器是符号双亲委派规则的。
打破双亲委派
典型的例子JNDI服务(对资源及性能集中管理和查找),它需要调用 由独立厂商实现并部署在应用程序的classpath下的JNDI接口提供者(SPI)的代码,但是启动类加载器不可能认识这些代码。
为了解决这个问题,java设计了“线程上下文类加载器(Thread Context ClassLoader)”。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法及性能设置,如果线程创建时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是程序类加载器。
JNDI服务使用线程上下文类加载器去加载需要的SPI代码,也就是父类加载器其请求子类加载器去完成类加载的动作。java设计SPI的加载动作基本采用这种方式,如JDBC、JNDI、JCE、JAXB、JBI等。
关于JDBC打破双亲委派模型
参考:https://www.jianshu.com/p/09f73af48a98
https://www.jianshu.com/p/78f5e2103048
java内存区域
程序计数器
本地方法栈:提供native方法服务
虚拟机栈-栈帧:局部变量表、动态连接,返回地址
堆-逃逸分析、新生代老年代、垃圾回收
方法区-元空间/永久代;
永久代:静态变量+常量池转移到堆中,类的元信息转移到元空间,静态变量逻辑上属于方法区,物理上属于堆
MetaspaceSize 表示的并非是元空间的大小,它的含义是:主要控制matesaceGC发生的初始阈值,也就是最小阈值。也就是说当使用的matespace空间到达了MetaspaceSize的时候,就会触发Metaspace的GC,也就是fullGC。
动态链接:符号引用转直接引用,每个栈帧包含一个指向运行时常量池中该栈帧所属方法的引用(运行期间完成),同类加载时解析阶段静态链接-符号引用转直接引用
静态链接:将一些符号引用(静态方法)替换为指向数据内存的指针或句柄(类加载解析期间完成)
逃逸分析
方法逃逸,线程逃逸,从不逃逸
分析对象动态作用域,判断对象逃逸级别
栈上分配
不会逃逸线程外,可考虑栈上分配;对象所占用的内存可以随栈桢出栈而被摧毁;HotSpot不支持
标量替换
不会逃逸出方法范围外,可考虑标量替换;把一个java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问
同步消除
不会逃逸出线程外,代表对象非共享,即可以将同步锁消除掉
对象
对象的创建过程
1.定位符号引用与加载 :当Java虚拟机遇到一条字节码new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。
2.分配内存(指针/空闲列表) :类加载检查通过后,虚拟机将会为新生对象分配内存
3.对象头设置:如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的GC分代年龄信息等。
对象的内存布局
1.对象头:Mark Word,元数据类型指针,数组)
2.实例数据 :在程序代码里定义的各种类型的字段内容
3.对齐填充
对象的访问定位
句柄:本地变量表->句柄池(到对象实例的指针/元数据指针)->对象地址引用/元数据引用
直接指针:本地变量表->堆对象地址引用->元数据引用
HotSpot选择指针,主要由于java对象访问非常频繁,节约开销
垃圾回收
哪些内存需要回收
方法区,堆:共享,动态,需要回收
程序计数器,本地方法栈,虚拟机栈:线程级别,不需要回收
垃圾对象判定-可达性分析-GCRoot、引用计数法
引用计数法:相互引用没办法处理
可达性分析:判断一个对象到GC Roots 没有任何引用链
GC Roots:当前栈帧引用的,类静态属性引用的,常量引用的对象,同步锁持有的
Native方法的引用对象,java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(如NullPointException,OutOfMemoryError)等,还有系统类加载器
对象引用
强引用:永远不会回收。
软引用:内存溢出之前进行回收。
弱引用:被弱引用关联的对象只能生存到下一次垃圾回收之前。
虚引用:任何时候都可能被垃圾回收器回收。
方法区回收--内容,判定方式
内容:废弃的常量,不再使用的类型(无用的类)
判定方式:1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2.加载该类的 ClassLoader 已经被回收。
3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾回收算法--复制、标记清除、标记整理
复制:只可使用一半内存
标记清除:标记+清除;碎片化问题,GC速度快;
标记整理:标记+移动+清除;内存规整,对整体吞吐友善,GC速度慢;
标记清除与标记整理对比
标记清除:标记+清除;碎片化问题,
标记整理:标记+移动+清除;
标记STW,移动STW
垃圾收集器
收集器 | 特点 |
---|---|
serial | 单线程 新生代 复制 |
parnew | 多线程并行 新生代 复制 |
parallel scavenge | 多线程并行 新生代 复制 注重吞吐量 |
serial old | 单线程 老生代 标记整理 |
parallel old | 多线程并行 老生代 标记整理 |
cms | 多线程并发 老年代 标记清除 最短停顿时间 (初始标记+并发标记+重新标记+并发清理) 两次stw(初始阶段+重新标记) 初始标记简单标记1次stw,重新标记解决并发标记时程序运作时产生的垃圾记录 缺点:1.cpu敏感,占用cpu线程=(cpu数量+3)/4 2.cms无法处理浮动垃圾,预留空间92%如果不足出现并发失败concurrent mode failure导致fullGC,启动serialold,原因:并发清理用户程序运行产生新的垃圾>8% 3.标记-清除,产生不连续空间碎片,导致提前fullGC |
g1 | 多线程并发+并行 老年代+新生代 全局标记整理,局部复制(无碎片化问题) 并行并发,分代收集,空间整合,可预测停顿 younggc动态调节 mixedgc(初始标记+并发标记+最终标记+筛选回收) 化整为零 |
内存分配与回收策略
优先eden,大对象老年代,15次存活老年代,空间担保机制计算老年代空间是否足够
cms
初始标记(initial mark,STW): 暂停所有的其他线程,仅仅标记下gc roots直接能关联到的对象,速度很快 ;
并发标记(concurrent mark): 从GC Roots的直接联系对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发进行。
重新标记(remark, STW): 重新标记阶段就是为了修正并发标记期间,因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清理(concurrent sweep): 清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
g1
整堆分成了2048个region,每个region可能是老年代/新生代
其他垃圾收集器,是整堆收集(新生代/老年代),g1面向局部
提供可停顿时间预测,能收集多少region收集多少region,对region进行优先级划分
对象创建->g1指定一个region作为eden,eden满了之后不会立即触发minorgc,会预测当前region收集时间与预测时间做对比,如果远远小于预测时间,扩容eden region数量,知道eden满了且预测时间接近,触发minor GC,或eden达到整堆空间60%,触发minor GC->
minorGC后,eden转到survier,多次后转到old->old达到阈值45%后(当对象大小达到region大小一半,将其放入Humongous region,其也属于old),触发mixed GC->mixedGC)
初始标记stw(优化:借助于minorGC标记的GC root),并发标记,最终标记,筛选回收stw(年轻代全部回收,minorgc回收只收集eden+servier,mixed会收集年轻代+老年代,region全是垃圾对象被收集,部分垃圾的region按效率看情况收集,会筛选8次,整个回收的region集合称为cset)
region局部复制算法,整体标记整理,region空间不足触发fullGC
rset跨region引用,从年轻代指向老年代
什么时候触发fullgc
老年代空间不足
调用 System.gc()
空间担保机制失败(1每次晋升到老年代对象平均大小 > 老年代剩余空间2 MinorGC后存活对象超过了老年代剩余空间)
元空间MetaspaceSize达到阈值
jdk7永久代满
什么时候触发stw
标记移动-触发stw
cms:初始标记,最终标记
g1:初始标记,最终标记,筛选回收(涉及移动)
jvm工具
jstat jstack jmap jvisual
heapdump
threaddump
内存泄露,频繁fullgc问题
网友评论