JVM JVM四种引用 强引用 对象引用直接new
1 Object obj = new Object();
强引用对象失效
1 2 3 4 public void method () { Object object = new Object() ; }
/当方法执行完毕后,强引用指向的 引用而对象new Object()就会等待被GC回收
没有任何引用指向new Object() 因此,new Object() 就会等待被GC回收
除了以上两个情况以外,其他任何时候GC都不会回收强引用对象
软引用 如果内存充足,GC不会随便的回收软引用对象;如果JVM内存不足,则GC就会主动的回收软引用对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public static void main (String[] args) throws Exception { SoftReference<Object> softReference = new SoftReference<>(new Object()); List<byte []> list = new ArrayList<>(); new Thread( () ->{ while (true ){ try { Thread.sleep(10 ); } catch (InterruptedException e) { throw new RuntimeException(e); } if (softReference.get() == null ) { System.out.println("softReference 软引用对象已经被回收了" ); System.exit(0 ); } } },"线程1" ).start(); while (true ){ if (softReference.get() != null ){ list.add(new byte [1024 * 1024 ]); } } } }
来自java.lang.ref.SoftReference
get()
返回 所引用的对象
弱引用 只要GC执行,就会将弱引用对象进行回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class WeakReferenceTest { public static void main (String[] args) { WeakReference<Object> weakReference = new WeakReference<>(new Object()); System.out.println(weakReference.get() == null ? "已被回收" : "没被回收" ); System.gc(); System.out.println(weakReference.get() == null ? "已被回收" : "没被回收" ); } }
来自java.lang.ref.WeakReference
虚引用 是否使用虚引用,和引用对象本身没有任何关系,无法通过虚引用来获取对象本身
来自 java.lang.ref.PhantomReference
虚引用不会单独使用,一般会和 引用队列(java.lang.ref.ReferenceQueue
)一起使用
流程 当gc回收一个对象,如果gc发现 此对象还有一个虚引用,就会将虚引用放入到 引用队列中,之后(当虚引用出队之后)再去回收该对象。因此,我们可以使用 虚引用+引用对象 实现:在对象被gc之前,进行一些额外的其他操作
GC –>如果有虚引用–>虚引用入队–>虚引用出队–> 回收对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class PhantomReferenceTest { public static void main (String[] args) throws Exception { W w = new W(); ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference<W> phantomReference = new PhantomReference<>(w,referenceQueue); w = null ; System.gc(); Thread.sleep(10 ); System.out.println(referenceQueue.poll()); } } class W {}
虚引用对象重写了finalize(),那么JVM会延迟 虚引用的入队时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class PhantomAndFinalize { public static void main (String[] args) throws Exception { E e = new E(); ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference<W> phantomReference = new PhantomReference<>(e,referenceQueue); e = null ; System.gc(); Thread.sleep(10 ); System.out.println(referenceQueue.poll()); } } class E { @Override protected void finalize () throws Throwable { super .finalize(); System.out.println("被回收之前的操作" ); } }
软引用实现缓存的淘汰策略 java中可以用引用实现 淘汰策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class SoftCache { Map<String, SoftReference<ObjectTest>> map = new HashMap<>(); void setCaches (String id,ObjectTest obj) { map.put( id,new SoftReference<ObjectTest>(obj)); } ObjectTest getCache (String id) { SoftReference<ObjectTest> softRef = map.get(id) ; return softRef == null ? null : softRef.get() ; } } class ObjectTest {}
双亲委派 当一个加载器要加载类的时候,自己先不加载,而是逐层向上交由双亲去加载;当双亲中的某一个加载器 加载成功后,再向下返回成功。如果所有的双亲和自己都无法加载,则报异常
JVM自带的加载器 在JVM的内部所包含,C++
加载 jre\lib\rt.jar
包含平时编写代码时 大部分jdk api,指定加载某一个jar -Xbootclasspath=a.jar
..\jre\lib\ext\\\*.jar
指定加载某一个jar -Djava.ext.dirs= ....
AppClassLoader/SystemClassLoader 系统加载器/应用加载器
加载classpath,指定加载 -Djava.class.path= 类/jar
用户自定义的加载器 独立于JVM之外的加载器,Java
都是抽象类java.lang.ClassLoader的子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyLoader { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("java.lang.Object" ); ClassLoader classLoader = clazz.getClassLoader(); System.out.println(classLoader); Class<?> clazz2 = Class.forName("com.dream.xiaobo.loader.Loader1" ); ClassLoader classLoader2 = clazz2.getClassLoader(); System.out.println(classLoader2); } } class Loader1 {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MyLoader2 { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("java.lang.String" ); ClassLoader classLoader = clazz.getClass().getClassLoader(); System.out.println("classLoader:" + classLoader); System.out.println("====" ); ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("systemClassLoader:" + systemClassLoader); System.out.println("====" ); ClassLoader parent = systemClassLoader.getParent(); System.out.println("parent:" + parent); System.out.println("====" ); ClassLoader parent2 = parent.getParent(); System.out.println("parent2:" + parent2); System.out.println("====" ); Enumeration<URL> resources = systemClassLoader.getResources("./com/dream/xiaobo/loader/MyLoader2.class" ); if (resources.hasMoreElements()) { System.out.println("url:" + resources.nextElement()); } } }
如果类是rt.jar中的,则该类是被bootstrap(根加载器)加载;如果是classpath中的类(自己编写的类),则该类是被AppClassLoader加载
定义类加载
:最终实际加载类的加载器
初始化类加载类
:直接面对加载任务的类
自定义类加载器详解 二进制名binary names
1 2 3 4 "java.lang.String" "javax.swing.JSpinner$DefaultEditor" "java.security.KeyStore$Builder$FileBuilder$1" "java.net.URLClassLoader$3$1"
$
代表内部类:
$数字
:第几个匿名内部类
数组的加载器类型和数组元素的加载器类型是相同
原声类型的数组是没有类加载器的
如果加载的结果是null 可能是此类没有加载器(int[]) , 也可能是 加载类型是“根加载器”
xxx.class文件可能是在本地存在,也可能是来自于网络 或者在运行时动态产生(jsp)
演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class MyClassLoaderImpl extends ClassLoader { private String path ; public MyClassLoaderImpl () { super (); } public MyClassLoaderImpl (ClassLoader parent) { super (parent); } @Override public Class findClass (String name) { System.out.println("加载findClass" ); byte [] b = loadClassData(name); return defineClass(name, b, 0 , b.length); } private byte [] loadClassData(String name) { System.out.println("加载loadClassData" ); if (path != null ){ name = path+ name.substring( name.lastIndexOf("." )+1 )+".class" ; }else { name = dotToSplit("out.production.volatile." +name)+".class" ; } byte [] result = null ; FileInputStream inputStream = null ; ByteArrayOutputStream output = null ; try { inputStream = new FileInputStream( new File( name) ); output = new ByteArrayOutputStream(); byte [] buf = new byte [2 ]; int len = -1 ; while ((len = inputStream.read(buf)) != -1 ) { output.write(buf, 0 , len); } result = output.toByteArray(); }catch (Exception e){ e.printStackTrace(); ; }finally { try { if (inputStream != null ){ inputStream.close(); } if (output != null ){ output.close(); } }catch (Exception e){ e.printStackTrace(); } } return result ; } public static void main (String[] args) throws Exception { MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl(); myClassLoader.path = "d:/" ; Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.loader.MyDefineCL" ); System.out.println(aClass.getClassLoader()); } public static String dotToSplit (String clssName) { return clssName.replace("." ,"/" ) ; } } class MyDefineCL { public void say () { System.out.println("Hello" ); } }
继承ClassLoader,重写的 findClass()
findClass(String name){...}
:是全类名的形式 a.b.c.class,并且开头 是: 包名.类名.class
loadClassData(String name){...}
:name所代表的文件内容->byte[] ,是文件形式的字符串a/b/c.class,并且开头out.production..
.class文件从classpath中删除,之后才可能用到 自定义类加载器;否在classpath中的.class会被 APPClassLoader加载
loadClass() ->findClass()->loadClassData()
: 代码执行流程
类加载器只会把同一个类加载一次,同一个class文件加载后的位置
先委托APPClassLoader加载,APPClassLoader会在classpath中寻找是否存在,如果存在则直接加载;如果不存在,才有可能交给自定义加载器加载
在双亲委派体系中,下层的加载器是通过parent引用上层的加载器。即在双亲委派体系中,各个加载器之间不是继承关系
双亲委派机制优势 可以防止用户自定义的类和rt.jar中的类重名而造成的混乱
继承关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class B { public B () { System.out.println("B被加载了,加载器是: " + this .getClass().getClassLoader()); } } public class A extends B { public A () { super (); System.out.println("A被加载了,加载器是: " + this .getClass().getClassLoader()); } } public class TestMyClassLoader2 { public static void main (String[] args) throws Exception { MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ; myClassLoader.path = "d:/" ; Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.extendss.A" ); Object aObject = aClass.newInstance(); System.out.println(aObject); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 存在继承关系 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的不是同一个加载器 --- 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 A with modifiers "public" A.class/B.class: 自定义加载器加载 原因是 main()方法所在类在 工程中(APPClassLoader),而A和B不在工程中(自定义加载器)。 造成这些异常的核心原因: 命名空间(不是由同一个类加载器所加载)
非继承关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Y { public Y () { System.out.println("Y被加载了,加载器是: " + this .getClass().getClassLoader()); } } public class X { public X () { new Y() ; System.out.println("X被加载了,加载器是: " + this .getClass().getClassLoader()); } } public class TestMyClassLoader3 { public static void main (String[] args) throws Exception { MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ; myClassLoader.path = "d:/" ; Class<?> aClass = myClassLoader.loadClass("com.dream.xiaobo.extendss.X" ); Object aObject = aClass.newInstance(); System.out.println(aObject); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 没有继承关系 X.class: D: 自定义加载器 Y.class: classpath 系统加载器 Y被加载了,加载器是: AppClassLoader X被加载了,加载器是: MyClassLoaderImpl 因为下层可以调用上层 --- X.class: classpath 系统加载器 Y.class: D: 自定义加载器 java.lang.NoClassDefFoundError
如果存在继承关系: 继承的双方(父类、子类)都必须是同一个加载器,否则出错
如果不存在继承关系: 子类加载器可以访问父类加载器加载的类(自定义加载器,可以访问到 系统加载器加载的Y类);反之不行(父类加载器 不能访问子类加载器)
OSGI
可以将项目部署在网络上,可以在A节点上 远程操作B节点。在操作上,可以对硬件无感。也可以在A节点上 对B节点上的项目进行运维、部署,并且立项情况下 在维护的期间,不需要暂时、重启。
类的卸载
系统加载器、扩展加载器、根加载器,这些加载器加载的类是不会被卸载
会被GC卸载GC
JVM监测工具 jps
: 查看Java进程 (java命令)
jstat
:只能查看当前时刻的内存情况;可以查看到 新生代、老年代中的内存使用情况
jmap
:查看堆内存的占用情况;也可以执行dump操作
jconsole
:图形的监控界面
如果通过jconsole中的”执行gc”按钮发现 GC回收的内存太少,就说明当前进程是存在问题的(至少是可以被优化的)
jvisualvm
: 监视 - 堆Dump -查找最大对象,从中可以发现 当前进程中是哪个对象 占据了最大的内存,从而对这个对象进行分析。
通过VM参数实现: 当内存溢出时,自动将溢出时刻的内存dump下来。
1 2 3 -Xmx100m -Xms100m -XX:+HeapDumpOnOutOfMemoryError
GC调优 调优实际是是一种取舍,以xx换xx的策略。因此在调优之前,必须明确方向:低延迟?高吞吐量?
在已知条件相同的前提下,牺牲低延迟 来换取 高吞吐量,或者反之。
GC的发展 JVM默认的GC
CMS GC(在jdk9以后被逐步废弃) –> G1 GC(jdk9) –> Z GC(jdk11)
串行GC,是一种在单核环境下的串行回收器。当GC回收的时刻,其他线程必须等待。一般不会使用。
在Serial 的基础上,使用了多线程技术,提高吞吐量。
CMS使用了多线程技术,使用标记清除算法
,可以极大提升效率,在低延迟上有很大的提升,CMS GC繁琐,参数太多
jdk9开始使用的默认GC。将堆内存划分为很多大小相等region,并会对这些区域的使用状态进行标记。以便GC在回收时,能够快速的定位出哪些region是空闲的,哪些是有垃圾对象,从而提升GC的效率。G1使用的算法是标记整理算法
jdk11开始提供全新的GC。回收TB级别的垃圾 在毫秒范围。
根据生命周期划分
回收新生代中的对象
回收整个堆空间(新生代、老年代)
简单的一个小问题
如果通过监测工具发现Minor GC和Full GC都在频繁的回收,如何优化?
思路
:Minor GC –> 短生命周期的对象太多了 –> 造成逃逸到老年代中的对象越多 –> 新生代多+老年代多 –> Full GC
解决办法
:Minor GC 可以尝试调大 新生代的最大空间,再调大 新生代晋升到老年代的阈值,从而降低 短生命周期的对象 从新生代转移到老年代的概率
正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……