MathDemo.java

JVM内容

JVM(Java Virtual Machine)是Java虚拟机的缩写,是Java程序运行环境。JVM是一种能够解释Java字节码并将其转换机器指令软件

JVM的作用

ProcessOn Flowchart

首先通过编译器把 Java 代码转换成字节

加载器(ClassLoader) 再把字节码加载内存中,将其放在运行数据区(Runtime data area)的方法区内

而字节码文件只是 JVM 的一套指令规范,并不能直接交给底层操作系统执行

因此需要特定的命令解析器执行引擎(ExecutionEngine),将字节码翻译底层系统指令,再交由 CPU 去执行,而这个过程需要调用其他 语言的本地库接口(Native Interface)来实现整个程序的功能

JVM包含两个子系统两个组件

两个子系统为Class loader(类装载)、 Execution engine(执行引擎);

两个组件为Runtime data area(运行数据区)、Native Interface(本地接口)。

JVM具有以下重要内容

1.类装载子系统(ClassLoader):

负责从系统文件或网络加载class信息内存中。根据给定的全限定类名(如: java.lang.Object)来装载class文件到Runtime data area(运行数据区)中的method area(方法区)。

2.字节码执行引擎

将字节码转换机器指令并执行,负责执行那些被加载的类的方法

代码运行时,执行引擎会首先去找到方法区中被加载的类的类型信息然后找到对应方法进行执行。

3.运行数据区:

这是JVM的重要组成部分,它包含了程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区等。

运行时数据区中,又分为线程私有和线程共享区域

线程共享区域包含堆区方法

线程私有区域包含: 栈、本地方法栈、程序计数器

6.垃圾回收器:自动回收不再使用对象以便释放内存空间

简述java类加载机制?

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验解析和初 始化,最终形成可以虚拟直接使用java类型

描述一下JVM加载Class文件的原理机制

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也 是一个类,而它的工作就是class文件从硬盘读取到内存中。在写程序的时 候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们特殊用法,像是反射,就需要显式的加载所需要的类。

类装载方式,有两种 :

1.隐式装载, 程序在运行过程中当碰到通过new方式生成对象时,隐式调用 类装载器加载对应的类到jvm

2.显式装载, 通过class.forname() 等方法,显式加载需要的类

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证 程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候 才加载。这当然就是为了节省内存开销。

什么是类加载器,类加载器有哪些?

主要有一下四种类加载器:

1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被 java程序直接引用

2. 扩展类加载器(extensions class loader): 它用来加载 Java 的扩展库。 Java 虚拟机的实现会提供一个扩展目录该类加载器在此目录里面查找 并加载 Java 类。

3. 系统类加载器(system class loader ):它根据 Java 应用的类路径 (CLASSPATH )来加载 Java类。一般来说,Java 应用的类都是由它来 完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取 它。

4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现

说一下类装载的执行过程?

类装载分为以下 5个步骤

加载:根据查找路径找到相应的 class 文件然后导入

验证检查加载的 class 文件的正确性;

准备:给类中静态变量分配内存空间

解析虚拟机将常量池中的符号引用替换直接引用的过程。符号引用理解一个标示,而在直接引用直接指向内存中的地址

初始化:对静态变量静态代码块执行初始化工作

什么是双亲委派模型

介绍双亲委派模型之前先说下类加载器。

对于任意一个类,都需要由加载它的 类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一 个独立的类名称空间

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类给定类的二进制名称,类加载器应尝试定位生成构成类定义的数据。典型的策略是将名称转换文件名然后文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候该数组的元素类型的 ClassLoader 是一致的。

双亲委派模型

在类加载的时候,系统会首先判断当前是否被加载过。已经被加载的类会直接返回,否则才会尝试加载(每个父类加载器都会走一遍这个流程)。

类加载器在进行类加载的时候,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成调用父加载器 loadClass()方法来加载类)。这样的话,所有的请求最终都会传送到顶层的启动类加载器 BootstrapClassLoader 中。

只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载(调用自己的 findClass() 方法来加载类)。

JVM 判定两个 Java 类是否相同的具体规则

JVM 不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即使两个类来源同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相同

双亲委派模型的好处

双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改

如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题

比如我们编写一个称为 java.lang.Object 类的话,那么程序运行时候,系统就会出现两个不同的 Object 类。

双亲委派模型可以保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。

这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而 ExtClassLoader 又会委托给 BootstrapClassLoader,BootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。

拷贝和浅拷贝

拷贝shallowCopy)只是增加了一个指针指向存在内存地址

拷贝deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加 的指针指向这个新的内存,使用拷贝的情况下,释放内存的时候不会因为出现拷贝时释放同一个内存的错误

复制:仅仅是指向复制内存地址,如果原地址发生改变,那么浅复制出来 的对象也会相应的改变。

深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。

Java代码执行步骤

1,当我们某个java程序,启动main方法以后,JVM的线程栈,会开辟一个空间,用来存放这个程序运行相关局部变量

2,当main方法开始运行,这个线程栈会为main方法创建一个区域,称为栈帧,这个里面存放main方法内部局部变量比如上面的mathDemo

3,当执行到compute方法之后,同样的也会为这个方法创建一个栈帧,存放compute方法对应的局部变量,也就是上面的 a,b,c

4,线程栈的数据结构,符合栈的特点,先进后出,main方法在下面,compute方法在上面,当compute方法使用完以后,先被销毁

5,为每个方法生成的栈帧中,其实还分成4个区,局部变量表,操作数栈,动态链接,方法出口,这四个区域其实就是跟代码执行的过程相关联

为什么设计程序计数器?

