# jvm-test **Repository Path**: zhuhuijie/jvm-test ## Basic Information - **Project Name**: jvm-test - **Description**: jvm 学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-24 - **Last Updated**: 2021-05-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JVM(Java Virtual Machine) 学习笔记 ## 1 类生命周期 > 类的加载-》连接-》初始化-》使用-》卸载 1. 类的加载 查找并加载类的二进制数据(将生成.class文件加载到Jvm) 2. 连接 - 验证 .class文件正确性校验 - 准备 static静态变量分配内存,并初始化默认值 static int num = 10 这里会使 num = 0准备阶段只有类,没有对象 初始化:static、非static、构造 - 解析 把类中的符号引用(com.zhj.pojo.User代替User对象)转为直接引用(直接使用内存地址) 3. 初始化 static int num = 10; 这里会把num = 0, 变为num = 10 4. 使用 对象初始化、垃圾回收、对象销毁 5. 卸载 jvm 结束声明周期时机 - 正常结束 - 异常结束 - System.exit() - 操作系统异常 ## 2 JVM 内存模型(JMM[Java Memory Model]) > 用于定义变量(所有线程的共享变量,不能使局部变量)的访问规则 > 线程调用会使用工作内存,将主内存的数据拷贝一份到工作内存 分为两个区域 主内存区域、工作内存区域 ![线程访问内存](https://img-blog.csdnimg.cn/20210523152255605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poajUyNjY2,size_16,color_FFFFFF,t_70) - 线程不可用直接访问主内存与其他变量的内存 - 不同线程,可以通过主内存间接访问其他工作内存(线程安全问题,锁机制就是线程独占) 1. Lock:将主内存变量,设为线程独占状态 2. Read:将主内存读取到工作内存在 3. Load:将读取到的变量写为工作副本 4. Use:把工作副本变量拿去给线程使用 5. Assign:把线程正在使用的变量,传递工作内存中的变量副本中 6. Store:把工作内存中的值传递给主内存 7. Write:将变量副本作为主内存中的变量进行存储 8. Unlock: 解决线程独占状态 要求以上动作是原子性的,对于64位的数据类型(long、double)有些非原子性协议 ## 3 volatile 概念:JVM提高的轻量级同步机制 作用: 1. 防止JVM对long、double等64位的原子性协议进行误操作(读取半个) 2. 可以使变量对线程立即可见(修改了工作内存中的变量副本,会立即同步其他线程的工作内存) 3. 禁止指令的重排序 原子性 num = 10; 非原子性: - int num = 10; int num; num=10; - instance = new Singleton(); 1. JVM分配内存地址和内存空间 2. 使用构造实例化对象 3. instance = 分配的地址 重排序: 排序对象是原子性的(不是原子性的需要先转化成原子性)操作,目的是为了提高执行效率 单例会静止重排序,内存屏障 1. 写操作前StoreStore 2. 写操作后StoreLoad 3. 读操作前LoadLoad 4. 读操作后LoadStore **重排序不会影响单线程的执行结果** volatile 不能保证线程安全与原子性 ## 4 JVM 内存区域 线程私有(对应JMM的工作内存) - 程序技术器(Program Counter Register) 1. 指向当前线程所执行字节码指令的地址 2. 调用本地方法是undefined 3. 不会产生内存溢出异常的 - 虚拟机栈(VM Stack) 描述方法执行的内存模型 - 方法执行会在虚拟机栈加一个栈帧 - 栈帧中包含:方法的局部变量 操作数栈 - 本地方法栈(Native Method Stack) 线程共享(对应JMM的主内存) - 方法区 存放:类的元数据(描述类的信息)、常量池、方法信息(方法数据,方法代码) 工程: 类的元数据、常量池 方法区太多也会OOM 存放编译期间产生的 - 堆(heap) 1. 存放对象实例(对象,数组) 2. 存放对象实例虚拟机最大的一块 3. JVM启动就创建完成 4. GC主要管理区域 5. 本身是线程共享的。可以画出多个线程私有的缓冲区 6. 允许空间不连续 7. 对可以分为新生代,老生代 大小比例1:2 8. 新生代(eden:8 (s0:1 s1:1幸存者))大小比例 8:1:1 9. 新生代使用率90%,使用时只能使用一个eden 与 一块s 10. 新生代,存放生命周期比较短的对象、比较小的对象 对象大小设置-XX: PretenureSizeThredshold。一般而言,大对象一般是集合、数组、字符串 MinorGC回收新生代中的对象,没回收走第一次转移到区,再回收就年龄就加1,年龄到达一定数字就会转入老生代 新生代在使用时只能同时使用一个区,为了避免碎片产生 老生代:生命周期比较长的对象 2大的对象 新生代特点: - 大部分对象在新生代 - 新生代回收频率高,效率高 老生代特点: - 空间大 -增长速度慢 -频率低 意义:可以根据项目中对象大小的数量,调节比例 对象太多也可能导致异常 虚拟机参数 ```vm -Xms64m -Xmn32m -Xmx128m 总大小 java -X -Xmixed 混合模式执行(默认) -Xint 仅解释模式执行 -Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件> 设置引导类和资源的搜索路径 -Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件> 附加在引导类路径末尾 -Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件> 置于引导类路径之前 -Xdiag 显示附加诊断消息 -Xnoclassgc 禁用类垃圾收集 -Xincgc 启用增量垃圾收集 -Xloggc: 将 GC 状态记录在文件中(带时间戳) -Xbatch 禁用后台编译 -Xms 设置初始 Java 堆大小 -Xmx 设置最大 Java 堆大小 -Xss 设置 Java 线程堆栈大小 -Xprof 输出 cpu 分析数据 -Xfuture 启用最严格的检查,预计会成为将来的默认值 -Xrs 减少 Java/VM 对操作系统信号的使用(请参阅文档) -Xcheck:jni 对 JNI 函数执行其他检查 -Xshare:off 不尝试使用共享类数据 -Xshare:auto 在可能的情况下使用共享类数据(默认) -Xshare:on 要求使用共享类数据,否则将失败。 -XshowSettings 显示所有设置并继续 -XshowSettings:system (仅限 Linux)显示系统或容器 配置并继续 -XshowSettings:all 显示所有设置并继续 -XshowSettings:vm 显示所有与 vm 相关的设置并继续 -XshowSettings:properties 显示所有属性设置并继续 -XshowSettings:locale 显示所有与区域设置相关的设置并继续 ``` 注意:导致内存溢出的异常,除了虚拟机的4个区域以外,在NIO会使用直接内存 ## 5 类的使用方式 类的初始化:首次主动使用时,才会初始化 ### 主动使用 1. new 构造类的使用 2. 访问类的静态成员 特殊情况: - 有final 不会初始化,如果有final是随机数 会初始化 3. 反射使用的类 Class.forName(A.class.getName()); 主动使用 4. 初始化一个子类时 5. 动态代理在执行所涉及的类 ## 6 助记符 反编译 aload_0 装载了一个引用类型 invokespecial init 初始化代码位置 getstatic 获取静态成员 bipush -128软引用>弱引用>虚引用 > 四种引用的出处 强 new 软Soft弱Weak虚Phantom Reference ### 7.1 强引用 Object obj = new Object(); 强引用什么时候失效? 1. 生命周期结束(作用域失效) ```java class A { public void method() { Object obj = new Object(); } // 当方法执行完毕,强引用指向的new Object()就会等待被回收 } ``` 2. 设置为null obj = null; 除以上两种情况以外gc任何时候都不会回收强引用对象 ### 7.2 软引用 根据JVM内存情况,如果JVM内存不足。GC就会回收软引用对象 ### 7.3 弱引用 GC开始就会回收弱引用对象 ### 7.4 虚引用(幻影引用或幽灵引用) 是否使用虚引用和引用对象本身没有任何关系 无法通过虚引用来获取对象本身。 引用get() -> 引用对象 虚引用get() -> null 虚引用不会单独使用,一般和引用队列一起使用 价值: GC -> 回收 将虚引用放入引用队列中,出队时才会再回该对象 在对象被回收之前,进行一些额外的操作 特殊情况: 对象重写 finalize() 方法 // 虚引用会延迟入队 ### 7.5 引用的作用 > 使用引用失效缓存的淘汰策略 java - > 缓存(90%-60%)-> db(iphone) LRU 一般淘汰策略 根据容量/缓存个数+ LRU进行淘汰 在java中还可以用引用失效淘汰策略 ### 双亲委派机制 类加载: - 连接 static静态变量初始化默认值 - 初始化 给static变量赋正确的值 总结:初始化 一定要注意顺序问题 (静态变量 构造方法的顺序问题) 双亲委派:JVM自带的类加载器(在JVM的内部所包含)、用户自定义类加载器 JVM自带的加载器 - 根加载器 Bootstrap - 加载 jre\lib\rt.jar (包含了平时编写代码时 大部分jdk api);指定加载某一个jar( -Xbootclasspath=a.jar) - 扩展类加载器,Extension:C:\Java\jdk1.8.0_101\jre\lib\ext\\\*.jar ;指定加载某一个jar(-Djava.ext.dirs= ....) - AppClassLoader/SystemClassLoader,系统加载器(应用加载器):加载classpath;指定加载(-Djava.class.path= 类/jar) ![类加载](https://img-blog.csdnimg.cn/20210524094259154.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poajUyNjY2,size_16,color_FFFFFF,t_70) 用户自定义加载器(myLoader1 myLoader2) 双亲委派:当一个加载器要加载类的时候,自己先不加载,而是逐层向上交由双亲去加载;当双亲中的某一个加载器 加载成功后,再向下返回成功。如果所有的双亲和自己都无法加载,则报异常。 自定义加载器 二进制名binary names: ``` "java.lang.String" "javax.swing.JSpinner$DefaultEditor" "java.security.KeyStore$Builder$FileBuilder$1" "java.net.URLClassLoader$3$1" ``` $代表内部类: $数字:第几个匿名内部类 ``` The class loader for an array class, as returned by {@link* Class#getClassLoader()} is the same as the class loader for its element* type; if the element type is a primitive type, then the array class has no* class loader. ``` 1.数组的加载器类型 和数组元素的加载器类型 是相同 2.原声类型的数组 是没有类加载器的 如果加载的结果是null: 可能是此类没有加载器(int[]) , 也可能是 加载类型是“根加载器” ```

