- 转载请注明出处:https://www.jianshu.com/p/13afdf841f68
Java8 JVM内存结构
基本结构与之前类似,只是Java8取消了之前的“永久代”,取而代之的是“元空间”——Metaspace,两者本质是一样的。“永久代”使用的是JVM的堆内存,而“元空间”是直接使用的本机物理内存。
GC Roots
如果判断一个对象可以被回收?
引用计数算法
维护一个计数器,如果有对该对象的引用,计数器+1,反之-1。无法解决循环引用的问题。
可达性分析算法
枚举根节点做可达性分析。即,从一组名为“GC Roots”的根节点(起始点)对象出发,向下遍历。那些没有被遍历到与GC Roots形成通路的对象,会被标记为“回收”(可达判断为存活,不可达判为死亡)。所谓的“GC roots”或者说tracing GC的“集合”,就是一组必须活跃的引用。
哪些对象可以作为GC Roots?
可以作为GC Roots举例:
本地方法栈(native)中引用的对象。
- 虚拟机栈(栈帧中的局部变量)中引用的对象。
- 方法区中常量引用的对象。
方法区中类静态属性引用的对象。
JVM参数
JVM 三种类型参数
- 标配参数(了解)
- X参数(了解)
- XX参数(重要)
标配参数
比如-version
、-help
、-showversion
等,几乎不会改变。
X参数
用得不多,比如-Xint
,解释执行模式;-Xcomp
,编译模式(第一次使用就编译成本地代码);-Xmixed
,开启混合模式(默认,先编译再执行)。
XX参数
重要,用于JVM调优。
JVM XX参数
XX参数包括:
- Boolean类型
- kv设值类型(键值对)
jinfo (查看当前运行程序的配置)
布尔类型
公式:-XX:+某个属性
、-XX:-某个属性
,开启(+)或关闭(-)某个功能。比如-XX:+PrintGCDetails
,开启GC详细信息。
KV键值类型
公式:-XX:属性key=值value
。比如-XX:Metaspace=128m
、-XX:MaxTenuringThreshold=15
。
JVM Xms/Xmx参数
-Xms
和-Xmx
十分常见,用于设置初始堆大小和最大堆大小。第一眼看上去,既不像X参数,也不像XX参数。实际上-Xms
等价于-XX:InitialHeapSize
,-Xmx
等价于-XX:MaxHeapSize
。所以-Xms
和-Xmx
属于XX参数。
JVM 查看参数
查看某个参数
使用jps -l
配合jinfo -flag JVM参数 pid
。先用jsp -l
查看java进程,选择某个进程号。
17888 org.jetbrains.jps.cmdline.Launcher
5360 org.jetbrains.idea.maven.server.RemoteMavenServer
18052 demo3.demo3
jinfo -flag PrintGCDetails 18052
可以查看18052 Java进程的PrintGCDetails
参数信息。
-XX:-PrintGCDetails
JVM参数调优:查看参数(第三个重要)
上面查看参数时使用参数
-flag/flags
,得需要具体的进程号,下面的这些不用,并且非常重要:
java -XX:+PrintFlagsInitial
,查看JVM初始参数
java -XX:+PrintFlagsFinal
查看修改更新后的参数
使用java -XX:+PrintFlagsFinal
打印中的结果有=
和:=
两种,如下:
=
就是没有被修改过的值,:=
就是被JVM默认加载的时候或者人为修改的参数java -XX:+PrintCommandLineFlags -version
打印命令行参数,可以方便查看使用的是那个垃圾回收器
JVM 常用参数
-Xmx/-Xms
如何解释 -Xmx和-Xms,属于X参数还是XX参数?
-Xms
等价于-XX:InitialHeapSize
-Xmx
等价于-XX:MaxHeapS
最大和初始堆大小。最大默认为物理内存的1/4,初始默认为物理内存的1/64。
-Xss
等价于-XX:ThresholdStackSize
,查看栈空间的大小(栈管运行,堆管存储)。用于设置单个栈的大小,系统默认值是0(不代表栈大小真的为0,而是代表是系统的默认值512k~1024k)。而是根据操作系统的不同,有不同的值。比如64位的Linux系统是1024K,而Windows系统依赖于虚拟内存。
-Xmn
新生代大小,一般不调。
-XX:MetaspaceSize
设置元空间大小。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。默认大概是用了20M的大小。
-XX:+PrintGCDetails
输出GC收集信息,包含GC
和Full GC
信息。这个比较重要。
-XX:SurvivorRatio
新生代中,Eden
区和两个Survivor
区的比例,默认是8:1:1
。通过-XX:SurvivorRatio=4
改成4:1:1
-XX:NewRatio
老生代和新年代的比列,默认是2,即老年代占2,新生代占1。如果改成-XX:NewRatio=4
,则老年代占4,新生代占1。
-XX:MaxTenuringThreshold
新生代设置进入老年代的时间,默认是新生代逃过15次GC后,进入老年代。如果改成0,那么对象不会在新生代分配,直接进入老年代。并且,自己设置的时候,最大值不能超过15。
以上参数使用示例:
使用jps查看进程信息
以元空间举例,查看初始元空间大小
改变元空间大小
重新查看元空间大小
四大引用
以下Demo都需要设置-Xmx
和-Xms
(手动设为10M),不然系统默认很大,很难演示。
强引用
当内存不足,JM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达
状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一
使用new
方法创造出来的对象,默认都是强引用。GC的时候,就算内存不够,抛出OutOfMemoryError
也不会回收对象,死了也不回收。
package Test;
public class T{
public static void main(String[] args) {
//定义强引用
Object A = new Object();
//引用复制,B也为强引用
Object B = A;
//A置空
A = null;
//手动垃圾回收
System.gc();
//B不会被回收,A会
System.out.println(B);
}
}
/*====================output========================
java.lang.Object@4554617c
*/
软引用
需要用Object.Reference.SoftReference
来显示创建。如果内存够,GC的时候不回收。内存不够,则回收,尽量不让其发生OOM。常用于内存敏感的应用,比如高速缓存。详见SoftReferenceDemo。
弱引用
需要用Object.Reference.WeakReference
来显示创建。无论内存够不够,只要是弱引用,GC的时候都会回收,也可以用在高速缓存上。详见WeakReferenceDemo
软引用和弱引用的适用场景:
假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取则会严重影响性能,如果一次性全部加载到内存中又可能造成内存溢出,此时使用软引用可以解决这个问题。设计思路是:用一个 HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
Map
> imageCache = new HashMap >();
WeakHashMap
传统的HashMap
就算key==null
了,也不会回收键值对。但是如果是WeakHashMap
,一旦内存不够用时,且key==null
时,会回收这个键值对。详见WeakHashMapDemo。
虚引用
需要用Object.Reference.PhantomReference
类来显示创建。顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列( Reference queue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize以后,做某些事情的机制。软应用和弱引用可以通过get()
方法获得对象,但是虚引用不行。虚引用的get方法形同虚设,总是返回null。虚引用在任何时候都可能被GC,不能单独使用,必须配合引用队列(ReferenceQueue)来使用。设置虚引用的唯一目的,就是在这个对象被回收时,收到一个通知以便进行后续操作,有点像Spring
的后置通知。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用 finalize方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。详见PhantomReferenceDemo。
引用队列
弱引用、虚引用被回收后,会被放到引用队列里面,通过poll
方法可以得到。关于引用队列和弱、虚引用的配合使用,见ReferenceQueueDemo。
强、软、弱、虚引用小结:
参考:https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/docs/JVM.md