zoukankan      html  css  js  c++  java
  • JVM初识

    JVM初识

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

    1、JVM的位置

    2、JVM的体系结构

    3、类加载器

    • 作用:加载class文件 ---》new Student();

    1、虚拟机自带的加载器

    2、启动类(根)加载器

    3、扩展类加载器

    4、应用程序(系统类)加载器

    示例:

    package lesson04;
    
    public class Car {
        public int age;
    
        public static void main(String[] args) {
            Car car1 = new Car();
            Car car2 = new Car();
            Car car3 = new Car();
    
            System.out.println(car1.hashCode());
            System.out.println(car2.hashCode());
            System.out.println(car3.hashCode());
    
            Class c1 = car1.getClass();
            Class c2 = car2.getClass();
            Class c3 = car3.getClass();
    
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
            System.out.println(c3.hashCode());
    
            ClassLoader classLoader = c1.getClassLoader();
            System.out.println(classLoader);//AppClassLoader
            ClassLoader parent = classLoader.getParent();
            System.out.println(parent);//ExtClassLoader  jre/lib/ext
            ClassLoader parent1 = parent.getParent();
            System.out.println(parent1);//null rt.jar
    
        }
    }
    
    

    结果:

    460141958
    1163157884
    1956725890
    685325104
    685325104
    685325104
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1540e19d
    null
    

    4、双亲委派机制

    示例:

    package java.lang;
    
    public class String {
        /**
         *双亲委派机制:安全
         * 1、APP ---->EXT---->BOOT(最终执行)
         */
        public String toString(){
            return "hello";
        }
    
        public static void main(String[] args) {
            String s = new String();
            System.out.println(s.getClass().getClassLoader());
            s.toString();
        }
        /**
         * 1、类加载器收到类加载的请求
         * 2、将这个请求向上委托给父类加载器完成,一直向上委托,直到跟加载器BOOT
         * 3、启动类加载器是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
         * 4、重复步骤 3
         * 
         * null :Java调用不到——————cc++
         * Java = C++; 去掉指针和内存管理  --》C++--
         */
    }
    
    

    结果:

    错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
       public static void main(String[] args)
    否则 JavaFX 应用程序类必须扩展javafx.application.Application
    
    Process finished with exit code 1
    

    5、沙箱安全机制

    组成沙箱的基本组件:

    • 字节码校验器(bytecode verifier):确保java类文件遵循java语言规范,这样可以帮助java程序实现内存保护,但并不是所有的类文件都会经过字节码校验,比如核心类。

    • 类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用

      • 它防止恶意代码区干涉善意的代码;//双亲委派机制
      • 它守护了被信任的类库边界
      • 它将代码归入保护域,确定了代码可以进行那些操作

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

      1、从最内层的JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;

      2、由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获取权限访问到内层类,破坏代码就自然无法生效;

      • 存取控制器(access controller ):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
      • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
      • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性 包括:
        • 安全提供者
        • 消息摘要
        • 数字签名 keytools
        • 加密
        • 鉴别

    6、Native

    package test;
    
    public class Demo {
        public static void main(String[] args) {
            new Thread(()->{},"my thread name").start();
        }
    
        /**
         * native:凡是带了native 关键字的,说明Java的作用范围达不到了,会去掉底层C语言的库
         * 会进入本地方法栈
         * 调用本地方法本地接口 JNI  java native interface
         * JNI作用:扩展java的使用,融合不同的编程语言为JAVA所用! 最初:c c++
         * Java诞生的时候 C C++ 横行,想要立足 就必须要有调用 C C++ 的程序
         * 它在内存区域中专门开辟了一块标记区域:Native Method Stack【本地方法栈】 登记了 native 方法
         * 在最终执行的时候,加载本地方法库中的方法通过JNI
         * 
         * java程序驱动打印机,管理系统,掌握即可,在企业级应用中较为少见!
         */
        public native void hello();
    
        /**
         * 调用其他接口 : Socket; WebService; http;
         */
    }
    
    

    7、PC寄存器

    程序计数器:program counter register

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

    8、方法区

    Method Area 方法区

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

    [static]静态变量、[final]常量、[Class]类信息(构造方法、接口定义)、运行时的常量池存放方法去中,但是 实例变量存在堆内存中,和方法无关

    9、栈

    • 栈是数据结构

      程序 = 数据结构 + 算法

    • 栈===》先进后出 后进先出

      队列==》先进先出 后进后出(FIFO : first input first output)

    • 栈:栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放了,对于栈来说不存在垃圾回收,一旦线程结束,栈就Over了

    • 栈存储:八大基本类型 + 对象的引用(地址) + 实例方法

    • 栈运行原理:栈帧

    • 栈满了:StackOverflowError 【error虚拟机就停止了】
    • 栈 + 堆 + 方法区 :交互关系 【一个对象在内存中实例化的过程】

    10、三种JVM

    • Sun公司

      Java -version可以查看

      HotSpot Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)

    • BEA公司 JRockit

    • IBM公司 J9VM

    11、堆[重点]

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

    • 类加载器读取了类文件后,一般会把什么东西放到堆中呢? 类、方法、常量、变量--保存我们所有引用的真实对象

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

      • 新生区 (伊甸园)Young/New
      • 养老去old
      • 永久区

    12、新生区、老年区、永久区

    新生区

    • 一个类诞生、成长的地方,甚至死亡
    • 伊甸园区:所有的对象都是在伊甸园区new出来的
    • 幸存者区 0区 1区

    真理:经过研究,99%的对象都是临时对象!

    永久区

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

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

    • JDK1.6之前:永久代,常量池在方法区
    • JDK1.7:永久代,但是慢慢的退化了【去永久代,常量池在堆中】
    • JDK1.8之后:无永久代,常量池在元空间

    元空间:逻辑上存在,物理上不存在 新生区 + 老年代 = JVM maxMemory

    示例:vm options

    vm options: -Xms1024m -Xmx1024m -XX:+PrintGCDetails

    package test;
    
    public class Demo02 {
        public static void main(String[] args) {
            //返回虚拟机试图使用的最大内存
            long maxMemory = Runtime.getRuntime().maxMemory();//字节 1024 * 1024
            //返回JVM的初始化总内存
            long totalMemory = Runtime.getRuntime().totalMemory();
    
            System.out.println("maxMemory:"+maxMemory+"字节	"+maxMemory/(double)1024/1024+"MB");
            System.out.println("totalMemory:"+totalMemory+"字节	"+totalMemory/(double)1024/1024+"MB");
    
            /**
             * 默认情况下:分配的总内存 是电脑内存的1/4,而初始化的内存 是1/64
             */
    
            /**
             * 遇到OOM:
             * 解决方式:
             * 1、尝试将堆内存扩大 看结果
             * 2、分析内存,看一下那个地方出现问题(专业工具)
             *
             * 初识内存和总内存都设置为1024M 并且打印GC消息
             * -Xms1024m  -Xmx1024m -XX:+PrintGCDetails
             */
    
            /**
             *  305664K + 699392K = 1005056K (除1024)---》 981.5M
             */
        }
    }
    
    

    结果:

    maxMemory:1029177344字节	981.5MB
    totalMemory:1029177344字节	981.5MB
    Heap
     PSYoungGen      total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
      eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
      from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
      to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
     ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
      object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
     Metaspace       used 3235K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 354K, capacity 388K, committed 512K, reserved 1048576K
    
    Process finished with exit code 0
    

    13、堆内存调优

    问:

    在一个项目中,突然出现了OOM故障,那么该如何排除?研究为什么出错?

    • 能够看到代码第几行出错:内存快照分析工具 MAT Jprofiler
    • Debug:一行行分析代码

    MAT Jprofiler作用

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

    安装插件

    • File ----》settings ----》 Plugins ----》 搜Jprofiler ---》search in market ----> install

    • 安装完之后重启IDEA

    • 下载安装Jprofiler 自定义安装路径要没有中文和空格 建议下载9.2.1的可用下面注册码

    • 弹出的license information

      选enter license key ---》name company 随便选 ----》 注册码如下选一个

    L-Larry_Lau@163.com#23874-hrwpdp1sh1wrn#0620
    L-Larry_Lau@163.com#36573-fdkscp15axjj6#25257
    L-Larry_Lau@163.com#5481-ucjn4a16rvd98#6038
    L-Larry_Lau@163.com#99016-hli5ay1ylizjj#27215
    L-Larry_Lau@163.com#40775-3wle0g1uin5c1#0674
    
    • 重新打开IDEA File ---> settings --->Tools ---> JProfiler ---> JProfiler executable

      找到下载路径的bin目录下的.exe 之后点击Apply OK

    示例:jprofiler用法

    可以根据不同的错 dump出文件

    • -Xms :设置初始化内存大小 1/64
    • -Xmx :设置最大分配内存 1/4
    • -XX:+PrintGCDetails :打印GC垃圾回收信息
    • -XX:+HeapDumpOnOutOfMemoryError :OOM dump

    VM options: -Xms1m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError

    package test;
    
    import java.util.ArrayList;
    
    //Dump
    public class Demo03 {
    
        byte[] array = new byte[1024*1024];
    
        public static void main(String[] args) {
            ArrayList<Object> list = new ArrayList<>();
            int count = 0;
            //OOM
            try {
                while (true){
                    list.add(new Demo03());
                    count++;
                }
            } catch (Exception e) {
                System.out.println("count+"+count);
            }
            /**
             * Throwable
             *     Exception
             *     Error
             */
        }
    }
    
    

    结果:

    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid9696.hprof ...
    Heap dump file created [3514346 bytes in 0.045 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at test.Demo03.<init>(Demo03.java:8)
    	at test.Demo03.main(Demo03.java:16)
    
    Process finished with exit code 1
    

    先看大对象-----》看线程 能定位到第几行!!!

    14、GC:垃圾回收(自动进行)

    GC的作用区只有堆--绿色部分

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

    • 新生代
    • 幸存区 (from to两个区 会交换的区域~~ from变to to变from)
    • 老年区

    GC两种类型:

    • 轻GC [普通的GC]: 新生代和幸存区(偶尔幸存区满的话)
    • 重GC [全局GC]:全部清

    题目:

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

    引用计数法:

    复制算法:

    • 好处:没有内存碎片!
    • 坏处:浪费了内存空间(多了一半空间永远是空的to,假设对象100%存活(极端情况:from区中好多对象都复制到to中))

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

    标记清除法:

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

    标记清除压缩:

    对标记清除的优化~~~

    再次优化

    • 先标记清除几次-------》再进行压缩

    总结:

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

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

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

    • 思考?没有最优的JVM算法吗? ----木有~~ 没有最好的算法~·只有最合适的(看场景)

      所以---------GC : 分代收集算法

      • 年轻代:【存活率低】 复制算法
      • 老年代:【区域大、存活率高】标记清除+标记压缩 混合实现(JVM调优,清多少次在压等等)

    15、JMM Java内存模型

    Java Memory Model

    • JMM是干嘛的? 作用:缓存一致性,用于定义数据读写的规则(遵守这个规则)

      JMM定义了线程工作内存和主内存之间的抽象关系:线程之间共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)

    解决共享对象可见性这个问题:volilate [当线程更改了共享变量 会马上刷新到主内存,保证其他线程取时候是正确的]

    • 如何学习? 学volilate

    • JMM制定了一些规则:

      • 不允许read和load、store和write操作之一单独出现。即 使用了read必须load,使用了store必须write
      • 不允许线程丢弃它最近的assign操作,即 工作变量的数据改变了之后,必须告知主内存
      • 不允许一个线程将没有assign的数据从工作内存同步回主内存
      • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
      • 一个变量同一时间只有一个线程对其进行lock操作。多次lock后,必须执行相同次数的unlock才能解锁
      • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新assgin或load操作初始化变量的值
      • 如果一个变量没有被lock,就不能对其进行unlock操作,也不能unlock一个被其他线程锁住的变量
      • 对一个变量进行unlock之前,必须把此变量同步回主内存

      JMM对这八种操作规则和对volilate的一些特殊规则就能确定哪些操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以我们一般也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。

    补充:

    多看博客,多百度~~·加油!

    思维导图网站:https://www.processon.com/

  • 相关阅读:
    9.8
    9.6
    9.5
    树状数组
    逆序对
    tab标签切换(无炫效果,简单的显示隐藏)
    JQuery 的选择器
    简单的JQuery top返回顶部
    Hello Word!
    java Data 计算自己活了多少天
  • 原文地址:https://www.cnblogs.com/ls-summer/p/14131268.html
Copyright © 2011-2022 走看看