栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

JVM总结

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM总结

目录

一,Java虚拟机 

1.概念

2.作用

3.运行流程

二,运行时数据区域

理解线程私有

1.Java虚拟机栈(线程私有)

作用

栈帧的组成

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

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

作用

4.堆区(线程共享)

作用

5.方法区(线程共享)

作用

三,内存布局的异常问题

1.内存溢出

概念

原因/什么情况下会导致

结果

解决方法

2.内存泄漏

概念

原因/什么情况下会导致

结果

解决方法

3.栈溢出

概念

原因

解决方法

四,JVM类加载

1.类加载时机

2.类加载过程

3.双亲委派机制

概念

作用

类型 

优缺点 

破坏双亲委派机制 

五,垃圾回收

1.死亡对象的算法

1)引用计数法

2)可达性分析

2.垃圾回收算法

1)标记-清除算法

2)复制算法

3)标记-整理算法

4)分代算法

3.垃圾收集器

1)ParNew + Parallel Scavenge 新生代收集器,使用复制算法

2)Parallel Old 老年代收集器:标记整理算法

3)CMS 老年代收集器

六,JMM(内存模型)

内存模型的作用

1.主内存与工作内存

2.内存间的交互操作

操作

happen —before原则


一,Java虚拟机 

1.概念

从硬件层面理解

  • 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的
  • Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统
  • JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行

从进程的角度理解

  • 启动一个Java进程
  • 会创建一个Java虚拟机
  • 并加载class字节码文件
  • 运行时翻译机器码,让cpu执行

2.作用
  • Java中的所有类,必须被装载到JVM中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中
  • JVM就是我们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行(也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行)

3.运行流程

二,运行时数据区域

理解线程私有

由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存

1.Java虚拟机栈(线程私有)

作用
  • Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的内存模型
  • 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 常说的堆内存、栈内存中,栈内存指的就是虚拟机栈

栈帧的组成

  1. 局部变量表: 存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数和局部变量
  2. 操作数栈:每个方法会生成一个先进后出的操作栈
  3. 动态链接:指向运行时常量池的方法引用
  4. 方法返回地址:PC 寄存器的地址

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

本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的

3.程序计数器(线程私有) 作用

用来记录当前线程执行的行号的

  • 程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
  • 如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
  • 如果正在执行的是一个Native方法,这个计数器值为空。
  • 程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域!

4.堆区(线程共享) 作用

程序创建的所有对象都在堆中保存

  • 堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。新生代还有 3 个区域:一个 Endn + 两个 Survivor(S0/S1)

5.方法区(线程共享) 作用

用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的

  • jdk1.7时,方法区是在Java进程的内存中
  • jdk1.8时,是叫元空间,属于本地内存(不在Java进程中)

三,内存布局的异常问题

1.内存溢出

概念

某个运行时数据区域,如果创建的数据,内存不足,就会造成OOM(内存溢出)

原因/什么情况下会导致
  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
  2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收
  3. 代码中存在死循环或循环产生过多重复的对象实体
  4. 使用的第三方软件中的BUG
  5. 启动参数内存值设定的过小

结果

会导致程序异常

解决方法
  • 优化空间利用率
  • 加大堆的内存

2.内存泄漏 概念

没有用的数据,一直占用内存,无法被gc

原因/什么情况下会导致
  1. io资源未关闭,多个用户都未关闭时,就会造成内存泄漏
  2. 单例模式造成的内存泄露
结果
  • 内存泄漏太多,就会造成内存溢出
  • 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏
  • 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的
  • 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次
  • 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏
解决方法
  1. 从程序本身入手,定时清理不使用的数据
  2. 使用软引用或是弱引用
  3. 加大内存
  4. 定时重启程序

3.栈溢出 概念

栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出

原因

对于一台服务器而言,每一个用户请求,都会产生一个线程来处理这个请求,每一个线程对应着一个栈,栈会分配内存,此时如果请求过多,这时候内存不够了,就会发生栈内存溢出

解决方法
  • 用栈把递归转换成非递归
  • 使用静态对象替代非静态局部对象
  • 增大堆栈大小值

四,JVM类加载

1.类加载时机

1)Java 类名 :Java程序的的入口类,需要先执行类加载,在执行main()

2)运行时,执行静态方法调用,静态变量操作等

3)new 对象的时候

4)通过反射创建一个类对象,然后就可以再通过反射,生成实例对象,或调用静态方法

2.类加载过程

加载:加载class字节码(二进制数据)到方法区,在堆中,生成一个Class类对象

验证:验证class字节码数据,是否安全,以及是否符合Java虚拟机规范

准备:静态变量设置为初始值(对象初始值就是null,基础数据类型,就是对应的初始值)常量               (final修饰的)会设置为真实的值

解析:将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程

初始化:静态变量真正的初始化赋值,静态代码块初始化

3.双亲委派机制 概念

不直接执行当前类加载器,而是先要查找当前类加载器的父类加载器,父类加载器也是类似的,先找父类,直到找到最顶层的类加载器,开始执行类加载,(找到class类就进行,找不到就交给下一级类加载器)

