JVM入门

一、JVM探究

  • 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
  • 么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?知道吗?
  • 谈谈JVM中,类加载器你的认识?
    (根加载器rt.jar,扩展加载器ext,application系统加载器)

学习建议从:
1、百度
2、思维导图
在这里插入图片描述

1. JVM的位置

JVM是运行在操作系统上面的,Java程序就是运行在JVM上,虚拟机之外的软件是跟JVM并列的,操作系统运行在硬件体系上
在这里插入图片描述

2. JVM的体系结构

在这里插入图片描述
这么大个区里面垃圾回收一定不会在那个地方存在。
堆里面肯定有垃圾,
栈不会有垃圾,main方法一进来压在最底下没执行一个方法存一个方法的引用,用完就会把这个方法丢出去,如果这个方法存在垃圾把他堵了引用方法执行不完,main方法结束不了程序就死了
在这里插入图片描述
所谓的JVM调优就是在堆里面调
在这里插入图片描述

3. 类加载器

作用:加载class文件
比如:new Student();是在做什么事情?
有一个类叫做Student这个类是抽象的,当我们使用new关键字之后他就变成一个具体的实例了,这个具体的实例引用是在栈里面,实例对象是放在堆里面
类是抽象的模板,对象是具体的
在这里插入图片描述

在这里插入图片描述
1、虚拟机自带加载器
启动类(根)加载器(Bootstrap)由C++编写的
扩展类加载器(Extension)
应用(系统)加载器(System)
2、用户自定义类加载器
java.lang.ClassLoader的子类
用户可以定制类的加载方式

4. 双亲委派机制

在这里插入图片描述

5. 沙箱安全机制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

jdk1.0:本地代码默认信任的、远程代码封存在沙箱里不执行
1.1:增加了安全策略(受信任权限)
1.2:增加了代码签名(差异化代码执行权限控制)
1.6:引入域的概念(也是权限问题)

沙箱的基本组件:
在这里插入图片描述
在这里插入图片描述

沙箱了解一下即可

6. Native

凡是带了native关键字的方法,说明Java的作用范围达不到了,会回去调用底层C语言的库

native 即 JNI,Java Native Interface(上面说JVM结构的本地方法接口)
Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。

JVM结构中的本地方法栈会调用JNI
JNI就会区调用本地方法库

JNI的作用:扩展Java的使用,融合不同的变成语言为Java所用!最初:C C++

Java诞生的时候C C++ 横行,想要立足,必须要有调用C、C++的程序
它在内存区域中专门开辟了一块标记区域:本地方法栈(Native Method Stack),登记Native方法


现在调用其他接口:Socket,webService,http

在最终执行的时候,加载本地方法库中的方法通过JNI

7. PC寄存器 Program Counter register

PC寄存器( PC register ):每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。PC寄存器里保存有当前正在执行的JVM指令的地址。 每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。保存下一条将要执行的指令地址的寄存器是 :PC寄存器。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

8. 方法区

存放所有的①类信息(构造方法、接口定义),②静态变量(static变量),③静态方法,④常量和⑤成员方法,6、运行时的常量池
精简一下就是:static、final、Class信息、运行时常量池
常量池分为:静态常量池(字节码文件常量池)、运行时常量池
关于类的信息可以去看反射那块的类加载过程。

1.又叫静态区,跟堆一样,被所有的线程共享。

2.方法区中存放的都是在整个程序中永远唯一的元素。这也是方法区被所有的线程共享的原因。

—>jdk1.6和jdk1.7方法区可以理解为永久区。
—>jdk1.8已经将方法区取消,替代的是元数据区。

(顺便展开静态变量和常量的区别: 静态变量本质是变量,是整个类所有对象共享的一个变量,其值一旦改变对这个类的所有对象都有影响;常量一旦赋值后不能修改其引用,其中基本数据类型的常量不能修改其值。)