However, some classes may not originate from a file; they may originate* from other sources, such as the network, or they could be constructed by an* application. The method {@link #defineClass(String, byte[], int, int)* defineClass} converts an array of bytes into an instance of class* Class. Instances of this newly defined class can be created using* {@link Class#newInstance Class.newInstance}. ``` xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp) ```java

The network class loader subclass must define the methods {@link * #findClass findClass} and loadClassData to load a class * from the network. Once it has downloaded the bytes that make up the class, * it should use the method {@link #defineClass defineClass} to * create a class instance. A sample implementation is: * *

 *     class NetworkClassLoader extends ClassLoader {
 *         String host;
 *         int port;
 *
 *         public Class findClass(String name) {
 *             byte[] b = loadClassData(name);
 *             return defineClass(name, b, 0, b.length);
 *         }
 *
 *         private byte[] loadClassData(String name) {
 *             // load the class data from the connection
 *              . . .
 *         }
 *     }
```

如果class文件来自原Network,则加载器中必须重写findClas()和loadClassData().



自定义类加载器的实现

实现流程:
1.public class MyClassLoaderImpl  extends ClassLoader

2.findClass(String name){...} :直接复制文档中的NetworkClassLoader中的即可

3.loadClassData(String name){...} :name所代表的文件内容->byte[]

4.细节:

loadClassData(String name): 是文件形式的字符串a/b/c.class,并且开头out.production..

findClass(String name):是全类名的形式  a.b.c.class,并且开头 是: 包名.类名.class

```java
package com.zhj.jvm.test.classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author zhj
 */
public class MyClassLoaderImpl extends ClassLoader {

    // 默认是系统加载器
    public MyClassLoaderImpl() {
        super();
    }

    // 可以干预加载器
    public MyClassLoaderImpl(ClassLoader classLoader) {
        super(classLoader);
    }

    public Class findClass(String name) {
        System.out.println(name);
        byte[] b = loadClassData(name);
        if (b != null) {
            return defineClass(name, b, 0, b.length);
        }
        return null;
    }

    private byte[] loadClassData(String name) {
        name = dotToSplit("target.classes." + name ) + ".class";
        File file = new File(name);
        byte[] bytes = null;
        if (file.exists()) {
            FileInputStream fileInputStream = null;
            ByteArrayOutputStream outputStream = null;
            try {
                fileInputStream = new FileInputStream(file);
                outputStream = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len = -1;
                while ((len = fileInputStream.read(buf)) != -1) {
                    outputStream.write(buf, 0, len);
                }
                bytes = outputStream.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileInputStream != null) {
                        fileInputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bytes;
    }

    public static String dotToSplit(String className){
        return className.replace(".","/") ;
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoad = new MyClassLoaderImpl();
        Class myClass = myClassLoad.loadClass("com.zhj.jvm.test.classload.MyDefineCL");
        System.out.println(myClass.getClassLoader());
        System.out.println(myClass.getName());
        MyDefineCL o = (MyDefineCL) (myClass.newInstance());
        o.say();
    }
}
class MyDefineCL{
    public void say() {
        System.out.println("hello ,world");
    }
}

```

自定义加载 会委派给父类加载
如果加载一个不存在的类,父类都加载不了,就会自己加载,自己抛出异常

使用自己的类加载器
操作思路:

要先将 .class文件从classpath中删除,之后才可能用到 自定义类加载器;否在classpath中的.class会被 APPClassLoader加载

```java
package com.zhj.jvm.test.classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author zhj
 */
public class MyClassLoaderImpl extends ClassLoader {

    private String path = null;

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    // 默认是系统加载器
    public MyClassLoaderImpl() {
        super();
    }

    // 可以干预加载器
    public MyClassLoaderImpl(ClassLoader classLoader) {
        super(classLoader);
    }

    public Class findClass(String name) {
        System.out.println(name);
        System.out.println("findClass...");
        byte[] b = loadClassData(name);
        if (b != null) {
            return defineClass(name, b, 0, b.length);
        }
        return null;
    }

    private byte[] loadClassData(String name) {
        if (path != null) {
            name = path + name.substring(name.lastIndexOf(".") + 1) + ".class";
        } else {
            name = dotToSplit("target.classes." + name) + ".class";
        }
        File file = new File(name);
        byte[] bytes = null;
        if (file.exists()) {
            FileInputStream fileInputStream = null;
            ByteArrayOutputStream outputStream = null;
            try {
                fileInputStream = new FileInputStream(file);
                outputStream = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len = -1;
                while ((len = fileInputStream.read(buf)) != -1) {
                    outputStream.write(buf, 0, len);
                }
                bytes = outputStream.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileInputStream != null) {
                        fileInputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bytes;
    }

    public static String dotToSplit(String className){
        return className.replace(".","/") ;
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoaderImpl myClassLoad = new MyClassLoaderImpl();
        // Class myClass = myClassLoad.loadClass("com.zhj.jvm.test.classload.MyDefineCL");
        myClassLoad.setPath("E:\\data_file\\");
        Class myClass = myClassLoad.loadClass("com.zhj.jvm.test.classload.MyDefineCL");
        System.out.println(myClass.getClassLoader());
        System.out.println(myClass.getName());
        /*MyDefineCL o = (MyDefineCL) (myClass.newInstance());
        o.say();*/
    }
}


```

代码流程:

```
loadClass() -> findClass() -> loadClassData()
```

一般而言,启动类加载loadClass();

**实现自定义加载器,只需要:**

**1.继承 ClassLoader**

**2.重写 findClass()**

说明,类加载器 只会把同一个类 加载一次; 同一个class文件  加载后的位置

结论:

自定义加载器 加载.class文件的流程:

先委托APPClassLoader加载,APPClassLoader会在classpath中寻找是否存在,如果存在 则直接加载;如果不存在,才有可能交给 自定义加载器加载。

自定义一个与JDK内重名的类,在该类定义一个method01() 系统找不到自己写的方法method01()
根据双亲委派,越上层越先加载,JDK中没有这个方法

双亲委派可以防止自定义类与JDK类重名
```bash
存在继承关系

A.class:  classpath
B.class:   classpath
原因
同一个AppClassLoader 会同时加载A.class和B.class

--
A.class:   d:\

B.class:   classpath
原因
A.class:自定义加载器加载
B.class:被AppClassLoader加载
因此,加载A.class和B.class的不是同一个加载器


IllegalAccess
---
A.class:    classpath

B.class:    d:\
NoClassDefFoundError
原因:
A.class: 被AppClassLoader加载  
B.class: 自定义加载器加载
因此,加载A.class和B.class的不是同一个加载器

--
A.class	d:\
B.class d:\
TestMyClassLoader2 can not access a member of class com.yanqun.parents.A with modifiers "public"
A.class/B.class: 自定义加载器加载
原因是 main()方法所在类在 工程中(APPClassLoader),而A和B不在工程中(自定义加载器)。


造成这些异常的核心原因: 命名空间(不是由同一个类加载器所加载)


----
没有继承关系

X.class:  D:		自定义加载器
Y.class:  classpath	系统加载器

Y被加载了,加载器是: sun.misc.Launcher$AppClassLoader@18b4aac2
X被加载了,加载器是: com.yanqun.parents.MyClassLoaderImpl@74a14482


---

X.class:  classpath  系统加载器
Y.class:  D:	    自定义加载器

java.lang.NoClassDefFoundError: com/yanqun/parents/Y

--
```
如果存在继承关系: 继承的双方(父类、子类)都必须是同一个加载器,否则出错;

如果不存在继承关系: 子类加载器可以访问父类加载器加载的类(自定义加载器,可以访问到 系统加载器加载的Y类);反之不行(父类加载器 不能访问子类加载器)

核心: 双亲委派

如果都在同一个加载器 ,则不存在加载问题; 如果不是同一个,就需要双亲委派。

如果想实现各个加载器之间的自定义依赖,可以使用ogsi规范

![加载结构](https://img-blog.csdnimg.cn/20210524144030919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poajUyNjY2,size_16,color_FFFFFF,t_70)

OSGi:

1.网状结构的加载结构

2.屏蔽掉硬件的异构性。例如,可以将项目部署在网络上,可以在A节点上 远程操作B节点。在操作上,可以对硬件无感。也可以在A节点上 对B节点上的项目进行运维、部署,并且立项情况下  在维护的期间,不需要暂时、重启。


### 类的卸载

1.系统自带(系统加载器、扩展加载器、根加载器):这些加载器加载的类  是不会被卸载。

2.用户自定义的加载器,会被GC卸载GC