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是否開啟了該端口,並將該端口設置到防火牆中允許通過