Java里面是没有静态变量这个概念的,不信你自己在某个成员方法里面定义一个static int i = 0;Java里只有静态成员变量。它属于类的属性。至于他放哪里?楼上说的是静态区。我不知道到底有没有这个翻译。但是深入JVM里是翻译为方法区的。虚拟机的体系结构:①Java栈,② 堆,③PC寄存器,④方法区,⑤本地方法栈,⑥运行常量池。而**方法区保存的就是一个类的模板,堆是放类的实例(即对象)**的。栈是一般来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。

方法区的大小由-XX:PermSize和-XX:MaxPermSize来调节,类太多有可能撑爆永久代。静态变量或常量也有可能撑爆方法区。

一般引用变量找值的话,如果没赋值会去方法区的常量池里面找或者是class模板等里面找,赋值了才会在堆上找

9. 栈

是一种数据结构
程序 = 数据结构 + 算法 持续学习~
程序 = 框架 + 业务 淘汰 吃饭

栈:先进后出、后进先出:桶
队列:先进先出(FIFO:First Input First Output)

程序一执行就是先把main方法丢到栈里面,然后再丢test方法,test方法会先执行完出去,然后main方法执行完出去程序结束
在这里插入图片描述
栈溢出原因:
无限压栈,栈就溢出了
在这里插入图片描述

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

栈里面存放的东西:8大基本类型+对象引用+实例的方法(本地栈)

栈运行原理:栈帧
每执行一个方法都会产生一个栈帧
程序正在执行的方法,一定在栈的顶部

栈分为栈顶和栈底
方法内部:方法索引(index),输入输出参数,本地变量(局部变量),Class File:引用,父帧,子帧
(子帧会调用下一个方法,父帧会调用上一个方法)

栈满了:StackOverflowError
栈 + 堆 + 方法区:交互关系
在这里插入图片描述

10. 三种JVM (我们目前学HotSpot)

在这里插入图片描述

  • Sun公司 HotSpot
  • BEA JRockit
  • IBM J9 VM

11. 堆(Heap)

一个JVM只有一个堆内存,堆内存大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?
类具体实例,方法,常量,变量

栈中一般是一些引用,运行时常量池都在方法区里面

比如对象的引用用个一两次很久都用不到,这时候对象的实例属于垃圾就要被垃圾回收
因为有垃圾,所以这个时候堆内存还要细分三个区

三个区域:

  • 新生区(伊甸园) Young/New
  • 养老区 old
  • 永久区 Perm

幸存区的意思是对象在经历过一次或者n次GC后还存活下来之后就会进入幸存区(两个幸存区是动态的,可能这次放在0区下次就会交换位置放在1区),假设我们把这个上限定位20次,经历超过20次GC之后就会进入养老区(大多数情况下养老区的对象不会被干掉),养老区空间不足的时候会执行重GC,当执行重GC后养老区仍然空间不足就会OOM

垃圾回收分两种:
轻量级垃圾回收简称轻GC
重量级垃圾回收简称重GC(Full GC)

轻GC针对新生区来的,重GC是养老区都快满了证明新生区和养老区这块要爆了就会进行重GC

永久存储区:基本类型的数据、JVM内置的一些东西
在这里插入图片描述
所以垃圾回收主要在新生区和养老区
幸存区主要是新生区和养老区的一个过渡,活下来的对象就进入养老区活不下来的就被GC

假设内存满了,OOM,堆内存不够!java.lang.OutOfMemoryError
示例:
解析:OOM原因就是一开始一直new String新生对象在新生区,然后GC回收,没回收掉的进幸存区然后再一步一步的到养老区直到养老区也满了内存就爆了
在这里插入图片描述
上面那张图是在JDK8之前的
在JDK8后,永久存储区改了个名字(元空间)

12、新生区、老年区

  • 对象:诞生(伊甸园区)和成长(幸存区)的地方,甚至是死亡(被GC两个区都有可能)。
  • 伊甸园区,所有对象都是在伊甸园区new出来的
  • 幸存者区(0,1)

假设伊甸园区只能存x个对象,new了x个对象之后会触发一次轻GC
轻GC有以下几种情况
有的对象可能还存在引用然后他就活下来了,有的引用清掉他就没用了那他就死了
活下来的进入幸存区,幸存区经过设定的上限,过了上限后进入老年区
养老区空间不足的时候会执行重GC,当执行重GC后养老区仍然空间不足就会OOM

