JVM探究

JVM探究

一、面试题

1.1、请谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
1.2、什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
1.3、JVM的常用调优参数有哪些?
1.4、内存快照如何抓取,怎么分析Dump文件?知道吗?
1.5、谈谈JVM中,类加载器的认知?

二、参考学习的视频和文章:

本篇主要学自b站狂神,知识浅显,待进一步学习和完善,学无止尽!
2.1视频:

狂神说–JVM快速入门

2.2文章:

java中的基本数据类型和引用类型在JVM中存储在哪?

堆栈知识讲解

一看你就懂,超详细java中的ClassLoader详解

年轻代、老年代、GC原理详细拆解

JVM参数列表

三、核心知识

3.1、JVM的位置

3.2、JVM的体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BYqPmmvK-1592488206326)(E:\typoraPic\jvm例图.jpg)]

3.3、类加载器
  1. Bootstrap ClassLoader:最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等;
  2. Extention ClassLoader:扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件;
  3. Appclass Loader:也称为SystemAppClass,加载当前应用的classpath的所有类;
package com.lxf.demo5;

public class Car {
    public static void main(String[] args) {
        //类是模板

        Car car1=new Car();
        Car car2=new Car();
        Car car3=new Car();

        //打印每个car对象的hashCode,发现不一样
        System.out.println(car1.hashCode());
        System.out.println(car2.hashCode());
        System.out.println(car3.hashCode());


        Class<? extends Car> aClass1 = car1.getClass();
        Class<? extends Car> aClass2 = car2.getClass();
        Class<? extends Car> aClass3 = car3.getClass();

        //打印每个car对象的class,发现一样
        System.out.println(aClass1.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3.hashCode());


        ClassLoader classLoader = aClass1.getClassLoader();

        //打印类加载器

        System.out.println(classLoader);//AppClassLoader 

        System.out.println(classLoader.getParent());//ExtClassLoader 目录位置:\jre\lib\ext

        System.out.println(classLoader.getParent().getParent());//null java程序获取不到(底层c、c++写的) rt.jar
    }
}

3.4、双亲委派机制
package java.lang;

public class String {
  /*
  *JVM加载一个class时先查看是否已经加载过,没有则通过父加载器,然后递归下去,直到BootstrapClassLoader,如果    		   *BootstrapClassloader找到了,直接返回,如果没有找到,则一级一级返回(查看规定加载路径),最后到达自身去查找这些对象。这   *种机制就叫做双亲委托
  *
  *作用:
  *1.避免重复加载
  *2.防止恶意加载
  *3.编写恶意类java.lang.Object,自定义加载替换系统原生类;
  */


    public String toString(){
        return "hello";
    }

    public static void main(String[] args) {
        String s=new String();
        s.toString();
    }
}

3.5、沙箱安全机制

学习文章:沙箱安全机制

3.6、Native(本地方法)
package com.lxf.demo5;

public class Demo {
    public static void main(String[] args) {
        new Thread(()->{

        },"my thread name").start();
    }
    //native:凡是带了native 关键字的,说明java的作用域范围抵达不到,会去调用底层c语言的库!
    //会进入本地方法栈
    //调用本地方法接口 JNI
    //JNI作用:扩展java的使用,融合不同的编程语言为java所用! 最初:c,c++
    //java诞生的时候:C、C++ 横行,想要立足,必须要有调用C、C++的程序
    //它在内存区域中专门开启一块标记区域:Native Method Stack,登记了native方法
    //最终执行的时候,通过JNI加载本地方法库中的方法

    //例如Java程序驱动打印机,管理系统
    private native void start0();
}

3.7、PC寄存器

程序计数器:Program Counter Register

