JVM相关知识

JDK1.8之后的JVM内存结构

一、JVM内存区域划分

        1.程序计数器(线程私有)

程序计数器(Program Counter Register),也有称作为 PC 寄存器,保存程序当前执行的指令的地址,CPU执行执行指令时,获得这个地址,计数器+1,转移指针得到下一条指令的地址。也就是说用来执行那条指令的。

        2.java栈(线程私有)

       Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,就会随之创建一个对应的栈帧,线程当前执行的方法锁对应的栈帧在java栈的顶部。每个线程都会有一个自己的java栈,线程私有互不干扰

        3.本地方法栈(线程私有)

和java栈的原理相似,本地方法栈是为执行本地方法服务的

        4.堆(线程共享 )

java中只有一个堆,堆是用来存储对象本身和数组的,是垃圾手机器管理的主要区域

        5.方法区(线程共享)

方法去存储了每个类的信息,在类和接口被加载到jvm中时对应的运行时常量池就被创建出来

        6.直接内存(线程共享)

NIO,使用native函数库直接分配堆外内存(物理内存),不占用堆空间

二、类的加载机制

       1.原理      

JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆(并不一定在堆中,HotSpot在方法区中)中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。

        2.生命周期

                加载——验证——准备——解析——初始化——使用——卸载

                其中验证、准备、解析统称为连接

三、类的加载器

        实现类的加载动作。在 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。

        有四种加载器:

启动类加载器,由C++实现,没有父类。

拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null

系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

自定义类加载器,父类加载器肯定为AppClassLoader。

加载体系:

双亲委派双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求交给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,请求最终将到达顶层的启动类加载器。如果顶层的启动类加载器可以完成类加载任务,就成功返回,倘若启动类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

(即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成)那么采用这种模式有啥用呢?

优势:1.可以避免重复加载,如果传递给子类一个已经被父类加载过的类,子类就不需要加载,而是传递给父类,由父类直接返回这个类。
           2.安全因素:防止核心api被篡改 如果传递一个核心api中已有的类给启动类,启动类加载器发现在核心api中有这个类就不会再加载了。

如果定义一个胡编的类,父类加载器加载不了就会传递给自定义加载器,这样做会报错,不可行

 显示加载和隐式加载

显示加载是指jvm加载class文件到内存中,用类加载器进行加载;隐式是通过虚拟机自动加载到内存中。

 四、GC回收机制

堆内存分配区域

 年轻代:是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

老年代:主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

永久代:指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

1.如何确定垃圾?

        引用计数法:如果要使用一个对象,通过引用计数器可以知道他的计数必定不为0,反之为0的对象就是不使用的可以被回收的

        可达性分析:通过 GC roots对象作为起点搜索,如果和对象之间没有可达的路径,则不可达,但是不可达对象有可能之前被用过,所以至少要经过两次标记,直到为0时,才可以被回收。如果是不可达但是仍有计数,则会造成内存泄漏。

2.收集算法

1).标记清除算法

把不在使用的对象进行标记,并清除,剩下的就是需要使用的对象。这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,如果存储大对象会触发GC回收,造成内存浪费以及时间的消耗。

2.).复制算法

为了解决内存碎片化,把内存分为两块,每次使用其中一块,把一块内存中(from)存活的对象复制到另外一块上,然后清除复制之前的那一块内存(from),这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。

3).标记整理算法

将存活的对象进行标记,并整理到一端,之后清除边界之外的对象。这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法适合老生代。

4).分代收集算法

分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以使用标记整理算法

3.回收机制

1、新对象创建申请,先判断伊甸区能否放下对象,能放下就分配内存,不能放下就进行一次小的minorgc

2.这次gc之后把存活下来的对象放到辛存者区中,如果这些存活的对象的gc

次数超过一个阈值,那么就会把他们转到老年代。

3.辛存者区中有s0 from,s1 to两块区域,这两个快区域会发生收集算法,腾出空间。

4.如果存活区还是放不下这个对象,就把对象转移到老年代,如果在老年代中能放下就分配内存,如果放不下就进行一次fullgc,把老年代和新生代中所有的数据进行gc,如果能放下就分配内存,还是放不下就报内存溢出。

注意:fullgc非常慢,所以要避免经常性的进行fullgc。

五、引用类型

 六、垃圾回收器

G1收集器  CMS收集器

七、性能监测jvisualvm

七、一些问题

 

 八、 IO/NIO

1.阻塞IO模型

        在读写的过程中发生阻塞的现象。当用户线程发出 IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。典型的阻塞 IO 模型的例子为:data = socket.read();如果数据没有就绪,就会一直阻塞在 read 方法。

 2.非阻塞IO模型

当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个
error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦内核中的数据准备
好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
所以事实上,在非阻塞 IO 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO
不会交出 CPU,而会一直占用 CPU

3.NIO

NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值