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快照分析异常对象,精准定位到类。

 

posted @ 2020-03-11 14:57  执笔人生  阅读(285)  评论(0编辑  收藏  举报