​ 每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区的方法字节码(用来存储指向一条指令的地址,也将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

3.8、方法区

Method Area方法区:

​ 方法区是被所有的线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有 定义的方法的信息都保存在该区域,此区域属于共享空间;

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

比如:static、final、Class、常量池(jdk1.7在堆,jdk1.8在元空间)

3.9、栈

存放:八大基本类型、类对象的引用类型

一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因

在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。

  • 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中
  • 当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。
二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。

同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量

  1. 当声明的是基本类型的变量其变量名及其值放在堆内存中的
  2. 引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

3.10、三种JVM
  • Sun公司 HotSpot (常用)
  • BEA JRocjit
  • IBM J9 VM
3.11、堆
一个JVM只有一个堆内存,堆内存的大小是可以调节的

类、方法、常量、变量,保存我们所有的引用类型的真实对象;

堆内存中还要细分为三个区域:

  • 新生代
    • 伊甸园区
    • from区
    • to区
  • 老年代
  • 永久区(jdk1.8后改为元空间)
2.12、新生代
  • 类:诞生和成长的地方,甚至死亡
  • 伊甸园区:所有的对象都是在伊甸园区创建的
2.13、老年代

当年轻带随着不断地Minor GC ,from survivor中的对象会不断成长,当from survivor中的对象成长大15岁的时候,就会进入老年代,在老年代的就能存储很久了,只有重GC时才会来清理老年代中的垃圾

3.14、永久区(jdk改名元空间,效果也有改变)

这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境,这个区域不存在垃圾回收!关闭VM虚拟机就会释放这个区域的内存

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

  • jdk1.6之前:永久代,常量池是在方法区
  • jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
  • jdk1.8之后:无永久代,常量池在元空间
3.15、堆内存调优
1.设置jvm最大内存和初始化内存大小

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

-Xms:设置初始化内存分配大小(默认1/64)

-Xms:设置最大分配内存(默认1/4)

PrintGCDetails :打印GC垃圾回收线程清理信息

package com.lxf.demo5;

public class MyTest {
    public static void main(String[] args) {
        long max = Runtime.getRuntime().maxMemory();
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max = " + max/1024/1024);
        System.out.println("total = " + total/1024/1024);
    }
}

2.内存快照分析:

MAT插件:eclipse集成的

Jprofiler插件

MAT,Jprofiler作用:

  • 分析Dump内存文件,快速定位内存泄漏
  • 获得堆中的数据
  • 获得大的对象

下载Jprofiler

  • 在Jprofier官网下载安装
  • idea下载jprofiler插件
  • 注册码:[email protected]#36573-fdkscp15axjj6#25257
  • 配置idea:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7rsbfIf-1592488206346)(E:\typoraPic\image-20200618153830768.png)]

测试:

package com.lxf.demo5;

import java.util.ArrayList;

public class Demo2 {
    byte[] array=new byte[1*1024*1024];//1m
    public static void main(String[] args) {
        ArrayList<Demo2> list=new ArrayList<Demo2>();

        int count=0;
        try {
            while(true){
                list.add(new Demo2());
                count++;
            }
        } catch (OutOfMemoryError e) {
            System.out.println("count = " + count);
            e.printStackTrace();
        }
    }
}

报错:

count = 819
java.lang.OutOfMemoryError: Java heap space//堆内存满了
	at com.lxf.demo5.Demo2.<init>(Demo2.java:6)
	at com.lxf.demo5.Demo2.main(Demo2.java:13)

打印Dump文件配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VGEwa8U8-1592488206356)(E:\typoraPic\image-20200618155007409.png)]

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

HeapDumpOnOutOfMemoryError:报OOM错时生成Dump文件

再次运行Demo2:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid21752.hprof ...
Heap dump file created [7901177 bytes in 0.036 secs]//生成Dump文件
count = 6
java.lang.OutOfMemoryError: Java heap space
	at com.lxf.demo5.Demo2.<init>(Demo2.java:7)
	at com.lxf.demo5.Demo2.main(Demo2.java:14)

打开Demo.java文件的位置,向上一直找到src平级,找到java_pid21752.hprof文件,双击打开

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uXQxy8s6-1592488206365)(E:\typoraPic\image-20200618155526578.png)]

3.16、GC(垃圾回收)

jvm算法讲解1

jvm算法讲解2

概述:

  1. 常用算法:复制算法、标记整理(压缩)、引用计数器、标记清除法

  2. JVM在进行GC时,并不是对这三个区域统一回收。大部分的时候,回收都是新生代

  3. GC两种类:轻GC(普通的GC)、重GC(全局GC)

题目:

  • JVM的内存模型和分区-详细到每个区
  • 堆里面的分区有哪些?Eden,from,to,老年代,说说他们的特点!
  • GC的算法有哪些?复制算法、标记整理(压缩)、引用计数器、标记清除法,怎么用?
  • 轻GC和重GC分别在什么时候发生?

引用计数法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HY1P6kl-1592488206378)(E:\typoraPic\image-20200618162528364.png)]

复制算法:

解释:年轻代每次GC后,幸存下来的会进入一个幸存区,这个幸存区就叫from,第二次GC后,再将from还在用的和Eden Space幸存下来的交给to区(from中的内容通过复制算法转移),此时to变from,from变to(清空变to)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8exhPXK-1592488206384)(E:\typoraPic\image-20200618165010837.png)]

  • 好处:没有内存的碎片

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

  • 复制算法最佳使用场景:对象存活度较低的时候(新生区);

标记清除法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UaX3RchL-1592488206390)(E:\typoraPic\image-20200618170616408.png)]

  • 优点:不需要额外的空间!
  • 缺点:两次扫描,严重浪费时间,会产生内存碎片

标记压缩(标记清除的优化):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHt1scuG-1592488206399)(E:\typoraPic\image-20200618171118004.png)]

总结:

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

思考:

  • 问题:难道没有最优的算法?
  • 答案:没有,没有最好的算法,只有最合适的算法–>GC:分代收集算法
  • 年轻代:存活率底–>复制算法
  • 老年代:区域大,存活率高–>标记清除+标记压缩混合 实现
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章