双亲委派模式优势
- 沙箱安全机制:自己写的String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
JDK1.0时期
在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱(Sandbox)机制。如下图所示JDK1.0安全模型:
JDK1.1时期
JDK1.0中如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。
因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略。允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型:
JDK1.2时期
在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示JDK1.2安全模型:
JDK1.6时期
当前最新的安全机制实现,则引入了域(Domain)的概念
虚拟机会把所有代码加载到不同的系统域和应用域。系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示,最新的安全模型(jdk1.6)
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
与JVM不同,操作系统底层中指向的是下一条执行的指令地址
在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)
程序计数器的内存区域是唯一一个没有任何内存溢出OutOfMemoryError情况的区域。
栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
栈:8大基本类型+对象引用+实例的方法
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常
使用 native
修饰的指令都是调用本地方法接口
它的具体做法是Native Method Stack中登记native方法,在(Execution Engine)执行引擎执行的时候加载Native Libraies
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
static final,Class,常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是存放对象实例,所有的对象实例以及数组都应当在堆上分配。
Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”、“老年代”、“永久代”、“Eden空间”、“From Survivor空间”、“To Survivor空间”等名词。
Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。
Java堆既可以被实现成固定大小的,也可以是可扩展的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError (OOM) 异常。
数组的内存
两个引用对象指向同一个数组
一个对象的内存图
两个对象使用同一个方法
两个引用指向同一个对象的内存图
Full GC 一般比 Minor GC 慢 10 倍以上
通过JVM参数设置
-XX:PretenureSizeThreshold
在 Serial和ParNew 收集器下有效,超过改参数大小直接进入老年代设置原因:为了避免大对象分配内存时的复制
在Eden区中坚持过 minor GC 进入survivor 区,age= 1;每坚持一次 minor GC , age += 1
默认 age = 15 进入老年代
Minor GC
后Eden区
存活的对象 Survivor 区
放不下部分进入老年代
Eden 区满了后触发 Minor GC , (回收 99%),剩余的进入 survivor to 区(空的)
配置担保参数:
-XX:-HandlePromotionFailure
如果回收完还是没有足够空间存储新的对象,就会发生OOM
遇到OOM 解决方法 -Xms1024m -Xmx1024m -XX:+PrintGCDetails (扩大初始内存)
- 一般情况下分配的总内存是电脑内存的四分之一,初始化的内存是六十四分之一(如下)
import com.sun.management.OperatingSystemMXBean; import java.lang.management.ManagementFactory; public class Test { public static void main(String[] args) { long maxMemory = Runtime.getRuntime().maxMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("虚拟机试图使用的最大内存 maxMemory:" + maxMemory + "字节 \t" + (maxMemory/(double)1024/1024) + "M"); System.out.println("虚拟机初始化使用的最大内存 totalMemory:" + totalMemory + "字节 \t" + (totalMemory/(double)1024/1024) + "M"); OperatingSystemMXBean osmb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); long totalPhysicalMemorySize = osmb.getTotalPhysicalMemorySize(); // long freePhysicalMemorySize = osmb.getFreePhysicalMemorySize(); // 电脑剩余内存 System.out.println("电脑内存总大小:" + totalPhysicalMemorySize + "字节 \t" + (totalPhysicalMemorySize/(double)1024/1024) + "M"); System.out.println("maxMemory/totalMemory : " + (double) maxMemory/totalMemory); System.out.println("totalPhysicalMemorySize/maxMemory : " + (double) totalPhysicalMemorySize/maxMemory); System.out.println("totalPhysicalMemorySize/totalMemory : " + (double) totalPhysicalMemorySize/totalMemory); } }
# 正常情况下 虚拟机试图使用的最大内存 maxMemory:3797417984字节 3621.5M 虚拟机初始化使用的最大内存 totalMemory:257425408字节 245.5M 电脑内存总大小:17083187200字节 16291.796875M maxMemory/totalMemory : 14.75152749490835 totalPhysicalMemorySize/maxMemory : 4.498632300151871 totalPhysicalMemorySize/totalMemory : 66.36169806517312 # -Xms1024m -Xmx1024m -XX:+PrintGCDetails (VM options) 虚拟机试图使用的最大内存 maxMemory:1029177344字节 981.5M 虚拟机初始化使用的最大内存 totalMemory:1029177344字节 981.5M 电脑内存总大小:17083187200字节 16291.796875M maxMemory/totalMemory : 1.0 totalPhysicalMemorySize/maxMemory : 16.598876082526743 totalPhysicalMemorySize/totalMemory : 16.598876082526743 Heap PSYoungGen total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000) eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000) from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000) to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000) ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000) object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000) Metaspace used 3294K, capacity 4500K, committed 4864K, reserved 1056768K class space used 355K, capacity 388K, committed 512K, reserved 1048576K # 305664k + 699392k = 981.5M # 新生代 + 老年代 = 虚拟机初始最大内存 # Metaspace 元空间在逻辑上存在, 在物理上不存在 # -Xms4m -Xmx4m -XX:+PrintGCDetails [GC (Allocation Failure) [PSYoungGen: 512K->504K(1024K)] 512K->520K(3584K), 0.0010041 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1014K->504K(1024K)] 1030K->636K(3584K), 0.0011249 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1016K->512K(1024K)] 1148K->788K(3584K), 0.0007922 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 虚拟机试图使用的最大内存 maxMemory:3670016字节 3.5M 虚拟机初始化使用的最大内存 totalMemory:3670016字节 3.5M 电脑内存总大小:17083187200字节 16291.796875M maxMemory/totalMemory : 1.0 totalPhysicalMemorySize/maxMemory : 4654.799107142857 totalPhysicalMemorySize/totalMemory : 4654.799107142857 Heap PSYoungGen total 1024K, used 699K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000) eden space 512K, 36% used [0x00000000ffe80000,0x00000000ffeaec30,0x00000000fff00000) from space 512K, 100% used [0x00000000fff00000,0x00000000fff80000,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 2560K, used 276K [0x00000000ffc00000, 0x00000000ffe80000, 0x00000000ffe80000) object space 2560K, 10% used [0x00000000ffc00000,0x00000000ffc45060,0x00000000ffe80000) Metaspace used 3295K, capacity 4500K, committed 4864K, reserved 1056768K class space used 355K, capacity 388K, committed 512K, reserved 1048576K
分析 OOM 的软件 还有 MAT(基本不用了)
# VM options -Xms # 设置初始化内存分配大小 /164 -Xmx # 设置最大分配内存,默认1/4 -XX:+PrintGCDetails # 打IGc垃圾回收信总 -XX:+HeapDumpOnOutOfMemoryError # OOM DUMP
# -Xms4m -Xmx4m -XX:+PrintGCDetails # 在代码中加入: String str = "1234567890"; while (true) { str += str; } [GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->528K(1536K), 0.0011578 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 998K->504K(1024K)] 1038K->616K(1536K), 0.0008813 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1016K->504K(1024K)] 1128K->744K(1536K), 0.0010449 secs] [Times: user=0.14 sys=0.00, real=0.00 secs] 虚拟机试图使用的最大内存 maxMemory:1572864字节 1.5M 虚拟机初始化使用的最大内存 totalMemory:1572864字节 1.5M [GC (Allocation Failure) [PSYoungGen: 937K->504K(1024K)] 1177K->896K(1536K), 0.0006825 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 504K->419K(1024K)] [ParOldGen: 392K->302K(512K)] 896K->721K(1536K), [Metaspace: 3291K->3291K(1056768K)], 0.0058845 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 832K->504K(1024K)] 1135K->982K(1536K), 0.0012532 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 504K->475K(1024K)] [ParOldGen: 478K->404K(512K)] 982K->879K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0058477 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen: 817K->634K(1024K)] [ParOldGen: 404K->324K(512K)] 1222K->959K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0048121 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [PSYoungGen: 634K->622K(1024K)] [ParOldGen: 324K->319K(512K)] 959K->941K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0046653 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] # GC GC GC GC FGC ... Heap PSYoungGen total 1024K, used 685K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000) eden space 512K, 74% used [0x00000000ffe80000,0x00000000ffedfb10,0x00000000fff00000) from space 512K, 59% used [0x00000000fff00000,0x00000000fff4ba18,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 512K, used 319K [0x00000000ffe00000, 0x00000000ffe80000, 0x00000000ffe80000) object space 512K, 62% used [0x00000000ffe00000,0x00000000ffe4fc10,0x00000000ffe80000) Metaspace used 3333K, capacity 4500K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K Exception in thread "main" java.lang.OutOfMemoryError: Java heap space # !!!!!! OOM at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) at java.lang.StringBuilder.append(StringBuilder.java:136) at jvm.Test.main(Test.java:35)
内存设置
# VM options -Xms # 设置初始化内存分配大小 /164 -Xmx # 设置最大分配内存,默认1/4 -XX:+PrintGCDetails # 打IGc垃圾回收信总 -XX:+HeapDumpOnOutOfMemoryError # OOM DUMP
-Xms2m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
String str = "1234567890"; int count = 0; while (true) { str += str; count++; }
使用 jprofiler 分析OOM原因
- Current Object Set --> Biggest Objects --> 分析内存占用
- ThreadDump --> main -- > 定位行数
-Xms:初始堆大小。只要启动,就占用的堆大小。 -Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。 -Xmn:堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了 -Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。 # 当出现大量局部变量,递归时,会发生栈空间OOM(java.lang.StackOverflowError)之类的错误。 -XX:NewSize:设置新生代大小的绝对值。 -XX:NewRatio:设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。 # java.lang.OutOfMemoryError:PermGenspace这个OOM错误 需要合理调大PermSize和MaxPermSize大小。 #-XX:MaxPermSize:设置持久代大小, JDK1.8以后,这个参数被替换成了 MaxMetaspaceSize # 一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值, # 设置得比初始值要大,对于8G物理内存的机器来说,一般将这两个值都设置为256M 默认 20M 左右 -XX:MaxMetaspaceSize # 设置元空间最大大小 -XX:MetaspaceSize # 设置元空间大小 -XX:ThreadStackSize : 设置JVM栈内存 -XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。 -XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。 -XX:HeapDumpPath:导出堆的转储文件路径。 -XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。
当对象引用计数器为0时,即无其他对象引用,判定为死亡,可被回收,但是存在循环引用问题,导致对象一直存活。
对象的引用分类:
- 强引用,程序中普遍存在的的对象引用
- 软引用,SoftReference实现,内存溢出前回收
- 弱引用,WeakReference实现,下一次垃圾回收
- 虚引用,PhantomReference实现,形同虚设
对象的起死回生
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。
以下七种收集器,如果相互之间存在连线,便可以搭配使用
新生代采用复制算法,老年代采用标记-整理算法
优点:简单而高效
用途:
Serial 收集器的多线程版本(回收策略、回收算法等完全一样)
新生代采用复制算法,老年代采用标记-整理算法
特点:
默认的收集线程数与CPU核数相同,可通过参数-XX:ParallelGCThreads 指定线程收集数
(不建议修改)
运行在Server模式下的虚拟机的首要选择
除了Serial收集器外,只有它能与CMS收集器配合工作
Parallel Scavenge 收集器类似于 ParNew 收集器,是 Server 模式下的默认收集器
新生代采用复制算法,老年代采用标记-整理算法
特点:
使用多线程和“标记-整理”算法
在注重吞吐量已及CPU资源的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old 收集器
CMS ( Conrrurent Mark Sweep )
以获取最短回收停顿时间为目标,HotSpot虚拟机第一款真正意义上的并发收集器
运作过程:
优点:
缺点:
G1(Garbage First)
基本特性:
G1 对大对象的处理:
G1 垃圾收集过程:
由于主存与 CPU 处理器的运算能力之间有数量级的差距,所以在传统计算机内存架构中会引入高速缓存来作为主存和处理器之间的缓冲,CPU 将常用的数据放在高速缓存中,运算结束后 CPU 再讲运算结果同步到主存中。
使用高速缓存解决了 CPU 和主存速率不匹配的问题,但同时又引入另外一个新问题:缓存一致性问题
在多CPU的系统中(或者单CPU多核的系统),每个CPU内核都有自己的高速缓存,它们共享同一主内存(Main Memory)。当多个CPU的运算任务都涉及同一块主内存区域时,CPU 会将数据读取到缓存中进行运算,这可能会导致各自的缓存数据不一致。因此需要每个 CPU 访问缓存时遵循一定的协议,在读写数据时根据协议进行操作,共同来维护缓存的一致性。这类协议有 MSI、MESI、MOSI、和 Dragon Protocol 等。
为了使处理器内部的运算单元能够最大化被充分利用,处理器会对输入代码进行乱序执行处理,这就是处理器优化。
除了处理器会对代码进行优化处理,很多现代编程语言的编译器也会做类似的优化,比如像 Java 的即时编译器(JIT)会做指令重排序。
处理器优化其实也是重排序的一种类型,这里总结一下,重排序可以分为三种类型:
JMM的三个特征:
如果从更深层次看这三个问题,其实就是『缓存一致性』、『处理器优化』、『指令重排序』造成的。
所有的变量都存储在主内存(Main Memory)中
每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的拷贝副本
线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存
不同的线程之间无法直接访问对方本地内存中的变量
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现:
synchronized的特点
一个线程执行互斥代码过程如下:
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
volatile是第二种Java多线程同步的手段,根据JLS的说法,一个变量可以被volatile修饰,在这种情况下内存模型确保所有线程可以看到一致的变量值
class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
加上volatile可以将共享变量i和j的改变直接响应到主内存中,这样保证了i和j的值可以保持一致,然而我们不能保证执行two方法的线程是在i和j执行到什么程度获取到的,所以volatile可以保证内存可见性,不能保证并发有序性(不具有原子性)。
如果没有volatile,则代码执行过程如下:
-Xms:初始堆大小。只要启动,就占用的堆大小。 -Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。 -Xmn:堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了 -Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。 -XX:NewSize:设置新生代大小的绝对值。 -XX:NewRatio:设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。 -XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。 # java.lang.OutOfMemoryError:PermGenspace这个OOM错误 需要合理调大PermSize和MaxPermSize大小。 #-XX:MaxPermSize:设置持久代大小, JDK1.8以后,这个参数被替换成了 MaxMetaspaceSize # 一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值, # 设置得比初始值要大,对于8G物理内存的机器来说,一般将这两个值都设置为256M 默认 20M 左右 -XX:MaxMetaspaceSize # 设置元空间最大大小 -XX:MetaspaceSize # 设置元空间大小 -XX:ThreadStackSize : 设置JVM栈内存 -XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。 -XX:HeapDumpPath:导出堆的转储文件路径。 -XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。
抓取内存快照
-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。 -XX:HeapDumpPath:导出堆的转储文件路径。
使用 jprofiler 分析OOM原因
- Current Object Set --> Biggest Objects --> 分析内存占用
- ThreadDump --> main -- > 定位行数
种类
双亲委派机制
沙箱安全机制
jdk1.8 之前 | jdk1.8 |
---|---|
堆和方法区连在了一起,但这并不能说堆和方法区是一起的,它们在逻辑上依旧是分开的。但在物理上来说,它们又是连续的一块内存。也就是说,方法区和前面讲到的Eden和老年代是连续的
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
方法区和永久代的关系很像Java中接口和类的关系,永久代是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。
# JDK1.8之前调节方法区大小: -XX:PermSize=N //方法区(永久代)初始大小 -XX:MaxPermSize=N //方法区(永久代)最大大小,超出这个值将会抛出OutOfMemoryError # JDK1.8开始方法区(HotSpot的永久代)被彻底删除了,取而代之的是元空间,元空间直接使用的是内存。参数设置: -XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小) -XX:MaxMetaspaceSize=N //设置Metaspace的最大大小
永久代和元空间内存使用上的差异:
表面上看是为了避免OOM异常。因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误
当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制
更深层的原因还是要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代,也不需要开发运维人员设置永久代的大小,但是运行良好。同时也不用担心运行性能问题了,在覆盖到的测试中, 程序启动和运行速度降低不超过1%,但是这点性能损失换来了更大的安全保障。