JVM

小德 2021-12-02 12:57:59
Categories: Tags:

JVM学习

1.请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?

2.什么是DOM?什么是栈溢出StackOverFlowError?怎么分析?

3.JVM的常用调优参数有哪些?

4.内存快照如何抓取,怎么分析Dump文件?知道吗?

5.谈谈JVM中,类加载器你的认识?

什么是JDK,JRE

1.JDK:Software Devleopment Kit(软件开发包,开发需要JDK)
JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有 的一切,还有编译器(javac)和工具(如javadoc和jdb).它能够创建和编 译程序
jdk 目录介绍:
bin 编译器和工具
demo 演示
docs HTML格式的类库文档(解压j2sdkversion-doc.zip之后)
include 用于编译本地方法的文件
jre java运行环境文件
lib 类库文件(jar包,java命令和其他的一些基础构件,如基本数据类型的封装类)
src 类库源文件(jar文件源码,可用JD-GUI等反编译文件获取)

2.JRE:Java Runtime Enviroment(java运行环境,用户只需JRE)
JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合, 包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但 是,它不能用于创建新程序。

3.JDK与JRE关系

如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。 如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但 是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲, 您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用 程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。

4.JVM:运行 Java 字节码文件(.class文件)的虚拟机。
JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果(跨平台特性,一定程度减慢了java程序的执行速度)。注意:JVM是分版本的,因为JVM需要调用平台(即操作系统)底层的函数。也可这样说,**JVM是不跨平台的(而Java语言是跨平台的)**,因为它的存在就是Java程序和操作系统之间的一个过渡,所以它得分版本。

img

JVM的位置

JVM在操作系统之上。

JVM的体系结构

JVM

类加载器

作用:加载class文件

1.用户自定义加载器

2.启动类(根boot)加载器

3.扩展类(ext)加载器 jre/lib/ext/

4.应用程序(app)加载器 jre/rt.jar

双亲委派机制

1、翻译的问题,parents翻译为父母,即双亲

2.AppClassLoader 向上委托了两次,即“双”,“亲”代表亲人的意思,对双亲的理解

3、或者直接理解成父委派模型(Parents Delegation Model)

向上委托:APP->EXT->BOOT(最终执行rt.jar)

启动类加载器会 检查是否能够加载当前这个类,能加载就结束,使用当前加载器,不能加载,抛出异常,通知子加载器进行加载。

加载:BOOT(最终执行rt.jar)->EXT->APP

null:java调用不到,用c、c++写的

JVM中提供了三层的ClassLoader:

Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

AppClassLoader:主要负责加载应用程序的主函数类

img

从上图中我们就更容易理解了,当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException

作用:

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

沙箱安全机制

什么是沙箱?

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

  所有的Java程序运行都可以指定沙箱,可以定制安全策略。

java中的安全模型:

  在Java中将执行程序分成本地代码远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示 JDK1.0安全模型
在这里插入图片描述

  但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示 JDK1.1安全模型
在这里插入图片描述

  在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示 JDK1.2安全模型
在这里插入图片描述

  当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示 最新的安全模型(jdk 1.6)
在这里插入图片描述

  以上提到的都是基本的Java 安全模型概念,在应用开发中还有一些关于安全的复杂用法,其中最常用到的 API 就是 doPrivileged。doPrivileged 方法能够使一段受信任代码获得更大的权限,甚至比调用它的应用程序还要多,可做到临时访问更多的资源。有时候这是非常必要的,可以应付一些特殊的应用场景。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。

组成沙箱的基本组件:

  虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

  类装载器采用的机制是双亲委派模式。

  1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。

Native

凡是带有native关键字,java作用达不到了,会去调用底层c语言库。

进入本地方法栈,调用本地方法接口(JNI),调本地方法库。

JNI作用:扩展java的使用,融合不同的编程语言为java所用!

它在内存区域中专门开辟一块标记区域:本地方法栈,标记native方法。

JAVA程序驱动硬件,才用到native方法。

PC寄存器

程序计数器:Program Counter Register

每一个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中方法字节码,在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

方法区

方法区被所有线程共享。所有字段和方法的字节码,以及构造函数,接口都在这定义。

在Java虚拟机中,方法区是可供各线程共享的运行时内存区域。

在不同的JDK版本中,方法区中存储的数据是不一样的。

在JDK1.6及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)等。

静态变量、常量、类信息(构造方法、接口定义)运行时的常量池存在方法区,但实例变量存在堆内存中,与方法区无光。

static\final\class =>常量池

在JDK1.7及以后,JVM已经将运行时常量池从方法区中移了出来,在JVM堆开辟了一块区域存放常量池。

栈:数据结构

程序=数据结构+算法

后进先出(桶)

队列:先进先出 FIFO

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

栈:八大基本类型+对象引用+实例方法

栈运行原理:栈帧

栈

程序正在执行的方法,一定在栈的顶部。

栈满了,抛StackOverflowerError

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

面向对象内存图

三种JVM

Sun公司Hot Spot,Java HotSpot(TM) Client VM (build 25.291-b10, mixed mode, sharing),是目前使用范围最广的Java虚拟机。

BEA公司JRockit,系列产品是一个全面的Java运行时解决方案组合。

