死锁
什么是死锁?
线程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)分类
- java虚拟机自带的加载器
- BootStrap 根加载器 (加载系统的包,如JDK核心库中的类:rt.jar中的类文件)
- Ext 扩展类加载器 (加载一些扩展jar包中的类)
- Sys/App 系统(应用类)加载器 (加载自己个人写 .java 代码)
- 用户自己定义的加载器
- 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快照分析异常对象,精准定位到类。