JVM入门笔记,以及Jprofiler 安装使用 以及GC常见的几种算法 JMM 简单了解

JVM 探究
对jvm 得理解?
Jvm java虚拟机,是虚拟出来的计算机,是通过实际的计算机上仿真模拟各种计算机功能来实现的,Java 语言的一个非常重要的特点就是与平台无关性,而使用Java 虚拟机是实现这一特点的关键
java8 虚拟机和之前的变化更新?
撤销了永久带,引用了元空间:
在hotSpot 虚拟机中,jdk 1.6 时,实际团队把方法区设计为永久带,这样GC 工作区域就可以扩展至方法区,这种策略可以避免为方法区单独设计垃圾回收机制,坏处就是,方法区的回收条件十分苛刻,而且回收效果也不好
在jdk1.7 版本,设计团队也意识到这个问题,但是只将方法区中的字符串常量池移除永久带
到了jdk1.8 版本,就不在有永久带这个概念,并且用元空间来代替原来的永久代
元空间内的规则:元空间中类及其相关的元数据和类加载器生命周期一致,每个类加载器有专门的存储空间,不会单独回收某个类,位置也是固定的,但是当类加载器不再存活时会把它相关的空间全部移除。
什么是OOM?
全称是“OUT OF Memory” 就是内存用完了,来源于java.lang.OutOfMemoryError 官方说明 Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector 意思就是说,当JVM 因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error (注:非exception,因为这个问题已经严重到不足以被应用处理)。
为什么会有OOM?
1,分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM 参数指定)太少
2,应用用的太多,并且用完没释放,浪费了,此时就会造成内存泄漏或者内存溢出
内存泄漏
申请使用完的内存没收释放,导致虚拟机不能再次使用内存,此时内存就泄漏了
内存溢出
申请的内存超出了JVM 能提供的内存大小,此时称之为溢出

在之前用C语言或者C++ 语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完没有释放,比如C++ 中new 了但是没有delete,就可能会有内存泄漏,偶尔没事,大量的话就会导致内存溢出了。

而在Java 语言中,由于存在了垃圾自动回收机制,所以我们一般不用主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄漏”的,但是如果编码不当,比如将某个对象的引用放到了全局的MAP 中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收,这种情况多了就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。

最常见的OOM 情况有以下三种
java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄漏或者堆的大小设置不当引起,对于内存泄漏,需要通过内存监控软件查找程序中的泄漏代码,而堆大小可以通过虚拟机参数修改
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,一般出现在class 或者jsp 页面,或者cglib等反射机制的情况,因为上述情况会产生 大量的Class 信息存储与方法区,此种情况可以通过更改方法区的大小来解决,另外过多的常量尤其事字符串也会导致方法区溢出
java.lang.StackOverflowError ------> 不会抛OOM error ,但也是比较常见的JAVA 内存溢出,Java 虚拟机溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈太小也会出现此种溢出,可以通过虚拟机参数设置
Jvm 的常用调优参数有哪些?
博客园人的jvm调优

1,JVM 的位置
在这里插入图片描述
Jvm 的体系结构
在这里插入图片描述
类加载器
在这里插入图片描述
1,虚拟机自带得加载器
2,启动类(根)加载器
3,扩展类加载器
4,应用程序加载器

/**
 * @author fjj
 * @date 2021/1/22 15:47
 */
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Demo demo = new Demo();
        //获取是那个类得
        Class<? extends Demo> aClass = demo.getClass();
        //获取是那个类加载得这个类
        ClassLoader classLoader = aClass.getClassLoader();
        System.out.println("classLoader = " + classLoader);
        //向上获取
        System.out.println(classLoader.getParent());
        //在上获取到根加载器
        System.out.println(classLoader.getParent().getParent());
    }
}

