• JVM 探究(上)


    死锁

      什么是死锁?

        线程A持有资源A,想要资源B。线程B持有资源B,想要资源A,如此便会死锁。

     1 // 面对死锁你该怎么办?
     2 // 日志
     3 // 查看堆栈信息! JVM 的知识
     4 // 1、获取当前运行的java进程号   jps   -l
     5 // 2、查看信息                  jstack 进程号
     6 // 3、jconsole 查看对应的信息!(可视化工具!)
     7 // ......
     8 public class DeadLockDemo {
     9     public static void main(String[] args) {
    10         String lockA = "lockA";
    11         String lockB = "lockB";
    12 
    13         new Thread(new MyLockThread(lockA,lockB),"T1").start();
    14         new Thread(new MyLockThread(lockB,lockA),"T2").start();
    15     }
    16 }
    17 
    18 class MyLockThread implements Runnable {
    19     private String lockA;
    20     private String lockB;
    21 
    22     public MyLockThread(String lockA,String lockB){
    23         this.lockA = lockA;
    24         this.lockB = lockB;
    25     }
    26 
    27     @Override
    28     public void run() {
    29         synchronized (lockA) {
    30             System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"-->get:"+lockB);
    31             try {
    32                 TimeUnit.SECONDS.sleep(2L);
    33             } catch (InterruptedException e) {
    34                 e.printStackTrace();
    35             }
    36             synchronized (lockB) {
    37                 System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"-->get:"+lockA);
    38             }
    39         }
    40     }
    41 }

    JVM虚拟机

    类加载器 (class loader)

    加载:查找并加载类的二进制数据

    链接:

    • 验证:保证被加载的类的正确性
    • 准备:给类静态变量分配内存空间,赋值一个默认的初始值
    • 解析:把类中的符号引用转换为直接引用 
      • 在把 .java 文件 编译为 .class 文件的时候,虚拟机并不知道所引用的地址; 助记符,符号引用;
      • 转为真正的直接引用,找到对应的直接地址!

    初始化:给类的静态变量赋值正确的值

     1 public class Test {
     2     private static int a = 1;
     3 }
     4 
     5 // 1. 加载  编译为 .class文件,通过类加载,加载到JVM
     6 
     7 // 2. 链接
     8 //      验证:保证 .class 文件没有问题
     9 //      准备:给int类型分配内存空间,a=0
    10 //      解析:符号引用转为直接引用
    11 
    12 // 3.初始化  把 1 赋值给 a , 此时a=1

    static加载分析:

     1 // JVM参数:
     2 // -XX:+TraceClassLoading  // 用于追踪类的加载信息并打印出来
     3 // 分析项目启动为什么很慢,可以用上述参数检查类加载情况(比如是不是上来就加载了很多第三方类)
     4 // rt.jar  jdk 出厂自带的,最高级别的类加载器加载的
     5 public class Demo2 {
     6     public static void main(String[] args) {
     7         System.out.println(MyParent2.str2);
     8     }
     9 }
    10 
    11 class MyParent1 {
    12     public static String str1 = "hello,str1";
    13 
    14     static {
    15         System.out.println("MyParent1...");
    16     }
    17 }
    18 
    19 class MyParent2 extends MyParent1 {
    20     public static String str2 = "hello,str2";
    21 
    22     static {
    23         System.out.println("MyParent2...");
    24     }
    25 }

    final 加载分析:

     1 public class Demo3 {
     2     public static void main(String[] args) {
     3         // final 在编译的时候 在常量池
     4         // 这个代码中编译的时候,将str这个常量放在了Demo3中,
     5         // 此后Demo3和MyParent03并没有关系,即Demo3并不存在MyParent03调用str
     6 //        System.out.println(MyParent03.str);
     7 
     8         System.out.println(MyParent03.str1);
     9     }
    10 }
    11 
    12 class MyParent03 {
    13     public static final String str = "hello,world";
    14     public static final String str1 = UUID.randomUUID().toString().substring(1,3);
    15     static {
    16         System.out.println("MyParent02...");
    17     }
    18 }

    类加载器(class loader)分类

    1. java虚拟机自带的加载器
      1. BootStrap 根加载器 (加载系统的包,如JDK核心库中的类:rt.jar中的类文件)
      2. Ext       扩展类加载器  (加载一些扩展jar包中的类)
      3. Sys/App    系统(应用类)加载器 (加载自己个人写 .java 代码)
    2. 用户自己定义的加载器
      1. Classloader,只需要继承这个抽象类即可,自定义自己的类加载器 (了解即可)
     1 public class Demo1 {
     2     public static void main(String[] args) {
     3         Object o = new Object();
     4         // 输出null 在这里并不代表没有,只是Java触及不到!
     5         // 因为根加载器是由c、c++写的
     6         System.out.println(o.getClass().getClassLoader());
     7         
     8         Demo1 demo1 = new Demo1();
     9 
    10         Class<? extends Demo1> aClass = demo1.getClass();
    11         // class com.coding.classloader.Demo1
    12         System.out.println(aClass);
    13 
    14         ClassLoader classLoader = aClass.getClassLoader();
    15         // sun.misc.Launcher$AppClassLoader@18b4aac2
    16         System.out.println(classLoader);
    17 
    18         ClassLoader classLoader2 = classLoader.getParent();
    19         // sun.misc.Launcher$ExtClassLoader@1540e19d
    20         System.out.println(classLoader2);
    21 
    22         ClassLoader classLoader3 = classLoader2.getParent();
    23         // null
    24         System.out.println(classLoader3);
    25 
    26 // 思考:为什么自己写的java.lang.String 不能运行?
    27         
    28 // 双亲委派机制 可以保证核心类不被自己写的类破坏
    29 // 双亲委派机制:一层一层的让父类去加载,如果父类不能加载,在往下类推
    30 
    31 // Demo1
    32 // AppClassLoader   03
    33 // ExtClassLoader   02
    34 // BootStrap        01  最顶层加载器
    35     }
    36 }

    程序计数器 (Program counter) (了解即可)

      每个线程都有一个私有的程序计数器

    (重点)方法区 (别名:非堆)

      Method Area 方法区 是Java虚拟机规范中定义的运行时数据区域,和堆(heap)一样可以在线程之间共享!

      JDK1.7之前:

        永久代:用于存储一些虚拟机加载类信息,常量,字符串,静态变量等等。。。这些东西都会放到永久代中;

        永久代大小空间是有限的:如果满了,就会OOM (OutOfMemoryError:)

      JDK1.8之后

        彻底将永久代移除 HotSpot JVM ,Java Heap中或者Meta space (Native Heap)元空间;

        元空间就是方法区在HotSpot JVM的实现;

        方法区重要就是来存:类信息,常量,字符串,静态变量,符号引用,方法代码。。。

        元空间和永久代,都是对JVM规范中方法区的实现。

        元空间和永久代最大的区别:元空间并不在Java虚拟机中,使用的是本地内存

    (重点)栈Stack

      栈的优势:存取速度比堆快,仅次于寄存器;栈的数据是不可以共享的

      程序的运行其实是压栈的过程;

      栈中一定不存在垃圾,线程一旦结束,该栈生命周期也会结束,即和线程生命周期一致

      栈存什么:本地变量表,基本数据类型,对象引用,,,

      我们这个栈主要是 HotSpot (指针)

    问题:你认识几种JVM?(3种)

    • HotSpot     SUN公司
    • JRockit         BEA公司
    • J9VM            IBM 公司

    (必须要会)堆 Heap

      Java 7之前:

        Heap堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。

        可以存的内容:类,方法,常量,保存了类型引用的真实信息;

        分为三个部分:

        • 新生区:Young
        • 养老区:Old Tenure
        • 永久区:Perm  

          堆内存在逻辑上分为三个部分:新生,养老,永久(JDK1.7之后,叫元空间)

              物理上分为:新生,养老;永久代(元空间)在本地内存中,不在JVM中

          GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC (轻量级的GC) 和 Full GC (重量级的GC),如果堆满了,就会爆出OOM

      新生区

        新生区 就是一个类诞生,成长,消亡的地方!

        新生区细分:Eden,s0 / s1,所有的类Eden被new 出来的,慢慢的当Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级的GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区,......假设清理了20次之后,出现了极其顽强的对象,有些对象突破了,15次的垃圾回收!这时候就会将这个对象送入养老区!  运行了几个月之后,养老区满了,就会触发一次 Full GC;  假设项目上线一年后,整个空间彻底的满了,系统就会OOM;排除OOM问题,或者重启...   

      永久区

        放一些JDK自身携带的Class ,interface的元数据;几乎不会被垃圾回收;

        JDK1.6之前,有永久代,常量池在方法区;

        JDK1.7,有永久代,但是开始尝试去永久代,常量池在堆中;

        JDK1.8之后,永久代就没有了,取而代之的是元空间;常量池在元空间中;

      养老区

        15次都幸存下来的对象进入养老区,养老区满了之后,触发Full GC

        默认是15次,可以修改!

      堆内存调优(入门级)

        我的环境:HotSpot 、jdk1.8;

     1 /*
     2  * 默认情况:
     3  * maxMemory:1796.0MB    (虚拟机试图使用的最大内存量  一般是物理内存的1/4)
     4  * totalMemory:123.0MB   (虚拟机默认使用的内存总量   一般是物理内存的1/64)
     5  */
     6 // 我们可以自定堆内存的总量
     7 // -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
     8 // -Xmx: 最大分配的内存 1/4
     9 // -Xmx: 初始化分配的内存大小 1/64
    10 // VM命令 -Xmx1024m -Xms1024m -XX:+PrintGCDetails
    11 public class HeapDemo1 {
    12     public static void main(String[] args) {
    13         // 获得堆内存的最大大小 和 初始大小
    14         long maxMemory = Runtime.getRuntime().maxMemory();
    15         long totalMemory = Runtime.getRuntime().totalMemory();
    16 
    17         System.out.println("maxMemory="+maxMemory+"字节、"+(maxMemory/1024/(double)1024)+"MB");
    18         System.out.println("totalMemory="+totalMemory+"字节、"+(totalMemory/1024/(double)1024)+"MB");
    19     }
    20 }
     1 /*
     2 * VM命令 -Xmx1024m -Xms1024m -XX:+PrintGCDetails
     3 *
     4 * 分析GC日志:[PSYoungGen: 2037K->499K(2560K)] 2037K->727K(9728K), 0.0087646 secs]
     5 *          [Times: user=0.00 sys=0.00, real=0.01 secs]
     6 * GC类型: GC 普通的GC    Full GC 重量级的GC
     7 * 2037K GC前的大小,499K GC后的大小,
     8 * (2560K)当前YoungGen的total大小,0.0087646 secs GC用时,
     9 * user 占用CPU的时间,sys OS调用等待的时间,real 应用暂停的时间
    10  */
    11 public class HeapDemo2 {
    12     public static void main(String[] args) {
    13         String str = "hello,Heap";
    14         while (true) {
    15             str += str +
    16                     new Random().nextInt(999999999)
    17                     +new Random().nextInt(999999999);
    18         }
    19         // java.lang.OutOfMemoryError: Java heap space
    20     }
    21 }

    Dump 内存快照

      在java程序运行的时候,想测试运行的情况,即可使用一些工具来查看;

        工具:jconsole.exe;  idea debug;  IDEA(Jprofile性能瓶颈分析插件)  Eclipse(MAT插件);

     1 /*
     2 * Jprofiler 快速体验
     3 * 安装 配置 激活...
     4 *
     5 * -Xmx8m -Xms8m -XX:+HeapDumpOnOutOfMemoryError
     6 * */
     7 public class HeapDemo4 {
     8     private byte[] bytes = new byte[1*1024*1024]; // 1MB
     9 
    10     public static void main(String[] args) {
    11         List<HeapDemo4> list = new ArrayList();
    12 
    13         int count = 0;
    14 
    15         try {
    16             while (true) {
    17                 list.add(new HeapDemo4());
    18                 count++;
    19             }
    20         } catch (Throwable e) {// error 和 Exception 平级
    21             System.out.println("count="+count);
    22             e.printStackTrace();
    23         }
    24 // java.lang.OutOfMemoryError: Java heap space
    25     }
    26 }

    谈谈在工作中如何排查OOM?

        1. 运行前的操作

        2. 监控

      通过Dump快照分析异常对象,精准定位到类。

  • 相关阅读:
    python字符串方法
    字符串格式化示例
    python中的list()函数和tuple()函数
    python中sort()方法的cmp参数
    条件/三元操作符
    html5 frameset5内嵌框架集
    Sublime Text3取消自动补全结束标签
    Python列表:元素的修改、添加、删除和排序
    SCOI2010 股票交易
    Codeforces 797 D. Broken BST
  • 原文地址:https://www.cnblogs.com/ShallowPen/p/12449627.html
Copyright © 2020-2023  润新知