zoukankan      html  css  js  c++  java
  • Java 虚拟机

    https://www.bilibili.com/video/BV1yE41187A3?p=2

    JDK

    Java Development ToolKit

    Java Development ToolKit(Java开发工具包),包括了JRE,一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)

    Java Runtime Enviromental

    Java Runtime Enviromental(java运行时环境),与JDK相比,它不包含开发工具——编译器、调试器和其它工具,如javac

    Java Virtual Mechinal

    Java Virtual Mechinal(JAVA虚拟机),JVM是JRE的一部分,JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用

    Java 跨平台与C/C++的区别

    • c/c++: 源码跨平台,不同平台需要重新编译
    • Java:字节码跨平台,一次编译在不同平台只需要安装jvm即可运行字节码

    JVM生命周期

    • 启动

    启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点

    • 运行

    main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程

    • 销毁

    当程序中的所有非守护线程都终止时,JVM才退出

    JVM体系结构

    执行引擎

    执行字节码,或者执行本地方法

    类加载器

    作用:加载.class文件

    三种类加载器

    • AppClassLoader:应用类加载器

    负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包.放入这个目录下的jar包对AppClassLoader加载器都是可见的(因为ExtClassLoader是AppClassLoader的父加载器,并且Java类加载器采用了委托机制)

    • ExtClassLoader:扩展类加载器

    负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包.放入这个目录下的jar包对AppClassLoader加载器都是可见的(因为ExtClassLoader是AppClassLoader的父加载器,并且Java类加载器采用了委托机制)

    • BootstrapClassLoader:启动类加载器

    负责加载JDK中的核心类库,由c++来写的,加载的是Javahome/jre/lib/rt.jar等

    JVM什么时候加载.class文件

    1. new

    实例化对象时会触发类加载器去类加载对应的路径去查找对应的.class文件,并创建Class对象

    1. 反射

    反射时,加载字节码到内存后生产的只是一个Class对象,要得到具体的对象实例还需要使用Class对象的newInstance()方法来创建具体实例

    双亲委派机制

    ClassLoader源码

     protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            // 获取父类加载器直到父类为null
                            c = parent.loadClass(name, false);
                        } else {
                            // 使用根加载器加载
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // 如果父类都未找到,则交给自定义类加载器
                       c = findClass(name);
                    }
        }
    

    即委派给父类加载器(AppClassLoader =》 ExtClassLoader =》 BootstrapClassLoader)加载。这样就不允许用户串改jdk的源码,也保证了代码的安全

    • Class.getClassLoader():获取当前Class类的 类加载器
    public class ClassLoader {
    
        public static void main(String[] args) {
            ClassLoader myclass = new ClassLoader();
    
            Class<? extends ClassLoader> aClass = myclass.getClass();
    
            /**
             * 1. 类加载器收到加载请求,加载ClassLoader类
             * 2. AppClassLoader将这个请求委托给父类加载器去完成,一直向上委托,直到BootstrapClassLoader
             * 3. BootstrapClassLoader、ExtClassLoader都未找到ClassLoader
             * 4. 最终由AppClassLoader在用户目录找到该类,并完成加载
             */
            System.out.println(aClass.getClassLoader());  // AppClassLoader
    
            System.out.println(aClass.getClassLoader().getParent());  // ExtClassLoader
    
            System.out.println(aClass.getClassLoader().getParent().getParent()); // BootstrapClassLoader,返回null,java不可见
        }
    }
    

    自定义类加载器

    • 继承ClassLoader
    • 重写findClass方法,使用双亲委派模式,委托其父类去加载(因此需要删除父加载器工作目录中的class,让自定义加载器加载)
    • (重写loadClass:不使用双亲委派)

    重写findClass

    1. 获取.class字节码文件的字节数组
    2. this.defineClass将.class的字节数组转化为Class类实例,并返回
    public class MyClassLoad extends ClassLoader{
        // 字节码存储地址
        private String path;
    
        public MyClassLoad(String path) {
            this.path = path;
        }
    
    
    
        public MyClassLoad(ClassLoader parent) {
            super(parent);
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File file = new File(path);
    
            try {
                // 获取字节码文件的字节数组
                byte[] bytes = getClassBytes(file);
                // 将.class的字节数组转化为Class类实例
                Class<?> myclass = this.defineClass(name, bytes, 0, bytes.length);
                return myclass;
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        // 将文件转换为字节数组
        private byte[] getClassBytes(File file) throws IOException {
            // 获取.class 文件的字节流
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
    
            while (true)
            {
                int i = fc.read(by);
                if (i == 0 || i == -1)
                    break;
                by.flip();
                wbc.write(by);
                by.clear();
            }
    
            fis.close();
    
            return baos.toByteArray();
        }
    }
    

    测试

    • 需要删除父类加载器加载目录中的.class文件(双亲委派会使用父类进行加载)
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            MyClassLoad myClassLoad = new MyClassLoad("D:\Person.class");
            Class<?> aClass = myClassLoad.loadClass("com.reflect.Person");
            System.out.println(aClass.getClassLoader());
            Object o = aClass.newInstance();
    
        }
    

    Java内存区域(运行时数据区域)

    Java运行时数据区域和内存模型是不一样的东西,内存区域是指Jvm 运行时将数据分区域存储,强调对内存空间的划分,而内存模型定义了JVM 在计算机内存(RAM)中的工作方式

    Java堆

    Java堆(Java Heap)是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例

    堆内存划分

    • 年轻代

    主要是用来存放新生的对象。新生代又细分为 Eden区、SurvivorFrom区、SurvivorTo区
    新创建的对象都会被分配到Eden区(如果该对象占用内存非常大,则直接分配到老年代区), 当Eden区内存不够的时候就会触发MinorGC(Survivor满不会引发MinorGC,而是将对象移动到老年代中)
    Minor GC操作后,Eden区如果仍然存活(判断的标准是被引用了,通过GC root进行可达性判断)的对象,将会被移到Survivor To区。而From区中,对象在Survivor区中每熬过一次Minor GC,年龄就会+1岁,当年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置,默认是15)的对象会被移动到年老代中,否则对象会被复制到“To”区。经过这次GC后,Eden区和From区已经被清空

    • 老年代

    随着Minor GC的持续进行,老年代中对象也会持续增长,导致老年代的空间也会不够用,最终会执行Major GC(MajorGC 的速度比 Minor GC 慢很多很多,据说10倍左右)。Major GC使用的算法是:标记清除(回收)算法或者标记压缩算法
    当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常

    • 永久代(元空间)

    值得注意的是:元空间并不在虚拟机中,而是使用本地内存(之前,永久代是在jvm中)。这样,解决了以前永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出,毕竟是和老年代共享堆空间。java8后,永久代升级为元空间独立后,也降低了老年代GC的复杂度

    设置堆参数

    设置初始化大小为500M,最大可用内存为500M,并打印GC详细信息

    • -Xms:设置初始分配大小,默认为物理内存的“1/64”
    • -Xmx:最大分配内存,默认为物理内存的“1/4”
    VM options:-Xms500m -Xmx500m -XX:+PrintGCDetails
    

    在内存溢出时Dump文件

    • -XX:+HeapDumpOnXXX:在xx时生成dump文件
    -Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError
    

    设置年轻代阈值,到达此阈值进入老年代,默认15

    -XX:MaxTenuringThreshold=20
    

    虚拟机栈

    • 线程隔离
    • 每个方法都会产生一个栈帧(Stack Frame)
    • 每个栈帧中存储局部变量表、操作数栈、动态链接、方法出口等信息

    本地方法栈

    和虚拟机栈区别是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务

    public class Thread implements Runnable {
         ...
          
        /**
         * Thread类中的native方法
         * 测试是否有线程被中断
         */
        private native boolean isInterrupted(boolean ClearInterrupted);
    
        ...
    }
    

    元空间

    元空间用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,线程共享

    • JDK8之前,方法区的实现是永久代(Perm),JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之
    • 线程共享
    • 存储类的基本信息(运行时常量池、静态变量、接口信息等)

    在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
    在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

    运行时常量池
    运行时常量池(Runtime Constant Pool)是元空间的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    程序计数器

    程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

    • 线程私有
    • 占少量内存空间
    • 提供当前线程所执行的字节码的行号指示器,为了线程切换后能恢复到正确的执行位置

    * 直接内存

    直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。
    在 JDK 1.4 中新加入了 NIO,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

    • 可以通过 -XX:MaxDirectMemorySize 参数来设置最大可用直接内存,如果启动时未设置则默认为最大堆内存大小,即与 -Xmx 相同

    JVM参数

    参数 含义 默认值
    -Xms 初始堆大小 物理内存的1/64(<1GB)
    -Xmx 最大堆大小 物理内存的1/4(<1GB)
    -XX:NewSize 设置年轻代大小(for 1.3/1.4)
    -XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64
    -XX:MaxPermSize 设置持久代最大值 物理内存的1/4
    -Xss 每个线程的堆栈大小
    -XX:MaxTenuringThreshold 垃圾最大年龄
    -XX:+CollectGen0First FullGC时是否先YGC false
    -XX:+PrintGCDetails 打印GC详细

    Java 内存模型(JMM)

    参考:
    https://www.cnblogs.com/czwbig/p/11127124.html

  • 相关阅读:
    LinkedList源码解析
    HashMap源码解析
    HashMap和Hashtable区别
    arcgis api for js 之网络分析服务发布
    arcgis api for js 之发布要素服务
    arcis api for js 值 3.17 本地部署
    ArcGIS 产品体系结构
    layui select下拉框选项不显示
    windows10企业版2016长期服务版激活
    PHP常见的输出语句
  • 原文地址:https://www.cnblogs.com/xiongyungang/p/13649276.html
Copyright © 2011-2022 走看看