JVM体系结构,类加载器,Native,JVM分区,OOM,GC算法等

1、JVM的位置

在这里插入图片描述

2、JVM体系结构

在这里插入图片描述

3、类加载器及双亲委派机制

类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。Java特意把这一步抽出来用类加载器来实现。
类加载器:
作用:加载Class文件
在这里插入图片描述
1、启动类(根)加载器,属于虚拟机自身的一部分,用C++实现的,主要负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径;等于是所有类加载器的爸爸。
2、扩展类加载器是Java实现的,独立于虚拟机,负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径;
3、应用程序类加载器,负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么此加载器就为默认加载器。
4、虚拟机自带的加载器
在这里插入图片描述

双亲委派机制:安全
原理
1、类加载器收到类加载的请求
2、将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器(BootstrapClassLoader)
3、启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
4、重复步骤 3

举个例子:
大家所熟知的Object类,直接告诉大家,Object默认情况下是启动类加载器进行加载的。假设我也自定义一个Object,并且制定加载器为自定义加载器。现在你会发现自定义的Object可以正常编译,但是永远无法被加载运行。
这是因为申请自定义Object加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。

4、沙箱安全机制:了解

5、Native、方法区

凡是带了native关键字的方法,说明Java作用范围达不到了,会去调用底层C语言的库!
会进入本地方法栈
调用本地方法接口 JNI
JNI的作用:扩展Java的使用,融合其他不同语言为Java所用。最初:C、C++
Java诞生的时候C、C++横行,要想立足,必须要有调用C、C++的程序
它在内存中专门开辟了一块区域,Native Method Stack ,登记Native方法
在最终执行的时候加载本地方法库中的方法通过JNI

比如Java程序驱动打印机等,调用其他接口,如Socket,WebService,http
在这里插入图片描述

PC寄存器
程序计数器:Programe Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也就是将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计

方法区
Method Area 方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息也都保存在此区域,此区域属于共享区间
静态变量,常量,类信息(构造方法,接口定义),运行时的常量池,存在方法区中,但是实例变量存在堆内存中,和方法区无关
static 、final 、class 、运行时常量池 的东西存在方法区中,就存这么点东西,其他东西跟它无关

6、深入理解一下栈

栈:数据结构
程序 = 数据结构 + 算法

栈: 先进后出、后进先出:桶
队列: 先进先出(FIFO:First In First Out)

喝多了吐就是栈,吃多了拉就是队列

为什么main()先执行,最后结束

栈: 栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over!

栈运行原理:栈帧
在这里插入图片描述
程序正在执行的方法引用,一定在栈的顶部,栈中的方法是方法区的引用

栈满了:StackOverflowError

栈 + 堆 + 方法区:交互关系

栈中存的是什么?怎么存?
中存的是基本数据类型和堆中对象的引用,在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:)为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。对象本身不存放在栈中,而是存放在堆(new出来的对象)或者常量池中(字符串常量对象存放的常量池中),局部变量【注意:(方法中的局部变量使用final修饰后,放在堆中,而不是栈中)】

:存放使用new创建的对象,全局变量

内存中对象实例化的过程
在这里插入图片描述
Pet.java

package com.fang;
public class Pet {
    String name;
    int age;
    
    //无参构造
    
    public void shout(){
        System.out.println("叫了一声");
    }
}

Application.java

package com.fang;
public class Application {
    public static void main(String[] args) {
        Pet dog = new Pet();
        dog.name = "旺财";
        dog.age = 3;
        dog.shout();
        System.out.println(dog.name);
        System.out.println(dog.age);
    }
}

7、走进HotSpot和堆

三种JVM
Sun公司的 HotSpot
BEA JRockit
IBM J9VM

我们学习都是: HotSpot

Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?类,方法,常量,变量,保存我们所有引用类型的真实对象;

堆内存中还要细分为三个区域:
新生区(伊甸园区) Young/New
养老区 old
永久区(元空间) Perm
在这里插入图片描述

8、新生区、永久区、堆内存调优

新生区
类:诞生和成长的地方,甚至死亡;
伊甸园,所有的对象都是在伊甸园区new出来的
幸存者区(0区,1区)from ... to ...
老年区

在这里插入图片描述
真理:经过研究,99%的对象都是临时对象

永久区

这个区域常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟机就会释放这个区域的内存

