JVM学习|JVM的位置、JVM的体系结构、类加载器、双亲委派机制、沙箱安全机制、本地方法栈、PC寄存器、方法区、堆(OOM内存溢出问题、GC:垃圾回收、复制算法、标记清除算法、标记压缩算法)

JVM的位置

jvm实际上可理解为运行在操作系统之上的软件

JVM的体系结构

首先一个程序会经过编译器生成class字节码文件,然后需要类加载器来将类加载到虚拟机中,运行数据区存在

1.方法区:存放类的基础信息,比如类名,类的属性名。静态变量、常量、常量池

2.有自己的java栈:存放对象的引用的(有一个内存地址,指向堆内存中的具体实例对象)

3.堆:存放对象实例的,包含对象的具体信息

4.本地方法栈:用来存放本地方法的引用

5.程序计数器:供线程使用,用来记录程序执行的位置

其中还包含执行引擎、本地方法接口,本地方法库。

后续提到的垃圾回收算法,他不是针对栈和程序计数器的,这里面不会有内存垃圾,引用用完随即弹出栈,不会留存,垃圾回收算法是针对堆和方法区的,而且99%是针对堆的

类加载器

作用:加载 Class 文件~

1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序加载器

如下图所示,来了一个Car类,类加载器将其加载到jvm中,在方法区给它的信息留存下来,当我们new这个类产生对象实例的时候,会在栈中加入这个对象实例的引用,这个引用指向存放这个实例对象具体信息的堆中,我们的这个对象实例可通过getclass方法获得这个类

类是模板,对象是具体的,就如下图所示,通过Car类创建多个实例对象,对这多个实例对象通过getClass方法获得这个类,可以看到,这三个实例对象的类是一致的

可以看出各个实例对象的哈希码不同,所以不是一个东西,而三个实例对象获取的类的哈希码是一致的。故类是同一个类

我们得到一个类时,也可以调用getClassLoader方法来获取加载该类的类加载器

分别调用该方法以及获取其类加载器的父 类加载器,可以看到有应用程序类加载器,扩展类加载器等

双亲委派机制

双亲委派机制:安全

1.类加载器收到类加载的请求,

2.AppLication将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器

3、启动加载器检査是否能够加载当前这个类, 能加载就结束、使用当前的加载器,否则,抛出异常,通知子加载器进行加载

4.重复步骤3

1.APP-->EXC---B00T(最终执行)

比如,我们自己写一个String类,重写这个toString的方法,然后新建这个类,并调用我们的这个类的toString方法

但是,运行程序后,会报错,显示String类中找不到main方法,可是我们自己刚定义的这个类中明明有main方法,那为什么没有呢?因为我们的JVM中采取了双亲委派机制,就是说,当我们new一个类时,类加载器会逐层的像上级的类加载器查看,看这个类到底是哪个加载器加载的,因为String这个类,在根类加载器中有定义,故此时生成的实例对象实际上是根类加载器加载的那个String类生成的,而非我们自己刚定义的那个,如果最上层的类加载器没有加载过该类,才会再逐层的像下去查看,找到层级相对较高的类加载器,用其中的对应的类加载实例。

这个机制,可以保证我们jvm内部中的一些类的属性不会随意的被外界更改,使得其更加安全

比如, 再定义个Student类,设置相应的方法,然后new这个类,用具体是实例对象调用这个方法,并且用这个实例对象获得类,以及对应的类加载器,可以看到其加载器就是应用程序类加载器,就是我们刚定义这个类的这层,而不是更高层的类加载器,因为更高层的类加载器中没有Student这个类

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

本地方法栈

它的具体做法是Native Method Stack 中登记native方法,在(Execution Engine)执行引擎执行的时候加载Native Libraies。【本地库】

新建一个线程,调用start方法启动线程,会发现该方法的底层是调用了一个private native void starte0();的方法,该方法用native修饰,表示其是一个本地方法,其底层是C语言编写的

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寄存器

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

方法区

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

栈:先进后出、后进先出:桶
队列:先进先出(FIFO:First Input First Output )
喝多了吐就是栈,吃多了拉就是队列
为什么main()先执行,最后结束
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就Over!

栈: 8大基本类型+对象引用+实例的方法
栈运行原理: 栈帧
栈满了:StackOverflowError
栈+堆+方法区:交互关系

三种JVM

Sun公司 HotSpot Java Hotspot(Tm)64-Bit server y (build 25,181-b13. mixed mode)
BEA JRockit
IBM J9 VM
我们学习都是: Hotspot

Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中? 类,方法,常量,变量~,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
新生区(伊甸园区)Young/New
养老区 old
永久区 Perm

GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够!java.lang.0utOfMemoryError: Java heap space
在JDK8以后,永久存储区改了个名字(元空间)

新建一个字符串,无限地给这个字符串对象追加信息,运行起来,发现发生了内存溢出错误OOM

真理:经过研究,99%的对象都是临时对象!
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或类信息~,这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个区域的内存~一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。直到内存满,就会出现OOM;
。jdk1.6 之前 : 永久代,常量池是在方法区;
jdk1.7永久代,但是慢慢的退化了,去永久代,常量池在堆中:
。jdk1.8之后:无永久代,常量池在元空间

这个持久代,逻辑上是存在于堆中,实际上不属于堆的,故也成为非堆

返回虚拟机试图使用的最大内存

返回jvm的初始化总内存

默认情况下: 分配的总内存 是电脑内存的 1/4,而初始化的内存:1/64

可以改变虚拟机参数,对分配堆内存进行更改

可以看到,参数更改后,分配的虚拟机堆内存大小发生变化

OOM内存溢出问题

当发生虚拟机内存溢出问题时00M:1.尝试扩大堆内存看结果2.分析内存,看一下哪个地方出现了问题(专业工具)

将新生代与老年代的堆内存相加,发现正好为虚拟机的分配堆内存,证明永久代不属于虚拟机堆内存,它只是逻辑上存在,物理上不存在。

还运行刚才的字符串追加程序

先修改虚拟机参数,修改分配堆内存的大小,设置小一点,然后打印出GC垃圾回收的日志,然后运行程序

可以看到GC尝试清理了内存,但是最后实在是清理不了了,于是发生了内存溢出错误

在一个项目中,突然出现了OOM故障,那么该如何排除~研究为什么出错
。能够看到代码第几行出错:内存快照分析工具,MAT,Jprofiler
。Dubug,一行行分析代码!
MAT, Jprofiler 作用
。分析Dump内存文件,快速定位内存泄露;
·获得堆中的数据
。获得大的对象~
在idea中安装Jprofiler插件

在官网下载这个工具的客户端,然后创建用户名,公司,以及粘贴一些现成的密钥,打开

在idea中,设置这个客户端的路径

运行这个往列表中无限追加对象的程序

毫无意外,出现内存溢出错误

这次再次运行,运行前,修改虚拟机参数,增加dump文件导出的命令,关于内存溢出错误,然后再次运行程序

显示,这个供Jprofiler分析的dump文件已经生成

打开这个类的实际目录地址

返回其上上上一层,即可看到这个dump文件,直接打开即可

就可以看到是一个数组占用了大量内存

打开大文件分析,可以看到是一个list列表占用了大量的内存

打开Thread Dump,找到main线程,就可以看到是程序中第17行的问题

即可精准定位是这个无限向列表中加对象的操作使得其发生了内存溢出错误

GC:垃圾回收

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

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

GC的算法

引用计数法

对堆中的对象的引用次数做统计,优先清除引用次数少的对象

复制算法

幸存区实际上一个是from,一个是to,这个from和to是一直互相变换的,空的那个是to,伊甸园区经过GC后存活的对象要转移到幸存区的to区,也就是空的那个区,此时,这个有对象的to区变为from区,空的那个from区变为to区,下次再垃圾回收伊甸园区存活的对象就要放到这个to区了,同时,from区的对象也要向这个to区复制过去,此时相当于所有新生代的对象都去到了这个to区,from区变空,再次交换from区和to区,以此重复

。好处:没有内存的碎片~
。坏处:浪费了内存空间~: 多了一半空间永远是空 to区。假设对象100%存活(极端情况)那么每次将对象从from复制到to就是一个很大的问题
复制算法最佳使用场景:对象存活度较低的时候;新生区~

标记清除算法

优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生内存碎片。

标记压缩算法

再优化:

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

思考一个问题:难道没有最优算法吗?
答案:没有,没有最好的算法,只有最合适的算法…>GC:分代收集算法
年轻代:
存活率低
复制算法!
老年代:
区域大:存活率
标记清除(内存碎片不是太多)+标记压缩混合 实现

JMM

1.什么是JMM?
JMM:(Java Memory Model的缩写)
2.它干嘛的?: 官方,其他人的博客,对应的视频!
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(LocalMemory)

解决共享对象可见性这个问题: volilate

JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存。不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁如果对一个变量进行1ock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量”对一个变量进行unlock操作之前,必须把此变量同步回主内存
IMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值