在这里插入图片描述
5,双亲委派机制
什么是双亲委派机制
当某个类加载器需要加载某个.class 文件时,它首先把这个任务给他的上级类加载器,递归这个操作,如果上级得类加载器没有加载,自己才会去加载这个类
类加载器得类别
BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,构造ExtClassLoader和AppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*或者
java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
java编写,加载程序所在的目录
CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
双亲委派机制的作用
1,防止重复加载同一个.class。通过委托去向上面问一问,加载过, 就不用在加载一遍,保证数据安全
2,保证核心.class不能被篡改,通过委托方式,不会去篡改核心.class , 即使篡改也不会去加载,即使加载也不会是同一个.class。不同的加载器加载同一个.class 也不是同一个calss 对象,这样保证了class 执行安全。
沙箱机制
Java 安全模型的核心就是java 沙箱,什么是沙箱?沙箱是一个限制程序运行的环境,沙箱机制就是将Java 代码定在虚拟机(Jvm)特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏,沙箱主要限制系统资源访问,那系统资源包括什么?Cpu ,内核,文件系统,网络,不同级别的沙箱对这些资源访问的限制也可以不一样
组成沙箱的基本组件
字节码校验器;确保java 类文件遵循java 语言规范,这样可以帮助java 程序实现内存保护。但并不是所有类文件都会经过字节码校验,比如核心类。
类加载器:其中类装载器在3个方面对java 沙箱起作用。
它防止恶意代码去干涉善意的代码
它守护了被信任的类库边界
它将代码归入保护域确定了代码可以进行那些操作
几么士X。
存取控制器(access controller) : 存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略
设定,可以由用户指定。
安全管理器(security manager) :是核心AP和操作系统之间的主要接口。实现权限控制,比存取控制器优
先级高。
安全软件包(security package) : java.security 下的类和扩展包下的类,允许用户为自己的应用增加新的安
全特性,包括:
。安全提供者
。消息摘要
。数字签名
。加密
。鉴别

native 关键字
这是一段线程的代码我们点进去后看到

/**
 * @author fjj
 * @date 2021/1/24 15:57
 */
public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        new Thread(()->{
    
    },"我的线程").start();
    }
}

在这里插入图片描述

在一个Class 类中又好多的没有方法体的方法
他们都加了一个关键字
在这里插入图片描述
这个关键字,说明是java 的作用范围达不到的,回去调用底层c语言的库
就会进入到本地方法栈,调用本地接口
本地接口的作用扩展java 的使用,融合不同编程语言为Java 所用
在java 诞生的时候 C ,C++,横行,想要立足,必须要有调用C,C++ 的程序
它在内存区域中专门开辟了一块标记区域 Native Method Stack 登记native 方法
在最终执行的时候,加载本地方法库中的方法通过本地接口
在这里插入图片描述
Pc 寄存器
程序计数器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码)在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口函数也再次定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量,常量,类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

栈:数据结构
栈:先进后出,后进先出,类似于桶的概念
队列:先进先出 管道

简单类实例化的过程
在这里插入图片描述
子类对象的实例化过程
在这里插入图片描述

Heap 一个JVM 只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?类方法,常量,变量,保存我们所有引用类型的真实对象

在这里插入图片描述
上图是Jdk 8之前的,后来把永久存储区改为了元空间。
经过研究,99%的对象都是临时的对象
永久区
这个区常驻内存的,用来存放Jdk 自身携带的Class 对象,interface 元数据,存储的是Java 运行时的一些环境或者类信息,这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存

一个启动类,加载了大量的第三方jar 包,Tomcat 部署了太多的应用,大量动态生成的反射类,不断的被加载,知道内存存满,就会出现OOM

jdk1.6之前永久代常量池在方法区
jdk1.7永久代,但是慢慢的退化了,去永久代,常量池在堆中
jdk1.8之后没有了永久代,常量池在元空间
在这里插入图片描述

/**
 * @author fjj
 * @date 2021/1/25 14:17
 */
