jvm内存模型+类加载机制+垃圾收集

  • Post author:
  • Post category:其他


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




    收集器:

    标记整理算法实现,

    运作流程主要包括以下:初始标记,并发标记,




    最终标记,筛选标记

    。不会产生空间碎片,可以精确地控制停顿。



版权声明:本文为lyyCSDNBLOG原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。