IBM公司J9VM,是一个高性能的企业级 Java 虚拟机。

一个JVM只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取类文件后,一般会把类、方法、常量、变量以及引用类型的真实对象放入堆中。

堆内存中分为三个区:

1.新生区

2.养老区

3.永久区

GC垃圾回收,主要在新生区和养老区

堆内存不够,OOM,抛OutOfMemoryError

在jdk8以后,永久存储区叫元空间。

新生区、养老区

新生区:类诞生和成长,甚至死亡

分为:伊甸园区和幸存者区,幸存者区又分为0区和1区

所有的对象都是在伊甸园区new出来的。

经过研究,99%的对象都是临时对象,活不到养老区。

新生区存活的进入养老区

新生区会进行轻量GC,养老区进行重量GC

永久区

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

一个启动类,加载了太多的第三方jar包,tomcat部署太多应用,大量动态生成反射类,不断的加载,直到内存满了,出现OOM。

jdk1.7:永久代,但是慢慢退化,去永久代,常量池在堆中。

jdk1.8之+后:无永久代,常量池在元空间。

常量池在方法区,方法区在元空间。

元空间逻辑上存在,物理上不存在。

堆内存调优

1
2
Runtime.getRuntime().maxMemory() //返回虚拟机试图使用的最大内存(单位:字节)
Runtime.getRuntime().totalMemory() //返回jvm的总内存(单位:字节)

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

堆内存满了OOM

1.尝试扩大堆内存

(IDEAJ的VM options)调参数

-Xms 设置初始化内存分配大小

-Xmx 设置最大分配内存

1
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
1
2
3
4
5
6
7
8
Heap
def new generation total 314560K, used 22369K [0x04600000, 0x19b50000, 0x19b50000)
eden space 279616K, 8% used [0x04600000, 0x05bd86d8, 0x15710000)
from space 34944K, 0% used [0x15710000, 0x15710000, 0x17930000)
to space 34944K, 0% used [0x17930000, 0x17930000, 0x19b50000)
tenured generation total 699072K, used 0K [0x19b50000, 0x44600000, 0x44600000)
the space 699072K, 0% used [0x19b50000, 0x19b50000, 0x19b50200, 0x44600000)
Metaspace used 3384K, capacity 3448K, committed 3520K, reserved 4480K

2.分析内存,看一下哪个地方出现了问题(专业工具)

JProfiler工具

MAT/JProfiler作用:

1.分析Dump内存文件,快速定位内存泄漏

2.获得堆中的数据

3.获得大的对象

IDEAJ安装插件JProfiler

下载jprofiler客户端

Tools->jprofiler配置

1
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

GC垃圾回收机制

GC作用区域在堆中,新生区、幸存区(from,to)和养老区。

大部分回收都在新生区。

GC分为:轻GC(普通的GC)和重GC(全局的GC)

当一个对象经历了15次GC都没有死(默认),就会进入养老区。

1
-XX:MaxTenuringThreshold=15 //设置进入老年区的经历GC的次数

常用算法

from区和to区相互转化,每转化一次,把转化后的to区回收

1.引用计数法:给每个对象分配计数器,计数器本身也会有消耗,一般不采用该算法,不太好。

2.复制算法:幸存区0区和幸存区1区,谁空谁是to。每次GC会将新生区对象移到幸存to区。当两个幸存区有相同部分,from区相同部分复制到to区,然后原to区变为from区,原from区变为to区,被回收。

好处:没有内存碎片

坏处:浪费内存空间,多一半空间永远是空to,假设对象100%存活(极端情况)

复制算法最佳使用场景,对象存活率较低。

3.标记清除法:扫描这些对象,对活着的对象进行标记,对没有标记的对象进行清除。

优点:不需要额外的空间

缺点:两次扫描,严重浪费时间,会产生内存碎片

标记压缩:防止内存碎片产生,再次扫描,向一端移动存活的对象,多了一个移动成本

标记清除压缩:先标记清除几次,再进行压缩

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

内存整齐度:复制算法=标记压缩算法>标记清除算法

内存利用率:标记压缩算法=标记清除算法>复制算法

难道没有最优的算法吗?

没有最好的算法,只有最合适的。

GC:分代收集算法。

新生区:存活率低:复制算法

养老区:存活率高:标记清除算法+标记压缩算法

JMM:java内存模型

1.什么是JMM?

他其实就是JVM内部的内存数据的访问规则,线程进行共享数据读写的一种规则,在JVM内部,多线程就是这么读取数据的

JMM即为JAVA 内存模型(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。JMM从java 5开始的JSR-133发布后,已经成熟和完善起来。

Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面,有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型。究竟什么是内存模型?内存模型描述了程中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的,这点没有错,但是编译器、运行库、处理器或者系统缓存可以有特权在变量指定内存位置存储或者取出变量的值。【JMM】(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了volatile或synchronized明确请求了某些可见性的保证。

2.它是干嘛的?官方,博客,对应视频!

JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。

3.它如何学习?

jvm和jmm的关系

java内存模型jmm是jvm规范,定义的一种抽象的内存模型。划分的抽象逻辑区域和jvm的内存模型区域是可以一一对应上的,如 jvm中 堆和方法区 中共享数据与都jmm主内存共享数据一致。

总结