1、java类加载过程
1、判断是否需要被加载
主动引用(java类必须加载)
遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,如果类没有进行初始化,则先初始化。这4个字节码常见的出现场景是:使用new关键字实例化对象的时候,读取或设置静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
反射调用时。
初始化一个类时,如果其父类还未初始化,则先出发父类初始化。
当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类。
被动引用(java类不会被加载)
2、加载: 将class文件从jar或者磁盘文件中读到jvm中,并且为这些类创建一个对象。
- 加载方式:双亲委派模式
-
类加载器分类:
-
引导类加载器,负责加载支撑Jre/lib目录下的核心类库
-
扩展类加载器:负责加载Jre/lib目录下的ext扩展类jar包
-
应用程序类加载器:负责加载classpath下的类包
-
自定义类加载器:负责加载用户自定义路径下的类包
-
双亲委派模式 :
如果一个类加载器收到类加载请求,它首先不会自己去尝试加载这个类,而这个请求委派给父类加载器完成。每个类加载器都如此,只有当父加载器在自己搜索范围内找不到指定类时(即 ClassNotFoundException),子加载器才会尝试自己去加载。
怎么打破双亲委派模式:
打破双亲委派机制则不仅
要继承
ClassLoader
类,还要
重写
loadClass
和findClass
方法
如何打出线程栈信息:
- 输入jps,获得进程号。
- 可以使用 fastthread 堆栈定位
- 或者 jstack -l > /tmp/output.txt 堆栈信息打到一个 txt 文件
- jstack pid 命令查看当前 java 进程堆栈状态
- top -Hp pid 获取本进程中所有线程CPU 耗时性能
3、链接:验证、准备、解析
2-1、验证:
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求。
文件格式验证:
元数据验证:
字节码验证:
符号引用验证:
2-2、
准备:
2-3、解析
4、初始化:
2、jvm内存模型(运行时数据区)
1、程序技术器
2、java虚拟机栈(字节码级别)
用于描述java方法执行的线程内存模型,每个方法被执行的时候,jvm都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个java方法执行完毕,这个栈帧就是在jvm中从入栈到出栈的过程。
这里会诞生两种异常状况:
1、如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflow异常。
2、如果Jvm容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
局部变量表:编译期的基础数据类型、对象引用、returnAddress
3、本地方法栈(用于Natvie修饰的方法)
和java虚拟机栈发挥作用非常相似,区别在于java虚拟机栈只是针对字节码服务,而本地方法栈则是用到本地Native方法服务。
java如果需要调用其他语言解释方法的时候,则可以通过Natvie告知编译器应该需要使用其他语言解释。
4、java堆(多个线程直接共享区域)
所有对象实例和数据都应该在堆上分配(垃圾收集器内存:新生代、老年代、永久代、Eden空间、From Survior空间、To Survivor空间等),多个线程直接共享区域
-Xmx和-Xms
如果在Java堆中没有内存完成实例分配、并且堆也无法再扩展时,会抛出OutOfMemoryError异常。
5、方法区 (多个线程直接共享区域)
用于储存类型信息、常量、静态变量、即时编译器编译之后的代码缓存等数据,(永久代内存管理)
-XX:MaxPermSize
如果方法区无法满足新的内存分配,将抛出OutOfMemoryError异常。
6、运行时常量池
实际上也是方法区的一部分,用于存储编译期生成类的字面量与符号引用,可以管理运行期的常量内存(运行时常量池)
受方法区的影响, 如果方法区无法满足新的内存分配,将抛出OutOfMemoryError异常。
3、直接内存(非运行时数据区)
使用Native来分配堆外内存,不受java堆大小限制,但会受本机总内存限制(物理内存、SWAP分区和分页文件),通过java堆里面的DirectByteBuffer对象操作NIO的channel与缓冲区的I/O方式。可以提高显著提高通道和缓冲区的读写性能,但是分配回收的需要更大的代价。
如果直接内存无法满足新的内存分配,将抛出OutOfMemoryError异常。
5、分代收集机制:(堆内存模型)
新生代:(Eden From(S1) To(S2))
Major GC/Old GC
老年代(Tenured)
Full GC
收集整个Java堆/方法区。
命令学习:
- 默认,新生代 ( Young ) 与老年代 ( Old )比例值为 1:2 ,可以通过参数 –XX:NewRatio 配置
- 默认,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
- Survivor 区中对象被复制次数为 15(对应虚拟机参数 -XX:+MaxTenuringThreshold)
6、垃圾收集过程/一次GC过程:
Minor GC/Yong GC过程
1、收集GC root可达对象,分配到Eden区,
2、当Eden区满了情况下,将Eden中GC root可达的对象通过标记复制法复制到From区(S0),同时通过标记清除法清除Eden区。
3、当From区(S0)满了的情况下,将From区依旧被GC root可达的对象复制到To区(S1)区,
同时标记清除To区。
4、当To区满了情况下,To区被GC root可到的对象复制到From区,同时标记清除To区。
5、对于躲过一次Minor GC的GC root可达的对象,使用引用计数+1,当引用次数达到15之后,进入老年代收集
Major GC/Old GC
当老年代区域满了的情况下,不会使用标记复制方法,而是使用标记整理法,即将GC root可达的对象向内存空空间一端移动,然后使用标记清除法清除掉边界以外的内存。
这样的好处是,不用像minc gc 使用s1区和s2区,可以直接节约掉50%空间,而且不会频繁出现复制操作,效率更好。
7、对象回收
1、引用计数法:在对象中添加一个引用计数器,如果有一个地方引用这个对象,计数+1,当引用失效,计数-1,当对象的计数器为0的时候,回收对象。
2、可达性分析法:
从GC Roots开始,向下搜索引用,没有任何引用链的情况下,证明GC Root不可达到这个对象,回收对象。
可作为GC Roots对象
1、java虚拟机栈中引用的对象,包括参数、局部变量、临时变量
2、方法区中的静态变量、常量引用
3、JNI、native引用
4、java虚拟机内部的引用,基础数据类型包装对象引用、异常对象、系统类加载器
5、sunchronized关键字持有的对象。
强引用关系存在永远不会被回收。new
A a = new Aclass();
a=null;
a之后不能再被引用,jvm会回收。
软引用关系会在发生内存溢出之前进行两次回收,如果还没有足够的内存,内存还是会溢出的。SoftReference,可实现缓存。
弱引用关系对象,不管内存够不够,都会被回收掉。WeakReference。
虚引用,该引用无法获取一个对象的实例,只是为了能在这个对象被回收时收到一个系统通知。PhantomRefrence
8、垃圾收集算法
1、清除算法
2、复制算法
3、整理算法
4、分代收集
9、垃圾收集器
新生代收集器:Serial ParNew Parallel Scavenge
老年代收集器:Serial Old CMS Parallel Old
堆内存垃圾收集器:G1(Garbage First)
CMS :并发收集器,基于标记-清除法实现的,
1、初始标记:只是标记一下GC root 直接关联的对象,会发生停顿。
2、并发标记:垃圾回收线程会遍历和GC root关联所有可达对象,和用户线程是直接并发执行的,因此不会停顿用户线程的。
3、重新标记:并发情况下,会发生用户线程执行而导致并发标记发生变动的记录,更新标记记录,会发生停顿并且会比初始标记时间更长。
4、并发清除:垃圾回收线程会清除标记清除GC root不可达的对象,可以和用户线程并发执行。
缺点:碎片化内存比较严重,必须预留一定的老年代内存空间来分配内存,不能等待占用100%的情况下才触发回收,如果预留的空间不足以满足用户分配的空间,将会触发一次并发失败,此时将会冻结用户线程,临时启用Serial Old重新收集老年代线程,这样停顿时间会非常严重
-XX:CMSInitiatingOccu-pancyFraction可以设置预留空间,慎重。需要生产反复设计
G1:
面向服务端应用的垃圾收集器,目标范围,不再区分老年代、新生代、java堆,而是基于Region的堆内存布局,将划分为大小相等的Region,每一个Region区都可以根据需要扮演Eden区、S0区、S1区、老年代区,并且根据不同的区域采用对应的策略去回收处理对象。
Region提供Humongous区域,用来存储超过Region区域容量的一半的大对象,当有超过整个Region区域大小的超大对象,将会以连续Humongous Region来存储。
-XX:G1HeapRegionSize来设置Region容量大小,范围1M~2M,且应为2的N次幂
建立停顿时间模型,后台维护一个优先级列表,可以让用户设置收集停顿时间来回收价值最大的Region。
-XX:MaxGCPauseMillis设置毫秒级
初始标记:仅仅只是标记一下GC Root直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时极短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里对象图,可以和用户线程并发执行,当对象扫描图完成后,会重新处理SATB记录下的并发时引用变动的对象。
最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量SATB记录。
筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划,这
里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。
其他垃圾收集器:
-
Serial
收集器:
单线程收集器,收集垃圾时,必须 stop the world,使用复制算法。 -
ParNew
收集器:
Serial 收集器多线程版本,也需要 stop the world,复制算法。 -
Parallel
Scavenge
收集器:
新生代收集器,复制算法收集器,并发多线程收集器,目标达到一个可控吞吐量。如果虚拟机总共运行 100 分钟,其中垃圾花掉 1 分钟,吞吐量就 99%。 -
Serial
Old
收集器:
Serial 收集器老年代版本,单线程收集器,使用标记、整理算法。 -
Parallel
Old
收集器:
Parallel Scavenge 收集器老年代版本,使用多线程,标记-整理算法。 -
CMS(Concurrent
Mark
Sweep)
收集器:
一种以获得最短回收停顿时间为目标收集器,
标记清除算法,运作过程:初始标记,并发标记,重新标记,并
发清除
,收集结束会产生大量空间碎片。 -
G1
收集器:
标记整理算法实现,
运作流程主要包括以下:初始标记,并发标记,
最终标记,筛选标记
。不会产生空间碎片,可以精确地控制停顿。