欢迎大家关注 github.com/hsfxuebao/j… ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1、JVM 复习串讲
JVM 内存结构
- JVM 体系结构
- Java8以后的JVM
GC 的作用域
常见的垃圾收集算法
-
引用计数算法
-
复制算法
-
标记清除算法
-
标记整理算法
2、谈谈 GC Roots
JM垃圾回收的时候如何确定垃圾?是否知道什么是 GC Roots?
什么是垃圾?
简单的说就是内存中已经不再被使用到的空间就是垃圾
引用计数算法
- Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。
- 因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,给对象中
- 添加一个引用计数器,每当有一个地方引用它,计数器值加1,每当有一个引用失效时,计数器值减1。
- 任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象。
- 那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。
枚举根节点做可达性分析(根搜索路径)
- 为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。
- 所谓“GCroots”或者说tracing GC的“根集合”就是一组必须活跃的引用。
- 基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
- 也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡。
那么问题来了,哪些可作为GC Roots的对象
- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象
- 方法区中的静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI(Native方法)引用的对象
3、JVM 系统默认值
JVM常用基本配置参数有哪些?
3.1、JVM参数类型
3.1.1、标配参数
在jdk各个版本之间稳定,很有大的变化
- -version
- -help
- java -showversion
3.1.2、X 参数
X 参数了解即可
- -Xint(修改编译模式)
- -Xcomp
- -Xmixed
3.1.3、XX 参数
1、Boolean 类型
公式:-XX:+或者- 某个属性,+表示开启、-表示关闭
示例
- 是否打印GC收集细节:
- -XX:-PrintGCDetails
- -XX:+PrintGCDetails
- 是否使用串行垃圾回收器
- -XX:-UseSerialGC
- -XX:+UseSerialGC
2、KV键值对类型
公式:-XX:属性key=属性值value
示例
- -XX:MetaspaceSize=128m
- -XX:MaxTenuringThreshold=15
题外话:jinfo 如何查看当前运行程序的配置
公式 1:jinfo -flag 配置项 进程编号
公式 2:jinfo -flags 进程编号
示例
- jps -l :得到 JVM 进程编号
- jinfo -flag PrintGCDetails pid :查看 PrintGCDetails 属性是否开启(pid 为进程编号)
题外话:两个经典参数 -Xms和-Xmx
- -Xms:等价于-XX:InitialHeapSize
- -Xmx:等价于-XX:MaxHeapSize
3.2、查看 JVM 默认值
-XX:+PrintFlagsInitial:主要查看初始默认值
公式:
- java -XX:+PrintFlagsInitial -version
- java -XX:+PrintFlagsInitial
示例
-XX:+PrintFlagsfinal:主要查看修改更新
公式:java -XX:+PrintFlagsFinal -version
示例
- = 表示没被修改过
- := 表示 JVM 默认加载时修改过或人为修改过
PrintFlagsFinal举例:运行Java命令的同时打印出参数
- java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m T(T 为 Java 类名)
-XX:+PrintCommandLineFlags:打印命令行参数
公式:java -XX:+PrintCommandLineFlags -version
C:\Users\Heygo\Desktop\Interview>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266620736 -XX:MaxHeapSize=4265931776 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocatio
n -XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
123456
4、JVM 常用参数
基础知识复习
public class HelloGC {
public static void main(String[] args) {
long totalMemory = Runtime.getRuntime().totalMemory(); // Java 虚拟机中的内存总量
long maxMemory = Runtime.getRuntime().maxMemory(); // Java 虚拟机试图使用的最大内存量
System.out.println("TOTAL_MEMORY:" + totalMemory / (double) 1024 / 1024 + "MB");
System.out.println("MAX_MEMORY:" + maxMemory / (double) 1014 / 1024 + "MB");
}
}
1234567891011121314151617
常用参数
-
-Xms:初始大小内存,默认为物理内存1/64,等价于-XX:InitialHeapSize
-
-Xmx:最大分配内存,默认为物理内存1/4,等价于-XX:MaxHeapSize
-
-Xss:设置单个线程栈的大小,等价于-XX:ThreadStackSize,一般默认为512~1024K;0代表默认出厂值
-
-Xmn:设置年轻代大小
-
-XX:MetaSpaceSize:设置元空间大小
- 元空间本质和永久代类似,都是对JVM中方法区的实现。不过元空间与永久代之间最大的区别是:元空间并不在虚拟机中,而是使用本地内存。
- 因此,默认情况下,元空间的大小仅受本地内存限制,默认大小约为 21M
- -Xms10m -Xmx10m -XX:MetaSpaceSize=1024m -XX:+PrintFlagsFinal
-
-XX:+PrintGCDetails:输出详细GC收集日志信息。
-
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
- 默认-XX:SurvivorRatio=8,则Eden:S0:S1 = 8:1:1
- 假如设置 -XX:SurvivorRatio=4,则新生代中 Eden:S0:S1=4:1:1
- 即SurvivorRatio值就是设置Eden区的比例占多少,S0/S1相同
-
-XX:NewRatio:配置年轻代与老年代在堆结构的占比
- 默认-XX:NewRatop=2,新生代占1,老年代占2,即新生代占整个堆的1/3
- 假如设置参数-XX:NewRatop=4,新生代占1,老年代占4,即新生代占整个堆的1/5
- NewRatio就是老年代占比,剩下的1给年轻代
-
-XX:MaxTenuringThreshold:设置垃圾最大年龄
- -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。
- 如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加对象在年轻代被回收的概率
典型设置案例:可以和面试官闲聊的案例
-Xms4096m -Xmx4096m -Xss1024k -XX:MetaSpaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
1
5、四中引用类型
强引用、软引用、弱引用、虚引用分别是什么?
5.1、整体架构
强、软、弱、虚
5.2、四种引用
5.2.1、强引用
强引用(默认支持模式)
- 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
- 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象
- 在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达。状态,它是不可能被垃圾回收机制回收的,即使该对象以后远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一
- 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)
5.2.2、软引用
软引用的含义
- 软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
- 对于只有软引用的对象来说,当系统内存充足时它不会被回收,当系统内存不足时它会被回收。
- 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
软引用代码示例
示例 1:内存够用不会回收软引用对象
-
代码
/**
- 内存够用的时候就保留,不够用就回收
*/ public class SoftReferenceDemo {
public static void main(String[] args) { softRef_Memory_Enough(); } public static void softRef_Memory_Enough() { Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; System.gc(); // 进行 GC System.out.println(o1); // 强引用变量为 null 必被回收 System.out.println(softReference.get()); // 内存够用不会回收软引用对象 }} 12345678910111213141516171819202122232425262728
-
程序运行结果:软引用对象并没有被回收
java.lang.Object@4554617c java.lang.Object@4554617c null java.lang.Object@4554617c 1234
示例 2:内存不够用必定回收软引用对象
-
代码
/**
- 内存够用的时候就保留,不够用就回收
*/ public class SoftReferenceDemo {
public static void main(String[] args) { //softRef_Memory_Enough(); softRef_Memory_NotEnough(); } /* JVm配置,故意产生大对象并配置小的内存,让内存不够用,导致OOM,看软引用的回收情况 -Xms5m -Xmx5m -XX:+PrintGCDetails */ public static void softRef_Memory_NotEnough() { Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1); System.out.println(softReference.get()); o1 = null; try { byte[] bytes = new byte[30 * 1024 * 1024]; } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println(o1); System.out.println(softReference.get()); } }} 123456789101112131415161718192021222324252627282930313233343536373839
-
程序运行结果:OOM 之前,必定进行一次 Full GC ,此时会清除软引用对象
[GC (Allocation Failure) [PSYoungGen: 1024K->504K(1536K)] 1024K->640K(5632K), 0.0008207 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] java.lang.Object@4554617c java.lang.Object@4554617c [GC (Allocation Failure) [PSYoungGen: 1403K->504K(1536K)] 1539K->752K(5632K), 0.0007773 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 504K->504K(1536K)] 752K->768K(5632K), 0.0005913 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 264K->637K(4096K)] 768K->637K(5632K), [Metaspace: 3426K->3426K(1056768K)], 0.0055154 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 637K->637K(5632K), 0.0002769 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) java.lang.OutOfMemoryError: Java heap space at com.Heygo.SoftReferenceDemo.softRef_Memory_NotEnough(SoftReferenceDemo.java:46) at com.Heygo.SoftReferenceDemo.main(SoftReferenceDemo.java:18) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 637K->620K(4096K)] 637K->620K(5632K), [Metaspace: 3426K->3426K(1056768K)], 0.0056716 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] null null Heap PSYoungGen total 1536K, used 92K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000) eden space 1024K, 9% used [0x00000000ffe00000,0x00000000ffe173f8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 4096K, used 620K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000) object space 4096K, 15% used [0x00000000ffa00000,0x00000000ffa9b138,0x00000000ffe00000) Metaspace used 3487K, capacity 4496K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K 12345678910111213141516171819202122
5.2.3、弱引用
弱引用的含义
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
弱引用代码示例
-
代码
public class WeakReferenceDemo { public static void main(String[] args) { Object o1 = new Object(); WeakReference weakReference = new WeakReference<>(o1); System.out.println(o1); System.out.println(weakReference.get());
o1 = null; System.gc(); System.out.println("..............."); System.out.println(o1); System.out.println(weakReference.get()); }} 12345678910111213141516171819202122
-
程序运行结果:执行 GC,弱引用对象被回收
java.lang.Object@4554617c java.lang.Object@4554617c ............... null null 12345
-
代码
public class WeakHashMapDemo { public static void main(String[] args){ myHashMap(); System.out.println("========"); myWeakHashMap(); }
private static void myHashMap(){ HashMap<Integer,String> map = new HashMap<>(); Integer key = new Integer(1); String value = "HashMap"; map.put(key,value); System.out.println(map); // key 置为 null ,关 HashMap 毛事啊,HashMap 已经将数据保存至 Node 节点中了 key = null; System.out.println(map); System.gc(); System.out.println(map); } private static void myWeakHashMap(){ WeakHashMap<Integer,String> map = new WeakHashMap<>(); Integer key = new Integer(2); String value = "WeakHashMap"; map.put(key,value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445
-
程序运行结果:如果 WeakHashMap 的 key 为 null ,GC 后该 KV 节点将被回收
{1=HashMap} {1=HashMap} {1=HashMap}
{2=WeakHashMap} {2=WeakHashMap} {} 1234567
- 虚引用需要java.lang.ref.PhantomReference类来实现。顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
- 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列ReferenceQueue联合使用。
- 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
- PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
- 换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
- Java 技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
-
代码
public class ReferenceQueueDemo { public static void main(String[] args) throws InterruptedException{ Object o1 = new Object(); ReferenceQueue referenceQueue = new ReferenceQueue<>(); WeakReference weakReference = new WeakReference<>(o1,referenceQueue); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll());
System.out.println("============="); o1 = null; System.gc(); Thread.sleep(500); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); }} 1234567891011121314151617181920212223242526
-
程序运行结果:在 GC 回收之前,将待回收的对象放入引用队列中,有点类似 Spring AOP 的后置通知
java.lang.Object@4554617c java.lang.Object@4554617c null
null null java.lang.ref.WeakReference@74a14482 1234567
-
代码
public class PhantomReferenceDemo { public static void main(String[] args) throws InterruptedException { Object o1 = new Object(); ReferenceQueue referenceQueue = new ReferenceQueue<>(); PhantomReference phantomReference = new PhantomReference<>(o1,referenceQueue);
System.out.println(o1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("================="); o1 = null; System.gc(); Thread.sleep(500); System.out.println(o1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); }} 123456789101112131415161718192021222324252627
-
程序运行结果:对象被回收时,将其放入了引用队列
java.lang.Object@4554617c null null
null null java.lang.ref.PhantomReference@74a14482 1234567
-
假如有一个应用需要读取大量的本地图片:
- 如果每次读取图片都从硬盘读取则会严重影响性能,
- 如果一次性全部加载到内存中又可能造成内存溢出。
-
此时使用软引用可以解决这个问题。设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
Map<String,SoftReference> imageCache = new HashMap<String,SoftReference>(); 1
- java提供了4种引用类型,在垃级回收的时候,都有自己各自的特点。
- ReferenceQueue是用来配合引用工作的,没有有ReferenceQueue一样可以运行。
- 创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动这相当于是一种通知机制。
- 当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事情。
-
代码:递归调用无结束条件
public class StackOverflowErrorDemo { public static void main(String[] args){ stackOverflowError(); }
private static void stackOverflowError() { stackOverflowError(); }} 12345678910111213141516
-
程序运行结果
Exception in thread "main" java.lang.StackOverflowError 1
-
代码
public class JavaHeapSpaceDemo { public static void main(String[] args){ String str = "seu";
while(true){ str += str + new Random().nextInt(11111111)+new Random().nextInt(22222222); str.intern(); } }} 123456789101112131415161718
-
JVM 参数
-Xms10m -Xmx10m 1
-
程序运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674) at java.lang.StringBuilder.append(StringBuilder.java:208) at com.Heygo.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:17) 123456
- GC回收时间过长时会抛出0utOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存
- 连续多次GC都只回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead Limit 错误会发生什么情况呢?
- 那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用孩一直是100%,而GC却没有任何成效
-
代码:将生成的字符串放到字符串常量池中
public class GCOverheadDemo { public static void main(String[] args) { int i = 0; List list = new ArrayList<>();
try { while (true) { list.add(String.valueOf(++i).intern()); } } catch (Throwable e) { System.out.println("***************i:" + i); e.printStackTrace(); throw e; } }} 123456789101112131415161718192021222324
-
JVM 参数
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m 1
-
程序运行结果:GC 说,老铁,我回收不动啊~~~
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7076K->7076K(7168K)] 9124K->9124K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0304580 secs] [Times: user=0.17 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7078K->7078K(7168K)] 9126K->9126K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0294555 secs] [Times: user=0.11 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7080K->7080K(7168K)] 9128K->9128K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0303605 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7081K->7081K(7168K)] 9129K->9129K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0273443 secs] [Times: user=0.11 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7083K->7083K(7168K)] 9131K->9131K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0323289 secs] [Times: user=0.13 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7085K->7085K(7168K)] 9133K->9133K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0282554 secs] [Times: user=0.25 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7087K->7087K(7168K)] 9135K->9135K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0279839 secs] [Times: user=0.22 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7088K->7088K(7168K)] 9136K->9136K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0268396 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7090K->7090K(7168K)] 9138K->9138K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0275986 secs] [Times: user=0.14 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7092K->7092K(7168K)] 9140K->9140K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0279635 secs] [Times: user=0.14 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7094K->7094K(7168K)] 9142K->9142K(9728K), [Metaspace: 3472K->3472K(1056768K)], 0.0259208 secs] [Times: user=0.22 sys=0.01, real=0.03 secs] ***************i:145410 [Full GC (Ergonomics) java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.toString(Integer.java:403) at java.lang.String.valueOf(String.java:3099) at com.Heygo.GCOverheadDemo.main(GCOverheadDemo.java:20) Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.toString(Integer.java:403) at java.lang.String.valueOf(String.java:3099) at com.Heygo.GCOverheadDemo.main(GCOverheadDemo.java:20) 1234567891011121314151617181920
- 写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属GC管辖范围,由于需要拷贝所以速度相对较慢ByteBuffer.allocateDirect(capability)第二种方式是分配OS本地内存,不属于GC管辖范围,由子不需要内存拷贝所以速度相对较快。
- 但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了
-
代码
public class DirectBufferMemoryDemo { public static void main(String[] args) { System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB"); try { Thread.sleep(300); } catch (Exception e) { e.printStackTrace(); } ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024); } } 12345678910111213141516171819
-
程序运行结果
配置的maxDirectMemory: 3618.0MB 1
-
代码同上
-
JVM 参数
-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails 1
-
程序运行结果
配置的maxDirectMemory: 5.0MB [GC (Allocation Failure) [PSYoungGen: 2035K->488K(2560K)] 2035K->792K(9728K), 0.0007089 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (System.gc()) [PSYoungGen: 564K->496K(2560K)] 868K->912K(9728K), 0.0008965 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 416K->705K(7168K)] 912K->705K(9728K), [Metaspace: 3483K->3483K(1056768K)], 0.0051785 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:694) at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at com.Heygo.DirectBufferMemoryDemo.main(DirectBufferMemoryDemo.java:22) 123456789
- 你的应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限
- 你的服务器并不允许你的应用程序创建这么多线程,Linux系统默认允许单个进程可以创建的线程数是1024个,你的应用创建超过这个数量,就会报
java.Lang.outofMemoryError:unable to create new native thread - 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
- 对于有的应用,确实需要创建很多线程,远超过Linux系统的默认1024个线程的限制,可以通过修改Linux服务器配置,扩大Linux默认限制
-
代码
package com.atguigu.Interview.study..jvm.oom;
public class UnableCreateNewThreadDemo {
public static void main(String[] args) { for (int i = 1; ; i++) { System.out.println(i); new Thread(() -> { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }} 123456789101112131415161718192021222324
-
Linux 编译带包名的 Java源文件
java -d . UnableCreateNewThreadDemo.java 1
-
Linux 运行带包名的 Java 程序
java com.atguigu.Interview.study..jvm.oom.UnableCreateNewThreadDemo 1
-
程序运行结果
-
当前普通用户无法终止该进程,需要 root 用户帮忙
ps -ef|grep java kill -9 pid 12
-
查看当前用户的可创建的最大线程数
ulimit -u 1
-
编辑配置文件:可以看到除了 root 用户无限制,其他用户所能创建的最大线程数为 1024
vim /etc/security/limits.d/90-nproc.conf 1
- 我们给张三用户多分配一些线程数
- Metaspace是方法区在Hotspot中的实现,它与永久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存,也即在java8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory
- Java 8及之后的版本使用Metaspace来替代永久代永久代,存放了以下信息:
- 虚拟机加载的类信息
- 常量池
- 静态变量
- 即时编译后的代码缓存
-
代码:利用 CGLIB 不停地往元空间中加载类
/**
- 模Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的
*/ public class MetaspaceOOMTest {
static class OOMTest { } public static void main(String[] args) { int i = 0;//模拟多少次后发生异常 try { while (true) { i++; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMTest.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor){ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return methodProxy.invokeSupper(o, args); } enhancer.create(); } } } catch (Throwable e) { System.out.println("********多少次后发生了异常:" + i); e.printStackTrace(); } }} 123456789101112131415161718192021222324252627282930313233343536373839
-
JVM 参数
-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m 1
-
程序运行结果
你知道弱引用的话,谈谈WeakHashMap
5.2.4、虚引用
虚引用的作用
引用队列:我(虚引用)被回收前需要被引用队列保存下
引用队列代码示例
虚引用代码示例
5.3、引用总结
软引用、弱引用适用场景
GC Roots 和四种引用类型的总结
6、OOM
面试题:请谈谈你对OOM的认识
java.lang.StackOverflowError
java.lang.OutOfMemoryError:Java heap space
java.lang.OutOfMemoryError:GC overhead limit exceeded
java.lang.OutOfMemoryError:Direct buffer memory
导致原因:
代码示例:查看 MaxDirectMemory
演示 DirectBuffer OOM
结论:
写 NIO 和 Netty 程序时要小心
java.lang.OutOfMemoryError:unable to create new native thread
高并发请求服务器时,经常出现如下错误 java.lang.OutOfMemoryError:unable to create new native thread,准确地讲,该 native thread 异常与对应的平台有关
导致原因:
解决办法:
非root用户登录Linux系统测试
终止程序
服务器级别调优参数
java.lang.OutOfMemoryError:Metaspace
代码示例