13. 永久区(持久代)

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

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

出现OOM跟这个区没什么关系,这个区一般放些程序启动第三方的jar包

什么情况在永久区就崩了?
一个启动类,加载了大量第三方jar包。
Tomcat部署了太多的应用,大量动态生成的反射类。
不断被加载,直到内存满就会生成OOM

方法区就是永久区(1.8元空间)
可以这么理解,Java堆是一个数据区,新生代、老年代组成了一个堆,永久区是另一个特殊的堆

之前我们也有了解到方法区存的东西,类的信息、static、final、常量池
在伊甸园区new出来的对象可以去方法区拿类的信息,所以这个区是共享的
在这里插入图片描述

逻辑上存在,物理上不存在

示例:求最大内存大小和初始化总内存大小
在这里插入图片描述
上面可以看到有一定的内存丢失现象,正常
在这里插入图片描述
斜体样式可以看出:默认情况下,分配的最大内存是计算机总内存的1/4,然后初始化内存大概是1/64
示例:然后可以去手动调参设置内存大小
在这里插入图片描述
-XX:PrintGCDetails打印查看GC信息
在这里插入图片描述
可见刚刚的设置生效,最大内存和初始化内存都是1G
在这里插入图片描述

面试遇到过OOM,堆内存满了
采取措施解决
1、先尝试着扩大堆内存空间看结果(假设扩大后走原样的代码还是报这个错的话就肯定是我代码有问题,这时候可能有垃圾代码或者无线死循环代码)
2、分析内存,看一下那个地方出现了问题(需要专业工具)

示例:OOM案例观察分析
在这里插入图片描述
参数调到特别小,方便查看垃圾回收,然后打印GC信息
分析:一开始前面4次GC,四次之后发现伊甸园区和幸存区都满了就执行了一个重GC,然后又可以在年轻代执行GC,这样反复循环直到最后一次重GC都清不了了,年轻代、老年代、元空间都满了,这时候报错OOM

14. 堆内存调优

在这里插入图片描述

使用JPofiler工具分析OOM原因
在一个项目中,突然出现了OOM故障,那么该如何排除?(扩大内存后),研究为什么出错?
方法:

  • 能够看到代码第几行出错:内存快照分析工具,MAT(Eclipse),Jprofiler(IDEA)
  • Dubug,一行行分析代码

MAT,Jprofiler(也有用jmeter的)作用:

  • 分析Dump内存文件,快速定位内存泄漏;
  • 获得堆中的数据
  • 获得大的对象~

使用教程:
1、idea下载
在这里插入图片描述
2、百度官网下载Jprofiler客户端
在这里插入图片描述
路径自定义,要求没有空格没有中文
在这里插入图片描述
在这里插入图片描述
这得配置一个地址插件,就是刚刚下载好了的
在这里插入图片描述
示例:
知识点:Dump内存文件,跟调优有关的
在这里插入图片描述
-XX:+HeapDumpOnOutOfMemoryError
在这里插入图片描述
下载好了之后,打开当前项目的文件目录
在这里插入图片描述
点开之后
第一个classes页面可以看出,char[]对象占用的内存最多
在这里插入图片描述
一般的话建议用biggest Objects
可以很明显的看到java.util.Arraylist占了最多,就知道代码里面很明显是arraylist出问题了,然后点开树状列表可以看到里面放了很多个对象而引起内存不足
在这里插入图片描述
可以看一下线程Dump,点开main线程可以看到是哪一行出错了
在这里插入图片描述
项目上线看不到具体报错行,只能dump后进行分析。

15. GC

在这里插入图片描述
JVM在进行GC时,

  • 新生代
  • 幸存区(from,to)
  • 老年区
    并不是对这三个区域统一回收,大部分时候,回收都是新生代
    GC两种类:轻GC(普通的GC)针对新生代和幸存区,重GC(全局GC)

题目:

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

成本1:引用计数法要给每个对象分配一个计数器(计算每个对象用了多少次)
成本2:每个对象赋值使用的时候,计数器本身也会有消耗
在这里插入图片描述
现在JVM一般不会用这个算法,并不高效,Java里面的对象太多了