public class Hello {
    
    
    public static void main(String[] args) {
    
    
        //返回虚拟机试图使用的最大内存
        long maxMemory = Runtime.getRuntime().maxMemory();
        //返回jvm 的初始化总内存
        long totalMemory = Runtime.getRuntime().totalMemory();
        //输出转换成MB
        System.out.println("maxMemory字节= "+maxMemory+"\t" +(maxMemory/(double)1024/1024)+"MB");
        System.out.println("totalMemory字节= " +totalMemory+"\t"+ (totalMemory/(double)1024/1024)+"MB");
    }
}

在这里插入图片描述
我们可以设置jvm 的参数
在这里插入图片描述
在这里插入图片描述
设置最大最小的
在这里插入图片描述
伊甸园,写错了!!!
在这里插入图片描述
利用Jprofiler 工具监控
1,在idea 下载插件
在这里插入图片描述
2,下载客户端的exe

Jprofiler
在这里插入图片描述
可以选择版本,我选择的是9.1.1 的
安装就是傻瓜式安装
在idea 里设置一下
在这里插入图片描述
使用一下 ,下面的代码会导致OutOfMemoryError

import java.util.ArrayList;
import java.util.Random;

/**
 * @author fjj
 * @date 2021/1/25 15:48
 */
public class Textoom {
    
    

    public static void main(String[] args) {
    
    
        String fjj="Fengjiaojiao";
        while (true){
    
    
        fjj+= fjj+new Random().nextInt(999999999)+new Random().nextInt(999999999)+new Random().nextInt(999999999);
}

    }
}

在这里插入图片描述
出现这种情况我们可以先设置一下参数,dump 一下
在这里插入图片描述
这时跑完后会看到这个
在这里插入图片描述
在这里插入图片描述
用我们的工具打开
在这里插入图片描述
在这里插入图片描述
这时就可以看到我们具体的代码是哪一行出了问题
在这里插入图片描述
GC的
jvm在进行GC 时,并不是对所有区域统一回收,大部分时候,回收的都是新生代
GC分为2种:轻GC(普通的GC)重GC(全局的GC)
GC的算法
引用计数法
所谓的引用计数法就是给每个对象一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器的值就会减1;任何时刻计数器的值为0的对象就是不可能再被使用的。
这个引用计数法时没有被Java所使用的
优点
1,可即时回收垃圾:在该方法中,每个对象始终知道自己是否有被引用,当被引用的数值为0时,对象马上可以把自己当作空闲空间链接到空闲链表。

2,最大暂停时间短。

3,没有必要沿着指针查找

缺点
1,计数器值的增减处理非常繁重

2,计算器需要占用很多位。

3,实现繁琐。

4,循环引用无法回收。
复制算法
复制算法就是将内存空间按容量分成两块。当这一块内存用完的时候,就将还存活着的对象复制到另外一块上面,然后把已经使用过的这一块一次清理掉。这样使得每次都是对半块内存进行内存回收。内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
优点
没有内存的碎片
缺点
浪费内存空间。多了一半空间永远空to 假设对象100%存活(极端情况)
最佳使用场景:对象存活度比较低的时候,新生区
标记扫描算法
标记扫描算法就是先标记那些没有使用的方法,然后扫描把那些没有标记的给删除
优点
不需要额外的空间
缺点
会产生内存碎片
标记压缩算法
是为了防止内存碎片的产生,向一段移动存活的对象多了一个移动的成本

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

分代收集算法
由于还没有最好的算法,只有最合适的算法。
我们可以用分代算法
年轻代:
存活率低:复制算法
老年代:
区域大,用标记清除+标记压缩混合实现
什么是JMM模型
1,Java 内存模型(Java memory Model )是一种抽象的概念,并不是真实存在,它描述的是一组规则或者规范,通过这组规范定义程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题。
2,Jvm 运行程序的实体是线程,而每个线程创建时,Jvm 都会为其创建一个工作内存(也可以叫栈空间)用来存储线程私有的数据
3,Java 内存模型中规定所有的变量都存储在住内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝。

猜你喜欢

转载自blog.csdn.net/m0_46937429/article/details/113041460
JMM