作用
  • 除了顶层的启动类加载器,其他的类加载器在加载之前,都会委派给它的父加载器进行加载,一层层向上传递,直到所有父类加载器都无法加载,自己才会加载该类。
  • 双亲委派模型,更好地解决了各个类加载器协作时基础类的一致性问题,避免类的重复加载;防止核心API库被随意篡改

类型 
  • 启动类加载器(Bootstrp ClassLoader),加载 /lib/rt.jar、-Xbootclasspath

  • 扩展类加载器(Extension ClassLoader)sun.misc.Launcher$ExtClassLoader,加载 /lib/ext、java.ext.dirs

  • 应用程序类加载器(Application ClassLoader,sun.misc.Launcher$AppClassLoader),加载 CLASSPTH、-classpath、-cp、Manifest

  • 自定义类加载器

优缺点 

好处:

确保安全,如Object,String类等,都是使用jdk提供的类,而不是使用自己定义的java.lang.Object

缺点:

保证了安全,但扩展性就没那么好了(灵活性降低)

破坏双亲委派机制 
  • JNDI 通过引入线程上下文类加载器,可以在 Thread.setContextClassLoader 方法设置,默认是应用程序类加载器,来加载 SPI 的代码。有了线程上下文类加载器,就可以完成父类加载器请求子类加载器完成类加载的行为。打破的原因,是为了 JNDI 服务的类加载器是启动器类加载,为了完成高级类加载器请求子类加载器(即上文中的线程上下文加载器)加载类。

  • Tomcat,应用的类加载器优先自行加载应用目录下的 class,并不是先委派给父加载器,加载不了才委派给父加载器。打破的目的是为了完成应用间的类隔离。

  • OSGi,实现模块化热部署,为每个模块都自定义了类加载器,需要更换模块时,模块与类加载器一起更换。其类加载的过程中,有平级的类加载器加载行为。打破的原因是为了实现模块热替换。

五,垃圾回收

1.死亡对象的算法

1)引用计数法

概念

每当堆中多一个引用时,计数器+1,少一个,计数器-1

缺陷

无法解决循环引用问题

2)可达性分析

核心思想

  • 通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的
  • 对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象

2.垃圾回收算法

1)标记-清除算法

概念

分为“标记”和“清除”两个阶段

标记:标记需要gc的对象

清楚:使用gc,将标记好的对象回收

缺陷

(1)效率低

(2)内存碎片:存放对象时,即使可用空间足够,但连续空间不足,也会触发另一次的gc

2)复制算法

概念

把用的内存划分为两块一样大小的空间,每次只使用其中一块,把存活的对象复制到另一块空间,然后把之前使用的空间清空

优点:清空半区的效率高,不会出现内存碎片

缺点:内存利用率低(不会超过50%)

3)标记-整理算法

类似于标记清楚算法,采取的方案是将存活的对象,移动到连续的空间,再清空剩余的空间

优点:不会出现内存碎片的问题

4)分代算法

在堆中,根据对象创建及回收的特性,分为了两块区域:

(1)新生代

Eden(E区),2个Survivor(S区)

原因:对象朝生夕死,很快的创建的,由很快的变为不可用的垃圾

采取的算法:复制算法

过程:如果出现S区不够的情况,老年代担保,可将数据存放到老年代

  1. 当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
  2. 当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
  3. 部分对象会在From和To区域中来回复制,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代

(2)老年代

对象可能长期存活

采取的算法:标记清除算法,标记整理算法

(3)新生代GC vs 老年代GC

3.垃圾收集器

从Java应用程序来说:大略上,可以分为2种类型

(1)用户体验优先(实时性要求比较高):用户要使用的程序,系统每次STW的时间最少,总的           STW的时间可能变多   ——ParNew(新生代)+CMS(老年代) G1

(2)吞吐量优先(批处理任务):用户不使用的,之执行任务的,总的STW时间最少

          ——Parallel Scavenge (新生代)+ Parallel Old (老年代)

1)ParNew + Parallel Scavenge 新生代收集器,使用复制算法

2)Parallel Old 老年代收集器:标记整理算法

3)CMS 老年代收集器

特性:用户体验优先(并发收集,低停顿)

采取”标记清除“算法

缺陷

  •  cpu比较敏感:gc线程需要cpu资源,相对就需要更多的cpu资源
  • 无法处理浮动垃圾
  • 内存碎片问题

4)G1 全堆收集器

用户体验优先

内存划分:把堆划分为相等的很多个区域,每个区域根据需要设置为E,S,T(老年代)

算法:整体上看基于”标记整理算法“,局部上看基于”复制算法“ 

六,JMM(内存模型)

内存模型的作用

不同硬件及操作系统,对内存的访问操作也不同,Java采取统一的Java内存模型来屏蔽以上的差异

1.主内存与工作内存

主内存:线程共享的,Java进程的内存

工作内存:线程私有的,cpu执行线程指令时,使用寄存器来保存上下文

2.内存间的交互操作

操作
  1. lock(锁定) : 作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  2. unlock(解锁) : 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  3. read(读取) : 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  4. load(载入) : 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  5. use(使用) : 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎。
  6. assign(赋值) : 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量。
  7. store(存储) : 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便后续的write操作使用。
  8. write(写入) : 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

happen —before原则

如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/838579.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号