一.请你谈谈实际的项目中在Java虚拟机会抛出哪些异常,每一个异常都是怎么产生的?java
1.java.lang.StackOverflowError 栈空间满了vim
public static void stackOverFlow(){ // 递归调用以后,把栈空间塞满了,当程序出现递归调用没有终止的时候,就会出现此类错误 // Exception in thread "main" java.lang.StackOverflowError stackOverFlow(); }
运行结果:缓存
2.java.lang.OutOfMemoryError: Java heap space 堆空间满了服务器
public static void outOfMemoryHeap() { // 经过不停的生成新的string对象,把堆空间塞满,堆中没有在存放新的对象,此时会出现该错误 // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space String str = "itheima"; while (true){ str += str + new Random().nextInt(111111)+new Random().nextInt(22222222); str.intern(); } }
运行结果:多线程
3.java.lang.OutOfMemoryError: GC overhead limit exceeded GC回收时间过长并发
public static void outOfMemoryGC() { /* * GC回收时间过长时候会抛出OutOfMemoryError,过长的定义是指,超过98%的时间用来作GC而且回收了不到 * 2%的堆内存,连续屡次GC都回收不到2%的极端状况下才会抛出.假如不抛出GC overflow limit会发生什么 * 呢?那就是GC清理的内存很快就会被再次填满,迫使GC再次执行,致使CPU的使用率一直在100%,可是GC却没有 * 任何成果. * 配置JVM参数 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails * * java.lang.OutOfMemoryError: GC overhead limit exceeded * */ int i = 0; List<String> list = new ArrayList<>(); try { while (true){ list.add(String.valueOf(++i).intern()); } }catch (Throwable e){ e.printStackTrace(); System.out.println("i = "+i); throw e; } }
运做结果:dom
4.java.lang.OutOfMemoryError: Direct buffer memory 外部内存满了ide
public static void outOfMemoryDbm() { /* 写NIO的程序进场使用ByteBuffer来读取或写入数据,这是一种基于通道和缓冲区的I/O方式,它可使用 Native函数库直接分配堆外内存,而后经过一个存储在Java堆里面的DirectByteBuffer对象做为这块的引用 进行操做.这样能够在一些场景中显著提升性能,由于避免了Java堆和Native堆中来回复制内存 ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,属于GC的范围,因为须要拷贝,全部 速度相对较慢 ByteBuffer.allocateDirect(capability)第一种方式是分配OS本地内存,不属于GC管辖的范围,因此 因为不须要进行内存拷贝因此速度相对较快 但若是不断分配本地内存,堆内存不多使用,那么JVM就不须要执行GCDirectByteBuffer对象们就不会被回收 这时候堆内存充足,可是本地内存已经使用光,再次尝试使用本地内存就会抛出OOM错误,JVM崩溃 JVM配置 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails * java.lang.OutOfMemoryError: Direct buffer memory nio程序常常出现 */ System.out.println("JVM最大可用内存: "+ (sun.misc.VM.maxDirectMemory() / (double)1024 / 1024 )+"MB"); try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 *1024); }
运行结果:函数
5.java.lang.OutOfMemoryError: unable to create new native thread 没法建立新的本地的线程高并发
public static void outOfMemoryUcnt(){ /* * 高并发请求服务器时候,会出现以下的错误,准确的来讲该异常与对应的平台有关 * 致使缘由: * 1.应用建立了太多线程,一个应用程序建立了多个线程,超过了系统的承载 * 2.服务器并不容许应用程序建立过多的线程,Linux默认容许单个进程能够建立的线程数为1024个 * 应用程序建立的线程超过1024,就会抛出java.lang.OutOfMemoryError: unable to create new native thread * 解决办法: * 1.想办法下降应用程序建立的线程数量,分析应用程序是否真的须要建立这么多线程,若是不是,修改代码下降线程数 * 2.对于有的应用,确实须要建立不少线程,远超过Linux系统默认的1024个上限,能够经过修改Linux服务器配置 * 扩大Linux的默认限制 root用户没有限制 * ulimit -u 查看当前的用户下能够容许建立的线程数 * vim /etc/security/limits.d/90-nproc.conf 文件 * */ for (int i = 1; ; i++) { System.out.println("i = "+i); new Thread(()->{ try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } }
运行结果:Windows没有测出来
6.java.lang.OutOfMemoryError: Metaspace 元空间溢出
static class OOMTest{}; 内部类 public static void outOfMemoryMetaSpace(){ /* * MetaSpace是方法区在HotSpot中的实现,它和持久区最大的区别在于:MetaSpace并不在虚拟机内存而使用本地内存 * 在JDK1.8中,class Metadata 被存储在MetaSpace的native memory * 永久代(元空间)存放了如下信息 * 1. 虚拟机加载的类信息 * 2. 常量池 * 3. 静态变量 * 4. 即时编译后的代码 * 配置JVM: -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=9m * * */ int i = 0; try { while (true){ i++; Enhancer e = new Enhancer(); e.setSuperClass(OOMTest); e.setUseCache(false); e.setCallBack(new MethodInterceptor(){ @Override public Object intercept(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{ return methodProxy.invokeSuper(o,args); } }); e.create(); } }catch (Throwable e){ System.out.println("第" + i+"次元空间溢出"); e.printStackTrace(); } }
运行结果:Windows没有出现!!!
二.请你能谈一谈Java的有哪些引用?JVM分别对不一样的引用时怎样进行回收的?有什么做用?
1.强引用
当内存不足时,JVM会进行垃圾回收,对于强引用对象,就算出现OOM错误也不会对该对象进行垃圾回收,强引用是最多见的普通对象的引用,
只要还有强引用指向一个对象,就说明这个对象还活着,垃圾回收机制就不会回收这个对象,在Java中,最多见的引用就是强引用,把一个对象赋值
给一个引用变量,这个引用变量也是强引用当一个变量是强引用时,它处于可达的状态,不被垃圾回收机制回收的.即便该对象之后永远也用不到,JVM
也不会对其进行回收,所以强引用也是引起Java内存泄漏的主要缘由之一.
对于一个普通的对象,若是没有其它得我引用关系,只要超过了引用的做用域,或者显式的把强引用赋值为null,通常就能够被垃圾回收机制回收.
// 强引用 public static void show01(){ Object o1 = new Object(); // new 出来的对象通常默认是强引用 Object o2 = o1; // o2 引用赋值 o2也是强引用 o1 = null; // 置空 System.gc(); // 手动GC垃圾回收 System.out.println(o1); System.out.println(o2); }
运行结果: o2没有被垃圾回收机制清除
2.软引用
软引用是相对于强引用弱化了一些的引用,须要使用java.lang.ref.SoftReference类来实现,可让对象豁免一些垃圾回收,
对于软引用的对象来讲,当系统的内存充足时,不会被垃圾回收机制回收,当系统的内存不足时,会被垃圾回收机制回收.
软引用一般用在一些对内存敏感的系统中,好比高速缓存就有用到软引用
//软引用 public static void show02(boolean flag){ if(flag){ // 内存充足 Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; System.gc(); System.out.println(o1); System.out.println(softReference.get()); }else{ // 内存不充足设置JVM -Xms5m -Xmx5m -XX:+PrintGCDetails Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; System.gc(); try { // 形成OOM,故意制造大对象 byte[] bytes = new byte[30 * 1024 * 1024]; }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(o1); System.out.println(softReference.get()); } } }
运行结果:
1.内存充足,o1被置空,但o2没有被回收
2.内存不足 o1,o2都被置为空
3.弱引用
须要使用java.lang.ref.WeakReference类来实现,它比软引用的生命周期更短
对于只有软引用的对象来讲,只要垃圾回收机制一运行,无论JVM的内存空间是否充足,都会回收该对象所占的内存
用处:假若有一个应用,须要读取大量的本地图片,若是每次都从硬盘中读取,严重影响速度,若是所有加载到内存,又可能引起OOM
使用软引用/弱引用能够解决此类问题
使用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,
JVM会自动回收这些缓存图片所占用的空间,从而有效避免OOM问题
Map<String,SoftReference<BitMap>> imageCache = new HashMap<String,SoftReference<BitMap>>();
public static void show03(){ Object o1 = new Object(); WeakReference<Object> weakReference = new WeakReference<>(o1); System.out.println(o1); System.out.println(weakReference.get()); o1 = null; System.gc(); System.out.println(o1); System.out.println(weakReference.get()); }
运行结果: 只要一运行垃圾回收,内存就会被释放
4.虚引用
虚引用主要是经过java.lang.ref.PhantomReference类来实现的,与其余的引用类型不一样,虚引用并不会决定对象的生命周期
若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候都有可能被垃圾回收机制回收,它不能单独使用也不能
经过它访问对象,虚引用必须和引用队列来联合使用虚引用的主要做用是跟踪对象被垃圾回收的状态,就是这个对象被收集器回收
的时候收到一个系统的通知或者后续添加作进一步的处理
public static void show04(){ Object o1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); System.out.println("---------------------"); o1 = null; System.gc(); System.out.println(o1); System.out.println(referenceQueue.poll()); System.out.println(phantomReference.get()); }
运行结果:
1.虚引用的get()方法永远只返回null
2.在进行垃圾回收以后,在引用队列中能够看到此对象,主要就是在对象销毁前作出一些通知,相似于Spring的后置AOP