程序计数器是为了记录某个线程执行的步骤,因为Java是多线程的,如果某个线程A在执行的时候,被别的线程B抢先执行,那么,当再次执行线程A的时候,通过程序计数器,就不会再去重复执行线程A之前执行过的代码,保证代码能够正常执行。

程序计数器的值怎么实现变化的 ?

我们编译好的字节码文件,比如MathDemo.class,是存放在方法区的,这个字节码文件由字节码执行引擎来执行,所以,程序计数器其实是由字节码执行引擎修改

操作数栈是什么

操作数栈就是Java代码在运行过程中,存放一些值、运算结果临时内存空间

动态链接什么

定义是:将符号引用变为直接引用底层主要是一些c++实现语法

这里要去理解什么符号什么符号引用、直接引用

符号其实就是我们Java中的一些方法名、变量等

符号引用指的是 :当程序运行调用某些方法的时候,会去找对应方法的代码(也就是字节码),这些代码存放在内存区域里面的方法区,

在方法区存放的具体位置,就是直接引用,这个被调用的方法名,就是符号引用。

所以,动态链接里面存放的主要就是一些方法区中的代码地址。

方法出口是什么

主要是记录这个方法执行完以后,后面应该继续执行什么内容

main方法中的局部变量

mathDemo是main方法中的一个局部变量,它的值是new的一个对象,new出来的对象其实是放在堆中的,所以main方法中的局部变量其实就是存放的一个引用地址,地址执行堆中的实际的对象。

所以在栈中,就存放了很多的一些地址引用,这些地址引用指向堆中的具体对象

方法区的作用

方法区其实主要存放的是常量、静态变量、类信息

比如之前类中的initData就是一个常量,user就是一个静态变量,这个user其实也是一个地址,指向堆中的new User()对象。

所以这个方法区中,其实也是存放了一些地址引用,这些地址也是指向堆中的具体对象

本地方法栈是什么

我们通过新建一个线程去调用start()方法的时候,查看start()方法的源码发现,在代码中存在一个start0()这个方法

这个start0()其实就是一个本地方法,它是由native修饰的,表示底层由C或者C++实现,那么这些代码在执行的时候,在内存中也需要一些空间来存放对应的一些代码数据,那这些空间其实就是本地方法栈。

堆是什么

GC是什么?为什么要GC

GC 是垃圾收集的意思(Gabage Collection),内存处理编程人员容易出现问 题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃

Java 提供的 GC 功能可以自动监测 对象是否超过作用域从而达到自动回收内存的目的

垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及 使用情况。

通常,GC采用有向图方式记录管理堆(heap)中的所有对象。通过这种方式 确定哪些对象是”可达的”,哪些对象是”不可达的”。

当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间

可以。

程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不 保证GC一定会执行。

怎么判断对象是否可以被回收?

垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。

一般有两种方法来判断:

  • 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题
  • 可达性分析算法:从 GC Roots 开始向下搜索搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

在Java中,对象什么时候可以被垃圾回收

当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被 回收了。

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。

如果你仔细查看垃圾收集器输出信息,就会发现永久代 也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因

什么是GC Roots

GC Roots就是将Eden区的对象作为根节点然后依次去分析这个对象中所有用到的其他对象,只要是这个对象还在被引用,就会被标记为非垃圾对象,其余的未标记的就是垃圾对象,这种算法就是可达性分析算法

GC Roots 根节点:线程栈的本地变量,静态变量,本地方法栈的变量等 ,就比如示例类中的 mathDemo,user

调优工具

系统自带 : jvisualvm

可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等

安装 Visual GC插件 ,可以观察内存运行的情况

VisualVM界面,工具》插件》已下载添加插件,选择已下载插件然后点击安装,直到安装完成

然后重启VisualVM

然后测试如下代码:

结合图形,了解内存中堆的结构

阿里的工具:Arthas

调优案例

调优的目的是什么 ?

先说一下刚才的循环案例,最终报了一个内存溢出的错误 OutOfMemoryError

为什么会报OOM,老年代放满了老年对象后,在报OOM之前,会做一个full gc ,也就是全局的一个垃圾回收,但是这些对象全部都在使用,没法回收,老年代没有空间,所以最终才报了OOM错误

所以,可以说,调优的目的是减少gc,减少full gc ,但是其实最终的目的是减少STW(stop the world)

STW是什么

stw表示停止用户线程,只要做gc后,JVM就会进行STW机制,停掉用户线程

比如用户在进行下单的操作,点击按钮,那这个操作其实都是在后台有对应的代码执行逻辑,这些都可以成为用户线程,gc是Java程序的后台线程,那么gc在执行的时候,会停掉用户的线程,也就是STW,那用户下单的时候,如果Java后台正好执行gc了,用户会有卡顿的感觉,这种现象对于用户的体验是不太友好的,所以要去减少STW的出现,也就是调优

为什么要设计STW机制?(阿里面试题)

如果不做STW机制,设想一下,当系统在做gc的时候,程序继续运行,那么会出现什么现象?

gc在做GC Roots 根节点判断的时候,程序继续运行,当程序执行完以后,这个线程中对应的其他的变量、栈空间等内容会全部被清空,因为这个时候线程已经随着程序执行结束消失了,那么刚才gc 判断出来的 GC Roots 根节点状态也就发生了改变,原来是非垃圾,现在变成垃圾对象,那不可能让gc再次去做GC Roots 这个工作,所以,就设计出了这种机制,当系统在做gc的时候,将用户线程先停止,然后当gc完成后,再次运行

调优案例

问题:电商项目上线之后,平时没问题,流量加大后,会频繁的出现full gc,频繁的full gc会让用户体验很差,怎么解决

ProcessOn Flowchart

原文地址:https://blog.csdn.net/lLlLlL__lL/article/details/134693848

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_43304.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注