1 JDK7和JDK8将字符串常量池存放在了堆中
字符串常量池string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中存放的,string pool实现为哈希表。
public class TestStringPool { //-Xms5m -Xmx5m -XX:-UseGCOverheadLimit //设置最大堆内存和初始堆内存都是5M, //-XX:-UseGCOverheadLimit的目的是关闭检查防止抛出GC overhead limit exceeded //超过98%的时间用来做GC并且回收了不到2%的堆内存,会抛出GC overhead limit exceeded public static void main(String[] args) { String str = "abc"; char[] array = {'a', 'b', 'c'}; String str2 = new String(array); //使用intern()将str2字符串内容放入常量池 str2 = str2.intern(); //这个比较用来说明字符串字面常量和我们使用intern处理后的字符串是在同一个地方 System.out.println(str == str2); //那好,下面我们就拼命的intern吧 ArrayList<String> list = new ArrayList<String>(); for (int i = 0; i < 50000000; i++) { System.out.println(i); String temp = String.valueOf(i).intern(); list.add(temp); } } }
运行一段时间后会抛出堆内存溢出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
2 元空间MetaSpace溢出
元空间是用来存放类和类加载器的元信息的,在Jdk8中元空间并不在虚拟机中,而是使用本地内存
public class Test { //-XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=20m //设置元空间的初始值为20M,最大值为20M static int index = 0; public static void main(String[] args) { while(true) { System.out.println(index++); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(User.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub return proxy.invoke(obj, args); } }); enhancer.create(); } } static class User{ } }
运行一段时间后会抛出堆内存溢出:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
为了防止应用程序将服务器的内存一直占满,最好要关注下元空间的占用大小
3 堆溢出
public class Test0004 { // 垃圾回收机制基本原则:内存不足的时候回去回收,内存如果足够,暂时不会区回收。尽量减少回收次数和回收的时间 // -Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError //-XX:+PrintGCDetails 打印详细日志 //-XX:+HeapDumpOnOutOfMemoryError 发生OOM时自动生成dump public static void main(String[] args) { List<Object> listObject = new ArrayList<>(); for (int i = 0; i < 100; i++) { System.out.println("i:" + i); Byte[] bytes = new Byte[1 * 1024 * 1024]; listObject.add(bytes); } System.out.println("添加成功..."); } }
运行一段时候会抛出异常:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
4 栈溢出
public class Test005 { //-Xss1024k //设置每个栈的大小 //递归的层数很多会抛出StackOverflowError private static int count; public static void count() { try { count++; count(); } catch (Throwable e) { System.out.println("最大深度:" + count); e.printStackTrace(); } } public static void main(String[] args) { count(); } }
运行一段时候会抛出异常:
Exception in thread "main" java.lang.StackOverflowError
5 垃圾回收机制:
垃圾回收指的是不定时去堆内存中清理没有被引用的对象
堆内存划分:
垃圾回收机制是怎么判断对象是否存活?
引用计数法(该方法已经不采用了,当出现循环依赖时,gc没法进行判断)
引用计数法的基本思路是为每个对象设置一个引用计数器,当gc时,发现该对象被引用,则引用计数器加1,否则引用计数器减1,当引用计数器值为0时,则证明此对象是不可用
根搜索算法
根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用
可以作为GCRoots的对象包括下面几种:
(1)虚拟机栈(栈帧中局部变量表)中引用的对象。
(2)方法区中的类静态属性引用的对象。
(3)方法区中常量引用的对象。
(4)本地方法栈中JNI(Native方法)引用的对象。
6 垃圾回收算法
1 标记-清除算法
分为两个步骤,第一个步骤就是标记,也就是标记处所有需要回收的对象。第二个步骤是清除,标记完成后就进行统一的回收掉那些带有标记的对象。缺点是效率低并且标记清除之后会产生大量不连续的内存碎片,一般用在老年代
2 复制算法
将可用内存按容量划分为大小相等的两块,s0区和s1区,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉,优点是效率高,可以避免内存碎片问题,缺点是浪费内存空间。一般用在新生代
3 标记-整理算法
标记整理算法与标记清除算法很相似,但最显著的区别是:标记清除算法仅对不存活的对象进行处理,剩余存活对象不做任何处理,造成内存碎片;而标记整理算法不仅对不存活对象进行处理清除,还对剩余的存活对象进行重新整理,因此其不会产生内存碎片,一般用在老年代
4 分代算法
把Java堆分为新生代和老年代,为每个对象设置一个年龄属性,综合使用上面的三种算法
7 JVM常见参数配置(JDK8)
-XX:+PrintGCDetails 详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值(Eden区域和Survivor区域)
-Xss 每个线程分配的栈大小
-XX:SurvivorRatio 新生代Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8 ( Eden : From : To = 8 : 1 : 1 )
-XX:NewRatio 新生代与老年代占比 1:2
-XX:+HeapDumpOnOutOfMemoryError 内存溢出时导出dump文件
-XX:+PrintGCDetails 打印详细gc日志
-XX:MetaspaceSize 元空间初始值
-XX:MaxMetaspaceSize 元空间可用最大值
8 默认收集器
JDK8默认的垃圾收集器是ParallelGC
JDK9中默认的垃圾收集器是G1
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,当新生代满时就会触发Minor GC,这里的新生代满指的是Eden代满,Survivor满不会引发GC
老年代 GC(Major GC /Full GC):指发生在老年代的垃圾收集动作,当老代满时会引发Full GC,Full GC将会同时回收新生代、老年代
9 JvisualVm的使用
为了让本机器上的jvisualvm 工具能够监视远程机器上(linux)的tomcat中线程运行状况,tomcat需要修改其对应配置。修改如下:
(1) 修改catalina.sh文件
CATALINA_OPTS="-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.12.145.108" # ----- Execute The Requested Command -----------------------------------------
其中红色所指IP是tomcat所属服务器的IP。蓝色所指端口为jmx连接时的端口
(2) linux防火墙配置
在(1) 中指定了jmx连接的端口,此时需要查看linux是否开启了该端口,并将该端口设置到防火墙中允许通过