JVM通俗易懂

一.JVM初识

jvm简介

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

在这里插入图片描述

Java类加载器(ClassLoader)及双亲委派

类加载器

classloader顾名思义,即是类加载。虚拟机把描述类的数据从class字节码文件加载到内存,并对数据进行检验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型

执行流程

在这里插入图片描述

启动类加载器(Bootstrap
Classloader)负责将<JAVA_HOME>/lib目录下并且被虚拟机识别的类库加载到虚拟机内存中。我们常用基础库,例如java.util.,java.io.,java.lang.**等等都是由根加载器加载。

扩展类加载器(Extention
Classloader)负责加载JVM扩展类,比如swing系列、内置的js引擎、xml解析器等,这些类库以javax开头,它们的jar包位于<JAVA_HOME>/lib/ext目录中。

应用程序加载器(Application
Classloader)也叫系统类加载器,它负责加载用户路径(ClassPath)上所指定的类库。我们自己编写的代码以及使用的第三方的jar包都是由它来加载的。

自定义加载器(Custom
Classloader)通常是我们为了某些特殊目的实现的自定义加载器,后面我们得会详细介绍到它的作用以及使用场景。

public class Test {

    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.getClass());
        Class<? extends Test> testClass = test.getClass();
        System.out.println(testClass.getClassLoader()+"  系统类加载器 (AppClassLoader)");
        System.out.println(testClass.getClassLoader().getParent()+"     扩展类加载器 (Extension ClassLoader)");
        System.out.println(testClass.getClassLoader().getParent().getParent()+"     启动类加载器 (Bootstrap Classloader)");
    }
}

执行结果

在这里插入图片描述

classloader双亲委托机制

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    //              -----??-----
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先,检查是否已经被类加载器加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 存在父加载器,递归的交由父加载器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 直到最上面的Bootstrap类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

执行流程

在这里插入图片描述

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

双亲委派的好处

果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap
classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

堆栈的区别

  • 物理地址: 堆的物理地址分配对对象是不连续的。因此性能慢些。 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
  • 内存分别: 堆因为是不连续的,所以分配的内存是在 运行期 确认的,因此大小不固定。一般堆大小远远大于栈。栈是连续的,所以分配的内存大小要在 编译期 就确认,大小是固定的。
  • 存放的内容: 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
    栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
  • 程序的可见度 堆对于整个应用程序都是共享、可见的。
    栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
  • GC机制:针对的是堆
    栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就over

堆的分区

在这里插入图片描述

  • Eden区:新对象都是在此new出来的
  • 老年代区:经过GC机制未清理的对象(长期存活的对象或者新生代无法容纳的大对象)进入老年代
  • 元空间:逻辑上存在,物理上不存在这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,java运行时的一些环境或类信息,不存在垃圾回收,关闭JVM就会释放这个区域的内存
 元空间的发展历程:
 1.6之前 永久代   常量池是在方法区中
 1.7永久代   但慢慢退化了-有了去永久代的想法     常量池在堆中  
 1.8后   无永久代    常量池在元空间中
 老年代中的对象:
 年龄达到一定的程度(默认是15)的对象
 在 survivor 空间中相同年龄所有对象大小的总和>survivor空间的一半

二.JVM调优

gc垃圾回收机制

当程序运行时,至少会有两个线程开启启动,一个是我们的主线程,一个时垃圾回收线程,垃圾回收线程的priority(优先级)较低。
垃圾回收器会对我们使用的对象进行监视,当一个对象长时间不使用时,垃圾回收器会在空闲的时(不定时)对对象进行回收,释放内存空间,程序员是不可以显示的调用垃圾回收器回收内存的,但是可以使用System.gc()方法建议垃圾回收器进行回收,但是垃圾回收器不一定会执行。

调优目的

使用较小的内存占用来获得较高的吞吐量或者较低的延迟。

重要指标

  • 内存占用:程序正常运行需要的内存大小。
  • 延迟:由于垃圾收集而引起的程序停顿时间。
  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

调优

根据内存调优

错误用例
public class Demo {
    public static void main(String[] args) {
        String str="123456";
        while (true){
            str+=str+new Random().nextInt(888888888)+new Random().nextInt(999999999);
        }
    }
}

先设置jvm的属性

-Xmx1024m:设置JVM最大可用内存为1024M。
-Xms1024m:设置JVM促使内存为1024m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-XX:+PrintGCDetails 用于打印输出详细的GC收集日志的信息.

在这里插入图片描述
报错信息
在这里插入图片描述

解决方案
  1. 先尝试扩大堆内存看结果
  2. 分析内存,看一下那个地方出现了问题(专业工具)
常用JVM参数参考
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
Xms 是设置初始化内存分配大小 默认是1/64
Xmx 是设置最大分配内存 默认是1/4
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。

gc垃圾回收设置优化

在这里插入图片描述

G1的适用场景
  • 面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜)
  • 最主要的应用是需要低GC延迟并具有大堆的应用程序提供解决方案(G1通过每次只清理一部分而不是全部Region的增量式清理来保证每次GC停顿时间不会过长)
  • 在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒
    用来替换掉JDK1.5中的CMS收集器,以下情况,使用G1可能比CMS好
    - 超过50% 的java堆被活动数据占用
    - 对象分配频率或年代提升频率变化很大
    - GC停顿时间过长(大于0.5至1秒)
  • 从经验上来说,整体而言:
    - 小内存应用上,CMS大概率会优于 G1;
    - 大内存应用上,G1则很可能更胜一筹。
    - 这个临界点大概是在 6~8G 之间(经验值)
其他收集器适用场景
  • 如果你想要最小化地使用内存和并行开销,请选择Serial Old(老年代) + Serial(年轻代)
  • 如果你想要最大化应用程序的吞吐量,请选择Parallel Old(老年代) + Parallel(年轻代)
  • 如果你想要最小化GC的中断或停顿时间,请选择CMS(老年代) + ParNew(年轻代)

jvm调优步骤

监控分析

分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点。

如何生成GC日志

生成GC日志
-XX:+UseG1GC 	代表使用G1垃圾收集器
-XX:MaxGCPauseMillis=100 	垃圾收集最大停顿时间
-Xmx256m 		代表堆内存最大大小
-XX:+PrintGCDetails 	输出GC详细信息
-XX:+PrintGCTimeStamps 	打印GC时间戳
-XX:+PrintGCDateStamps 
-XX:+PrintHeapAtGC 	代表执行GC前和之后堆内存状态
-Xloggc:G:\y2\JVM专题\gc.log  代表日志输出目录

产生dump文件

生成dump配置

准备工作

  • idea先下载JProfiler工具
  • JProfiler官网下载软件
  • idea中设置JProfiler
    在这里插入图片描述

java OOM错误示例

public class Demo {
    public static void main(String[] args) {
        String str="123456";

        try {
            while (true){
                str+=str+new Random().nextInt(888888888)+new Random().nextInt(999999999);
            }
        }catch (Error e){
            e.printStackTrace();
        }

    }
}

java 运行jvm配置

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

java 运行结果
在这里插入图片描述
java 生成dump文件
在这里插入图片描述

查看dump文件

双击打开*.hprof文件
在这里插入图片描述
在这里插入图片描述

后续待更

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值