美文网首页
JVM性能篇

JVM性能篇

作者: 依弗布德甘 | 来源:发表于2020-02-05 12:03 被阅读0次

    类加载机制

    • JVM用类存储加载的类信息、常量、静态变量、编译后的代码等数据
    • 虚拟机规范中这是一个逻辑区划,具体实现根据不同虚拟机来实现
      如:oracle的HotSpot在java7中方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个区域进行管理
    class加载

    类生命周期

    从加载到卸载
    1. 加载 读取二进制内容
    2. 验证 验证class文件格式规范、语义分析、引用验证、字节码验证
    3. 准备 分配内存、设置类static修饰的变了初始值
    4. 解析 类、接口、字段、类方法等解析
    5. 初始化 为静态变了赋值、执行静态代码块
    6. 使用 创建实例对象
    7. 卸载 从JVM方法区中卸载
    类生命周期

    类加载器

    • 类加载器负责装入类,搜索网络、jar、zip、文件夹、二进制数据、内存等指定位置的类资源

    一个java程序运行,最少有三个类加载器实例,负责不同的加加载

    类加载器
    1. Bootstrap Loader 核心类库加载器
      • C/C++实现,无对应java类:null
      • 加载JRE_HOME/jre/lib目录,或用户配置的目录
      • JDK核心类库 rt.jar ... String..
    2. Extension Class Loader 拓展类库加载器
      • 加载JRE_HOME/jre/lib/ext目录,JDK拓展包,或用户配置的目录
    3. Application Class Loader 用户应用程序加载器
      • 加载java.class.path指定的目录,用户应用程序class-path 或者java命令运行时参数 -cp..

    相关问题引导

    • 查看类对应的加载器
    • JVM如何指定我们的类在何方
    • 类不会重复加载
    • 类的卸载
    • 双亲委派模型

    查看类对应的加载器

    /**
     * 查看类的加载器实例
     */
    public class ClassLoaderView {
        public static void main(String[] args) throws Exception {
            // 加载核心类库的 BootStrap ClassLoader
            System.out.println("核心类库加载器:"
                    + ClassLoaderView.class.getClassLoader().loadClass("java.lang.String").getClassLoader());
            // 加载拓展库的 Extension ClassLoader
            System.out.println("拓展类库加载器:" + ClassLoaderView.class.getClassLoader()
                    .loadClass("com.sun.nio.zipfs.ZipCoder").getClassLoader());
            // 加载应用程序的
            System.out.println("应用程序库加载器:" + ClassLoaderView.class.getClassLoader());
    
            // 双亲委派模型 Parents Delegation Model
            System.out.println("应用程序库加载器的父类:" + ClassLoaderView.class.getClassLoader().getParent());
            System.out.println(
                    "应用程序库加载器的父类的父类:" + ClassLoaderView.class.getClassLoader().getParent().getParent());
        }
    }
    

    JVM如何指定我们的类在何方

    jvm 命令 查看类加载信息由上到下
    jcmd :查看有哪些进程
    jcmd -help : 帮助
    jcmd pid (pid 是进程ID) help : 查看找执行当前进程的命令
    jcmd pid VM.system_properties :查看属性配置

    类不会重复加载

    • 在循环外面加载一次后,不会再加载
    • 循环里面会加载-因为循里面Loader每次都是新的
    import java.net.URL;
    import java.net.URLClassLoader;
    
    /**
     * 指定class 进行加载e
     */
    public class LoaderTest {
        public static void main(String[] args) throws Exception {
            URL classUrl = new URL("file:D:\\");//jvm 类放在位置
    
            // URLClassLoader loader  = new URLClassLoader(new URL[]{classUrl});
    
            while (true) {
                // 创建一个新的类加载器
                URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
    
                // 问题:静态块触发
                Class clazz = loader.loadClass("HelloService");
                System.out.println("HelloService所使用的类加载器:" + clazz.getClassLoader());
    
                Object newInstance = clazz.newInstance();
                Object value = clazz.getMethod("test").invoke(newInstance);
                System.out.println("调用getValue获得的返回值为:" + value);
    
                Thread.sleep(3000L); // 1秒执行一次
                System.out.println();
    
                //  help gc  -verbose:class
                newInstance = null;
                loader = null;
            }
            // System.gc();
        }
    }
    

    类的卸载

    上述代码中 System.gc(); 为卸载类操作;loader需要放置在循环内部才会触发卸载

    双亲委派模型

    为了避免重复加载,由下到上逐级委托,由上到下逐级查找

    1. 首先不会自己去尝试加载类,而是把这个请求委派给父加载器去完成;
    2. 每一层次的加载器都是如此,因此所有的类加载请求都会传给上层的启动类加载器
    3. 只有当父加载器反馈自己无法完成该加载请求时,子加载器才会尝试自己去加载
    import java.net.URL;
    import java.net.URLClassLoader;
    
    /**
     * 双亲委派机制
     */
    public class LoaderTest1 {
        public static void main(String[] args) throws Exception {
            URL classUrl = new URL("file:D:\\");
            // 测试双亲委派机制
            // 如果使用此加载器作为父加载器,则下面的热更新会失效,因为双亲委派机制,HelloService实际上是被这个类加载器加载的;
            URLClassLoader parentLoader = new URLClassLoader(new URL[]{classUrl});
    
            while (true) {
                // 创建一个新的类加载器,它的父加载器为上面的parentLoader
                URLClassLoader loader = new URLClassLoader(new URL[]{classUrl}, parentLoader);
    
                Class clazz = loader.loadClass("HelloService");
                System.out.println("HelloService所使用的类加载器:" + clazz.getClassLoader());
                Object newInstance = clazz.newInstance();
                Object value = clazz.getMethod("test").invoke(newInstance);
                System.out.println("调用getValue获得的返回值为:" + value);
    
                // help gc
                newInstance = null;
                value = null;
    
                System.gc();
                loader.close();
    
                Thread.sleep(3000L); // 1秒执行一次
                System.out.println();
            }
        }
    }
    

    垃圾回收机制

    内存回收-标记

    内存回收第一步需要标记,标记哪些是需要被回收的内存

    不同类型内存的判断机制

    • 对象回收-引用计数
    • 对象回收-可达性分析
    • 方法区回收
    标价需要回收的内存

    可达性分析算法
    将对象及其引用关系看做一个图,选定活动的对象作为GC Roots
    每个GC Root 都会去检测内存中是否还存在引用

    可达性分析算法

    引用行和可达性级别

    引用类型

    1. 强引用(StrongReference):最长久的普通对象引用,只要还有强引用指向一个对象,就不会回收
    2. 软引用(SoftReference):JVM认为内存不足时,才会去试图回收软引用指向的对象。(缓存场景)
    3. 弱引用(WeakRefrence):虽然是引用,但随时可能被回收
    4. 虚引用(PhantomReference):不能通过它访问对象。供了对象呗finalize以后,执行指定逻辑的机制(Clenaner)

    可达性级别

    1. 强可达(Strongly Reachable):一个对象可以有一个或多个线程可以不通过各种引用访问到的情况
    2. 软可达(Softly Reachable):就是当我们只能通过软引用才能访问到的对象状态
    3. 弱可达(Weakly Reachable):只能通过弱引用访问时的状态。当弱引用被清除的时候,就符合销毁条件
    4. 幻想可达(Phantom Reachable):不存在其他引用,并且finalize过了,只有幻想引用指向这个对象
    5. 不可达(unreachable):意味着对象可以被清除了

    垃圾收集算法

    • 标记-清除(Mark-Sweep)算法
      手续标识出所有要回收的对象,然后进行清除;标记清除过程效率有限,并有内存碎片化问题,不适合特别大的堆;

    • 复制(Copying)算法
      划分两块同等大小的区域,收集时将活着的对象复制到另一块区域,拷贝过程顺序放置,可避免内存碎片化问题;复制+预留内存,有一定的消耗浪费资源

    • 标记-整理(Mark-Compact)
      类似于标记-清除,但为了避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间


    分代收集

    根据对象存活周期,将内存划分为几个区域,不同区域采用适合的垃圾收集算法

    新的对象会分配到Eden,如果超过-XX:PretenureSizeThreshold:设置大对象直接进入老年代的阀值

    • 新生代
      新生代有3个区域;Eden与Form采用复制算法;To采用标记-整理算法;每次执行算法后会记录内存的等级,等级到达一定会进入老年代
    • 老年代
      老年代采用标记-整理算法;大对象会直接进入到老年代;
      分代收集

    垃圾收集器

    • 串行收集器
      单个线程来执行垃圾回收,新生代 与 老年代
    串行收集
    • 并行收集器
      与串行收集器的区别就是它是采用多线程执行的,并且可以设置GC时间和吞吐量等值

    • 并发收集器
      专用老年代,基于标记-清除算法

    并发收集
    • JDK9后默认为并发收集器 G1 -XX:+UseG1GC
      可以有效的避免内存随便。新生代老年代找不到大内存时执行的FullGC
    G1

    垃圾收集器组合

    • 目前JVM默认的垃圾收集器组合为
      新生代-PrallelScavenge + 老年代-Parallel Old

    • 通常的调优采用方式:
      新生代-ParNew + 老年代-CMS


    JDK内置命令工具

    • javap 主要用于根据Java字节码文件反编译Java源代码文件
      javap <options><classes>
    javap
    • jstack能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况
    • jstat
    jstat
    • jcmd 可代替jps工具查看本地的jvm信息
    jcmd
    • jinfo 查看运行中jvm的全部参数,还可以设置部分参数
    jinfo
    • jmap 打印出java内存中的Object的情况;或将JVM中的堆以二进制输出成文本
    jmap

    Jconsole 工具

    JDK自带工具,在java JDK bin 目录下

    jconsole

    Jvisualvm 工具

    JDK自带工具,在java JDK bin 目录下,相比Jconsole更加灵活,可安装插件

    Jvisualvm Jvisualvm-GC

    相关文章

      网友评论

          本文标题:JVM性能篇

          本文链接:https://www.haomeiwen.com/subject/khzlxhtx.html