复制

谁空谁是to区
每次GC都会将Eden活的对象移到幸存区重:一旦Eden区被GC后,就会是空的!(不是GC了就是到幸存区了,所以肯定是空)

1、假设伊甸园区过来个对象,现在to和from都有一个对象,那么就会复制其中一个区里面的对象进另一个区,然后不管是幸存区to还是from,反正空的那个变成to区
2、当幸存区内部GC,所有对象都在from区,然后from区复制对象进to区,原来的from因为没有对象了就变成了to区他们就是这样动态交换的
只要复制过去后这两个区他们的身份就立马变换了
自己理解:一般不管是eden到to或者是from到to,反正to里面有对象就变from了下次再复制的时候又是from变成to

复制算法就是为了保证to区干净,因为复制过去后to区就变成了空的,并且to区和from区在相互复制并且相互移动位置但是变空的那个就是to区

幸存区进入老年区阈值设置,一般是默认15次GC
(可以百度JVM调优参数)
在这里插入图片描述
下面该图:
绿色的表示新的对象,一开始from区一个对象,eden区一个对象,新生区GC的时候from区的对象和eden区的对象到to区,这时候to区变成from区并且拥有两个对象,如果之前那个对象在幸存区达到阈值那么则进入老年区
在这里插入图片描述

  • 好处:没有内存的碎片(对象都存在一个幸存区里面)
  • 坏处:浪费了内存空间,多了一半空间永远是空to,(浪费了一个幸存区的空间)
    极端情况:假设对象100%存活(则复制量大成本高)

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

标记清除算法

在这里插入图片描述

GC时对活着对象标记一下,清除时对没有标记的对象进行清除

  • 优点:不需要额外的空间!
  • 缺点:两次扫描,严重浪费时间;会产生内存碎片(hash不好定位)
标记压缩

优化上面的标记清除的
两次扫描不好解决,内存碎片可以解决让对象往一端移动过去就行了方便定位

在这里插入图片描述
没有内存碎片所以比上面的标记清除算法高效一点
但是多了一个移动成本

标记清除压缩
其实再优化就是先标记清除5次再压缩1次,这样清除5次后内存碎片就变多了,再压缩成本就小很多。

GC算法总结

内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
(复制算法1次,标记清除算法2次,标记压缩算法3次)
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
从效率上来说复制算法最好但是浪费了内存

思考一个问题:难道没有最优的算法吗?
答案:没有,没有最好的算法,只有最合适的算法 -->GC:也被称之为分带收集算法

分代收集算法:
年轻代:

  • 存活率低
  • 复制算法!

老年代:

  • 区域大:存活率高
  • 标记清除(内存碎片不是太多)+标记压缩混合 实现
    JVM调优就是调这些东西,比如说多少次清除完才去压缩,年轻代进入老年代的阈值等

学到这里大概入门了,深究的话可以百度学习还有看思维导图,面试题
书籍推荐:《深入理解JVM》

JMM:Java Memory Model

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

主内存:Java就一个
线程工作内存:每个线程都有一个自己的工作区域,是从主内存拷贝的

示例:拷贝过去修改其他线程的工作内存是不可见的
在这里插入图片描述
Java内存模型带来的问题:
1、可见性问题
解决共享对象可见性问题:volilate
使用volilate解决可见性问题,使用synchronized解决同步问题

  1. 它该如何学习?
    JMM:抽象的概念,理论

8种操作(指令):

这边write和store写反了,先store再write
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量 才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便 随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机 遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变 量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中, 以便后续的write使用
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内 存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须 write
  • 不允许线程丢弃他近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量 实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解 锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前, 必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题: 程序不知道主内存的值已经被修改过了,当A线程修改了工作内存的变量写入到主内存时,线程B还不知道该值被改变过,不可见。
在这里插入图片描述

别人问你JMM,就是问你跟并发相关的问题,说白了在问主存和工作内存数据是否一致,解决方案上面有说volatile和synchroized

主要是要学习volatile

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无极的移动代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值