JVM性能篇

类加载机制

  • 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
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性能篇)