JVM 学习笔记

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])

用于定义变量(所有线程的共享变量,不能使局部变量)的访问规则
线程调用会使用工作内存,将主内存的数据拷贝一份到工作内存
分为两个区域 主内存区域、工作内存区域
线程访问内存

  • 线程不可用直接访问主内存与其他变量的内存
  • 不同线程,可以通过主内存间接访问其他工作内存(线程安全问题,锁机制就是线程独占)
  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大的对象
新生代特点:

  • 大部分对象在新生代
  • 新生代回收频率高,效率高
    老生代特点:
  • 空间大
    -增长速度慢
    -频率低
    意义:可以根据项目中对象大小的数量,调节比例
    对象太多也可能导致异常

虚拟机参数

-Xms64m 
-Xmn32m 
-Xmx128m 总大小

java -X
-Xmixed           混合模式执行(默认)
-Xint             仅解释模式执行
-Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件>
                      设置引导类和资源的搜索路径
-Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
-Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
-Xdiag            显示附加诊断消息
-Xnoclassgc        禁用类垃圾收集
-Xincgc           启用增量垃圾收集
-Xloggc:<file>    将 GC 状态记录在文件中(带时间戳)
-Xbatch           禁用后台编译
-Xms<size>        设置初始 Java 堆大小
-Xmx<size>        设置最大 Java 堆大小
-Xss<size>        设置 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<x<127 8位带符号整数
sipush 16位 去掉bipush
无论是定义int 或者 short等
特殊情况 0-5 iconst_(0-5) -1 是iconst_m1
ldc : int float String 常量 ldc2_w long double

7 JVM四种引用级别

Object obj = new Object(); --强引用
根据引用的强弱关系: 强引用>软引用>弱引用>虚引用

四种引用的出处 强 new 软Soft弱Weak虚Phantom Reference

7.1 强引用

Object obj = new Object();
强引用什么时候失效?

  1. 生命周期结束(作用域失效)
class A {
    public void method() {
        Object obj = new Object();
    } // 当方法执行完毕,强引用指向的new Object()就会等待被回收
}
  1. 设置为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中还可以用引用失效淘汰策略

8 双亲委派机制

类加载:

  • 连接 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)
    类加载
    用户自定义加载器(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[]) , 也可能是 加载类型是“根加载器”

<p> 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)* <tt>defineClass</tt>} 
 converts an array of bytes into an instance of class* <tt>Class</tt>. 
 Instances of this newly defined class can be created using* 
 {@link Class#newInstance <tt>Class.newInstance</tt>}.

xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp)

<p> The network class loader subclass must define the methods {@link
 * #findClass <tt>findClass</tt>} and <tt>loadClassData</tt> 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 <tt>defineClass</tt>} to
 * create a class instance.  A sample implementation is:
 *
 * <blockquote><pre>
 *     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
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }

如果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

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加载

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类重名

存在继承关系

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规范

加载结构

OSGi:

1.网状结构的加载结构

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

类的卸载

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

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

9 JVM监测工具

jps: 查看Java进程 (java命令)

jstat:只能查看当前时刻的内存情况;可以查看到 新生代、老年代中的内存使用情况

jmap:查看堆内存的占用情况;也可以执行dump操作

jconsole:图形的监控界面

​ 例如:如果通过jconsole中的"执行gc"按钮发现 GC回收的内存太少,就说明当前进程是存在问题的(至少是可以被优化的)

jvisualvm: 监视 - 堆Dump -查找最大对象,从中可以发现 当前进程中是哪个对象 占据了最大的内存,从而对这个对象进行分析。

通过VM参数实现: 当内存溢出时,自动将溢出时刻的内存dump下来。

-Xmx100m
-Xms100m
-XX:+HeapDumpOnOutOfMemoryError

10 GC调优

Java开发者为什么不把所有的参数调到最优?非得让我们手工去调?

取舍。

调优实际是是一种取舍,以xx换xx的策略。因此在调优之前,必须明确方向:低延迟?高吞吐量呢?

有两种情况需要考虑:

1.在已知条件相同的前提下, 牺牲低延迟 来换取 高吞吐量,或者反之。

2.随着软件硬件技术的发展,可能 二者都升高。

GC的发展:

JVM自身在GC上进行了很多次的改进升级:

JVM默认的GC: CMS GC(在jdk9以后被逐步废弃) -> G1 GC(jdk9) -> Z GC(jdk11)

  • Serial GC:

    串行GC,是一种在单核环境下的串行回收器。当GC回收的时刻,其他线程必须等待。一般不会使用。

  • Parallel GC:

    在Serial 的基础上,使用 了多线程技术。 提高吞吐量。

  • CMS GC

    CMS使用了多线程技术,使用“标记-清除”算法,可以极大提升效率 (尤其在低延迟上有很大的提升)。繁琐,参数太多,对开发者的经验要求太高。

  • G1 GC

    jdk9开始使用的默认GC。特点:将堆内存划分为很多大小相等region,并会对这些区域的使用状态进行标记。以便GC在回收时,能够快速的定位出哪些region是空闲的,哪些是有垃圾对象,从而提升GC的效率。G1使用的算法是“标记-整理”算法。

  • Z GC

    jdk11开始提供全新的GC。回收TB级别的垃圾 在毫秒范围。

如果从生命周期角度划分,GC也可以划分成:Minor GC,和Full GC

  • Minor GC:回收新生代中的对象
  • Full GC:回收整个堆空间(新生代、老年代)

案例:

如果通过监测工具发现: Minor GC和Full GC都在频繁的回收,如何优化?

Minor GC为什么会频繁执行?因为 新生代中的对象太多了 Minor GC->短生命周期的对象太多了->造成逃逸到老年代中的对象越多-> 新生代多+老年代多->Full GC

Minor GC:可以尝试调大 新生代的最大空间

再调大 新生代晋升到老年代的阈值,从而降低 短生命周期的对象 从新生代转移到老年代的概率。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值