一个启动类,加载了大量的第三方jar包,Tomcat步数了太多的应用,大量动态生成的反射类。不断的被加载,直到内存,满,就会出现OOM;

jdk1.6之前:永久代,常量池是在方法区;
jdk1.7		:永久代,但是慢慢的退化了,叫做**去永久代**,常量池在堆中
jdk1.8之后:无永久代,常量池在元空间

在这里插入图片描述
在这里插入图片描述
内存调优
// -Xms 设置初始化内存分配大小 1 / 64
// -Xmx 设置最大分配内存,默认 1 / 4
// -XX:+PrintGCDetails //打印垃圾回收信息
// -XX:+HeapDumpOnOutOfMemory //OOM Dump
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后运行,我们可以算一笔账:
305664K(新生代) + 699392K(老年代) = 1005056K = 981.5MB

所以说明了,永久代逻辑上存在,物理上不存在,可以认为堆内存就是指的新生代和老年代的总大小,GC垃圾回收也是在新生代(伊甸园区)和老年区进行的,永久代和GC没关系

再来看一下OOM
在这里插入图片描述
在这里插入图片描述
这里运行结果可以看出来垃圾回收机制,前几次只是在新生代的轻量级的GC,后面第四次的时候发现伊甸园区和幸存区都满了,有了重量级的Full GC,后面又一次轻GC,又满了,又进行重GC,重复,直到老年区也已经满了,然后就抛出OOM 的异常(OutOfMemoryError),堆空间满了

9、使用JProfiler工具分析OOM的原因

首先安装和配置Jprofiler,可以去参考视频使用JProfiler工具分析OOM的原因
在一个项目中,突然出现了OOM故障,该如何排除?研究为什么出错
能够看到代码第几行出错:内存快照分析工具:MAT、Jprofiler
Debug,一行行分析代码!
MAT、Jprofiler作用:
分析Dump内存文件,快速定位内存泄漏;
获得堆中的数据
获得大的对象

内存调优
// -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
内存调优之后,可以Dump下来一些内存快照文件,如下显示:
在这里插入图片描述
从项目目录找到它,然后打开,用Jprofiler打开它
在这里插入图片描述
在Jprofiler里面可以看到一些信息,明显这里ArrayList出问题了
在这里插入图片描述
再看线程Dump,看main线程哪里出了问题
在这里插入图片描述

10、GC介绍之引用计数法

在这里插入图片描述
JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代
新生代
幸存区(from , to)
老年区
GC两种类型:轻GC(普通的GC),重GC(全局GC)

GC题目:
JVM的内存模型和分区,详细到每个区放什么?
堆里面的分区有哪些?Eden,from,to,老年区,说说他们的特点!
GC的算法有哪些?标记清除法、标记压缩、复制算法、引用计数器,怎么用的?
轻GC和重GC分别在什么时候发生?

引用计数法:通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收
在这里插入图片描述

11、GC之复制算法:

	**复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。**
     当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来**GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。**

在这里插入图片描述
在这里插入图片描述
好处:没有内存碎片,弥补了标记/清除算法中,内存布局混乱的缺点
坏处:浪费了内存空间:多了一半空间永远是空的(幸存区to区),假设对象100%存活(极端情况),那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。

复制算法最佳使用场景:对象存货度较低的时候,新生区。

12、GC回收算法之标记清除压缩算法

标记清除算法:
在这里插入图片描述
标记清除算法的优缺点:
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生内存碎片。

标记压缩:再优化
在这里插入图片描述

标记清除压缩:先标记清除几次(产生了内存碎片了),再标记压缩(整理产生的内存碎片)

13、GC算法总结

内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法

思考:有没有最优的垃圾回收算法?
答案:没有,没有最好的算法,只有最合适的算法---->GC:分代搜集算法

年轻代:存活率低,复制算法
老年代:区域大:存活率高,标记清除(内存碎片不是太多) + 标记压缩混合实现

既然这三种算法都各有缺陷,高人们自然不会容许这种情况发生。因此,高人们提出可以根据对象的不同特性,使用不同的算法处理,类似于萝卜白菜各有所爱的原理。于是奇迹发生了,高人们终于找到了GC算法中的神级算法-----分代搜集算法

以上JVM学习的Line

学习《深入理解Java虚拟机》 周志明,掌握学习JVM的方法

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值