JVM详解

JVM

JVM的内存区域

线程私有区域:程序计数器、虚拟机栈、本地方法区

线程共享区域:方法区、堆

直接内存

JDK的NIO模板提供基于channel与buffer的IO操作方式是基于堆外内存的。



JVM运行时内存

JVM运行时内存又叫JVM堆。其中新生代默认占1/3,老年代2/3,永久代占非常少的堆内存空间。

新生代由分为eden区(8/10),SurvivorFrom(1/10),Survivor(1/10)。

老年代主要存在长生命周期的对象和大对象。老年代GC叫MajorGC。在进行MajorGC之前,JVM会进行一次MinorGC,如果GC后仍然出现老年代且老年代空间不足或无法找到足够大的连续内存分配给新的大对象,会触发MajorGC。

MajorGC采用标记清楚算法。因为要扫描老年代所有对象,MajorGC耗时较长,且容易产生内存碎片。如果老年代没有内存空间可以分配时,会跑出Out Of Memory异常。

永久代主要存放Class和Meta的信息。Class在被类加载时放入永久代。GC不会在程序运行期间对永久代进行清理,这也导致了永久代的内存会随着加载的class文件的增加而增加。在加载的Class文件过多时,会抛出OOM。比如Tomcat引用JAR文件过多导致JVM内存不足而无法启动。

JAVA8中,永久代已经被元数据区(也叫作元空间)取代。元数据区和永久代类似,二者的最大区别是:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。



常用的垃圾回收算法

1、标记清除算法

标记和清除两个阶段。清除后没有重新整理可用空间,如果回收的小对象过多,则会引起内存碎片化的问题。

2、复制算法

复制算法是为了解决内存碎片化问题而设计的。内存分为区域1、2、新生对象都在区域1内,区域1满后对区域1进行一次标记,并将标记后仍然存活的对象复制到区域2,再将区域1清理。

缺点:1、内存浪费 2、长时间存活的对象频繁在区域1、2之间来回复制,影响系统运行效率。

3、标记整理算法

标记整理算法整合了标记清楚、复制算法的优点,其标记阶段和标记清楚算法相同,在标记完成后将存活的对象移到内存的另一端,然后清楚现在这段的对象并释放内存。

4、分代收集算法

前三个算法都无法对所有类型的对象进行垃圾回收。因此,针对不同的对象类型,JVM采用了不同的垃圾回收算法。

JVM新生代对象数量多但是声明周期短,每次存活的对象少,采用复制算法。JVM将新生代进步分为Eden,Suvivor区。Survivor区又分为SurvivorFrom、SurvivorTo。JVM在运行过程中主要使用Eden和SurvivorFrom,进行垃圾回收是将存活对象复制到SurvivorTo,然后清理Eden和SruvivorFrom。

JVM老年代主要存放声明周期较长的对象和大对象,因而每次只有少量非存活的对象被回收,因此采用标记清楚算法。

在JVM中还有一个区域---方法区的永久代。永久代用来存储class类、常量、方法描述等。在永久代主要回收废弃的常量和无用的类。

在新生代的Eden和SurvivorFrom的内存空间不足时会触发一个GC,该过程被称为MinorGC。如果此时SurvivorTo区无法找到连续的内存空间存储某个对象,则将这个对象直接存储到老年代。若Survivor区的对象经过一次GC后仍然存活,则将其年龄加1。默认情况下,对象在年龄达到15时,将被移到老年代。

5、分区收集算法

分区收集算法将整个堆空间划分为连续的大小不同的小区域。在每个小区域单独进行内存使用和垃圾回收。它可以根据系统可以接受的停顿时间,每次都快速回收若干个小区域中的内存,以缩短回收时系统停顿的时间,最后以多次并行累加的方式逐步完成整个内存区域的垃圾回收。




垃圾收集器

新生代

Serial 单线程复制算法

ParNew 多线程复制算法

Parallel Scavenge 多线程复制算法

老年代

CMS 多线程标记清除算法

Serial Old 单线程标记整理算法

Parallel Old 多线程标记整理算法

G1 多线程标记整理算法

G1

JAVA G1学习


Java网络模型编程

阻塞I/O模型

用户线程发出I/O请求,内核检查数据是否就绪,此时用户线程一致阻塞等待内存数据就绪;在内存数据就绪后,内核将数据复制到用户线程中,并返回I/0执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。

data = socket.read()

非阻塞I/O模型

非阻塞I/O模型指用户线程在发起一个I/O操作后,无须阻塞便可以马上得到内核返回的一个结果。如果内核返回的结果为false,则表示内核数据还没准备好,需要稍后再发起I/O操作。准备好了,用户再请求就会正常返回。

在该模型中,用户线程需要不断询问内核数据是否就绪。

while(true){

    data = socket.read();

    if(data = true){

        break;

    }}

多路复用I/O模型

Java NIO就是基于多路复用I/O模型实现的。在该模型中会有一个被称为selector的线程不断轮询多个socket的状态,只有在socket有读写事件时,才会用用户线程进行I/O操作。

对于该模型来说,在事件响应体(消息体)很大时,selector线程就会成为性能瓶颈,导致后续的事件迟迟得不到处理。在实际应用中,一般不建议做复杂逻辑运算,只做数据的接收和转发,将具体的业务操作转发给后面的业务线程处理。

信号驱动I/O模型

用户线程发起一个I/O请求操作后,系统会为该请求对应的socket注册一个信号函数,然后用户线程就可以继续执行其他业务逻辑;在内核数据就绪时,系统会发送一个信号到用户线程,用户线程在接收到该信号后,就会在信号函数中调用对应的I/O读写操作完成实际的I/O请求操作。

异步I/O模型

在异步I/O模型中,用户线程会发起一个Asynchronous read操作到内核,内后收到后立刻返回一个状态,来说明请求是否成功发起。在此过程中,用户线程不会阻塞。内核等数据准备完成并将数据复制到用户线程,并发送一个信号到用户线程,通知用户线程Asynchronous读操作已完成。用户线程不需要关心整个I/O操作是如何进行的,在接收到内核返回的成功或失败信号时,说明I/O操作已经完成,直接使用数据即可。

它和信号驱动I/O的区别:

异步I/O模型中,I/O操作的两个阶段(请求发起,数据读取)都是在内核中完成的,最后发送一个信号通知用户线程。用户线程直接使用数据,不需要再次调用I/O函数进行具体的读写。因此用户线程非阻塞。

信号驱动模型中,用户收到信号后,还需要调用I/O函数进行实际的I/O读写操作。



JVM类加载机制

待填坑

你可能感兴趣的:(JVM详解)