深入理解JVM读书笔记--内存管理
2019独角兽企业重金招聘Python工程师标准>>>
一. Java的运行时数据区域
(1)程序计数器(线程私有):是一块较小的内存空间,它的作用是当前线程所执行字节码的行号指示器。字节码解释器就是通过计数器的值来获得下一条需要执行的指令。
如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是native方法,这个区域为空。
Java中的多线程为了能够获得正确的执行位置,每一个线程都需要一个独立的程序计数器,这块内存称为"线程私有内存"。
这也是唯一一个java虚拟机规范没有规定任何OutOfmemroyError的区域。
(2)虚拟机栈(线程私有):它与线程的生命周期相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(stack frame),用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
局部变量表存放基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用,returnAddress(字节码指令)类型。
这两个区域常见的异常信息:StackOverflowError:线程请求的深度超过虚拟机规定的深度;OutOfMemoryError:无法申请到足够的内存。
(3)本地方法栈(Native method Stack): 类似于虚拟机栈。
(4)堆(java heap)(线程共享): 所有线程共享的区域,java虚拟机启动的时候创建,用于存放对象实例。
虚拟机规范对堆的描述:所有的对象实例和数组都要在堆上分配,但是随着JIT技术和逃逸分析技术的成熟,栈上分配,标量替换优化技术将会导致一些微妙的变化,所有对象都分配在堆上渐渐变得不是那么绝对了。
它也是垃圾回收的主要区域。
(5)方法区(线程共享):用于存储已被加载的类的结构信息,常量,静态变量,即时编译后的代码等数据。
这个区域的垃圾回收目标主要是针对常量池的回收和对类型的卸载,但是回收次数较少。
(6)运行时常量池:方法区的一部分。用于存放编译期生成的各种字面量和符号引用。这部分区域是在类被JVM加载之后就创建出来的。
一般来讲除了保存class文件中描述的符号引用外,还以把翻译出来的直接引用也存在运行时数据区。
(7)直接内存:这不是虚拟机运行时数据区的一部分。NIO引入了一种基于通道与缓冲区的I/O方式,他可以使用Native函数库直接分配堆外的内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
二. 新创建的对象在内存中的分配
3. 实战OutOfMemoryException
a) 模拟Heap内存溢出
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
异常信息:
[GC [DefNew: 8192K->1024K(9216K), 0.0262916 secs] 8192K->4618K(19456K), 0.0263380 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] [GC [DefNew: 6401K->1024K(9216K), 0.0352715 secs] 9996K->9748K(19456K), 0.0353201 secs] [Times: user=0.05 sys=0.00, real=0.05 secs] [GC [DefNew: 7664K->7664K(9216K), 0.0000458 secs][Tenured: 8724K->10240K(10240K), 0.0796814 secs] 16388K->11929K(19456K), [Perm : 379K->379K(12288K)], 0.0798297 secs] [Times: user=0.08 sys=0.00, real=0.08 secs] [Full GC [Tenured: 10240K->8002K(10240K), 0.0838774 secs] 19456K->15528K(19456K), [Perm : 379K->379K(12288K)], 0.0839470 secs] [Times: user=0.09 sys=0.00, real=0.09 secs] [Full GC [Tenured: 8604K->8604K(10240K), 0.0847476 secs] 17820K->17820K(19456K), [Perm : 379K->379K(12288K)], 0.0850530 secs] [Times: user=0.08 sys=0.00, real=0.08 secs] [Full GC [Tenured: 8604K->8591K(10240K), 0.0913390 secs] 17820K->17807K(19456K), [Perm : 379K->373K(12288K)], 0.0914197 secs] [Times: user=0.09 sys=0.00, real=0.09 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2760) at java.util.Arrays.copyOf(Arrays.java:2734) at java.util.ArrayList.ensureCapacity(ArrayList.java:167) at java.util.ArrayList.add(ArrayList.java:351) at org.wk.core.jvm.HeapOOM.main(HeapOOM.java:14) Heap def new generation total 9216K, used 9216K [0x315e0000, 0x31fe0000, 0x31fe0000) eden space 8192K, 100% used [0x315e0000, 0x31de0000, 0x31de0000) from space 1024K, 100% used [0x31de0000, 0x31ee0000, 0x31ee0000) to space 1024K, 0% used [0x31ee0000, 0x31ee0000, 0x31fe0000) tenured generation total 10240K, used 8597K [0x31fe0000, 0x329e0000, 0x329e0000) the space 10240K, 83% used [0x31fe0000, 0x32845748, 0x32845800, 0x329e0000) compacting perm gen total 12288K, used 374K [0x329e0000, 0x335e0000, 0x369e0000) the space 12288K, 3% used [0x329e0000, 0x32a3d8f8, 0x32a3da00, 0x335e0000) ro space 10240K, 54% used [0x369e0000, 0x36f5e4a8, 0x36f5e600, 0x373e0000) rw space 12288K, 55% used [0x373e0000, 0x37a822a0, 0x37a82400, 0x37fe0000) |
b) 模拟虚拟机栈和本地方法栈内存溢出
-verbose:gc -Xss128k
stack length2404 Exception in thread "main" java.lang.StackOverflowError at org.wk.core.jvm.JavaStackSOF.stackLeak(JavaStackSOF.java:6) at org.wk.core.jvm.JavaStackSOF.stackLeak(JavaStackSOF.java:7) at org.wk.core.jvm.JavaStackSOF.stackLeak(JavaStackSOF.java:7) |
c) 运行时常量溢出(典型的方法是String.intern())
参数设置:-XX:PermSize -XX:MaxPermSize
-verbose:gc -XX:PermSize=10M -XX:MaxPermSize=10M
[GC 4416K->493K(15872K), 0.0041773 secs] [GC 4187K->666K(15872K), 0.0039332 secs] [GC 4588K->1441K(15872K), 0.0041589 secs] [Full GC 3212K->1829K(15872K), 0.0617274 secs] [Full GC 1829K->1829K(15936K), 0.0578372 secs] Exception in thread "main" [Full GC 1906K->666K(15936K), 0.0214695 secs] java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at org.wk.core.jvm.RunTimeConstantPoolOOM.main(RunTimeConstantPoolOOM.java:12) |
d) 方法区溢出
思路:产生大量的类填充方法区。如Spring或Hibernate中产生过多的代理类。
e) 本机直接内存溢出
参数:-XX:MaxDirectMemorySize制定。默认的是与java堆的最大值一样。
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at org.wk.core.jvm.DirectMemoryOOM.main(DirectMemoryOOM.java:15) |
二. 垃圾回收算法
1. 引用计数法
思路:给每一个对象添加一个引用计数器,如果有一个地方引用该对象时,计数器加一,当引用失效时,计数器减一,垃圾回收器只需要收集计数器为0的对象。
2. 根搜索算法
a) 思路:通过一系列的名为"GC Roots"的对象作为起点,从这些节点向下搜索,搜索的路径成为引用连(Reference Chain),当一个对象到"GC Roots"没有任何应用链相连,则证明此对象不可用,即为可回收对象。
b) Java中的GC Roots
i. 虚拟机栈(栈帧中的本地变量表)中的引用对象
ii. 方法区中的类静态属性引用的对象
iii. 方法区中的常量引用对象
iv. 本地方法中的JNI引用对象
3. Java中引用的概念
a) 强引用(Strong Reference):程序中普遍存在,类似"Object obj = new Object()",只要引用存在,GC就不会回收该对象。
b) 软引用(Soft Reference):描述一些还有用,但是不是必须的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列到回收范围进行二次回收。(JDK中有SoftReference的实现)
c) 弱引用(Weak Reference):描述比软引用更弱的引用对象。它的生命周期是下一次垃圾回收之前。(JDK中有WeakReference的实现)
d) 虚引用(Phantom Reference):虚引用不会对对象的生命周期产生影响,更无法通过虚引用获得一个对象的实例。为对象设置一个虚引用的目的就是垃圾回收器回收时获得一个通知。
4. 如何判定一个类是无用的类:
a) 该类的所有实例已被回收。
b) 该加载类的ClassLoader 已被回收。
c) 该加载类的java.lang.Class对象没有在任何地方使用,即不能通过反射访问此类。
5. 通过虚拟机参数查看类的引用情况:-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XXTraceClassUnLoading查看累的加载和卸载信息。
6. 垃圾收集算法
a) 标记清除算法:标记清除算法分为两个阶段:首先标记需要回收的所有对象,标记完成后清除掉所有被标记的对象。
该算法的缺点:
效率问题:标记和清除的效率都不高;
空间问题:标记清除后会产生大量不连续的内存碎片。
b) 复制算法:将内存分成大小相等的两块,每次自用其中的一块,没进行一次垃圾回收把未清除对象复制到另外一块上。
该算法的优点:不用考虑内存碎片的问题,每次只要移动堆指针,按顺序分配内存即可,实现简单,运行高效。
该算法的缺点:算法将原来的内存缩减为一般大小,代价太高。
c) 标记-整理算法
d) 分代收集算法
这种算法将java堆分为新生代和老年代。根据新生代和老年代的不同分别按照不同的算法进行收集。新生代使用复制算法,而老年代使用标记-清除或者标记-整理算法。
e)
7. 垃圾收集器
a) Serial收集器:这是一个单线程收集器,他在进行垃圾收集时必须停止所有的其他工作线程。
Serial收集器对于运行在Client模式下的桌面虚拟机来说是一个很好的选择。
(非常类似于java多线程中的Barrier或者CountDownLatch)
b) ParNew收集器:Serial收集器的多线程版本。
它是运行在Server模式下的新生代的收集器。
可用参数:-XX:SurvivorRatio, -XX:PretenurSizeThreshold, -XX:HandlePromotionFailure, -XX:UseConcMarkSweepGC, -XX:UseParNewGC, -XX:ParallelGCThreads
c) Parallel Scavenge收集器(吞吐量优先收集器):它的关注点是达到一个可控制的吞吐量。
可用参数:-XX:MaxGCPauseMillis, -XX:GCTimeRatilo(1-100的整数), -XX:+UseAdaptivePolicy(这是一个开关参数,这个参数打开后就不需要用户手工指定新生代,Eden,与Survivor的大小,虚拟机会根据性能的监控信息自动的这些参数,以提供最合适的停顿时间和最大的吞吐量,这称为GC自适应调节策略)。
d) Serial Old收集器:单线程收集器,采用"标记整理算法"。主要是Client模式下的地JVM应用。Server模式下的主要用途:在JDK1.5以及以前的版本中配合Parallel Scavenge收集器搭配使用;另一个作为CMS的后背预案,在并发收集发生Concurrent Mode Failure时使用。
e) Parallel Old收集器:Parallel Old的老年代版。使用多线程和"标记-整理"算法。
f) CMS收集器:一种以获取最短停顿时间为目标的收集器。它采用的是"标记-清除"算法。
工作过程:> 初始标记(initial mark)
> 并发标记(concurrent mark)
> 重新标记(remark)
> 并发清楚(concurrent sweep)
在初始标记和重新标记两个阶段需要"stop the world"。初始标记标记的是GC Roots能够直接关联的对象,并发标记是Roots Tracing的过程,重新标记是标记哪些产生变动的对象记录。
g) G1收集器
8. 垃圾收集常用的参数
三.内存分配与回收策略
1. 对象优先分配在Eden区
当Eden区没有足够的内存空间时,发起一次Monitor GC。
package org.wk.core.jvm; public class MinorGC { private static final int _1MB = 1024 * 1024; public static void testAllocation() { byte[] a1, a2, a3, a4; a1 = new byte[2 * _1MB]; a2 = new byte[2 * _1MB]; a3 = new byte[2 * _1MB]; a4 = new byte[4 * _1MB]; } public static void main(String[] arg) { testAllocation(); } } |
[GC [DefNew: 6487K->148K(9216K), 0.0094068 secs] 6487K->6293K(19456K), 0.0094970 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 4408K [0x315e0000, 0x31fe0000, 0x31fe0000) eden space 8192K, 52% used [0x315e0000, 0x31a08fe0, 0x31de0000) from space 1024K, 14% used [0x31ee0000, 0x31f053f0, 0x31fe0000) to space 1024K, 0% used [0x31de0000, 0x31de0000, 0x31ee0000) tenured generation total 10240K, used 6144K [0x31fe0000, 0x329e0000, 0x329e0000) the space 10240K, 60% used [0x31fe0000, 0x325e0030, 0x325e0200, 0x329e0000) compacting perm gen total 12288K, used 378K [0x329e0000, 0x335e0000, 0x369e0000) the space 12288K, 3% used [0x329e0000, 0x32a3e920, 0x32a3ea00, 0x335e0000) ro space 10240K, 54% used [0x369e0000, 0x36f5e4a8, 0x36f5e600, 0x373e0000) rw space 12288K, 55% used [0x373e0000, 0x37a822a0, 0x37a82400, 0x37fe0000) |
2. 大对象直接进入老年代。
虚拟机提供了一个参数:-XX:PretenureSizeThreshold,用来设定大对象直接分配到老年代。这个参数只对Serial和ParNew有效。
public static void testPretenureSizeThresHold() { byte[] a4 = new byte[4 * _1MB]; } |
Heap def new generation total 9216K, used 507K [0x315e0000, 0x31fe0000, 0x31fe0000) eden space 8192K, 6% used [0x315e0000, 0x3165ef38, 0x31de0000) from space 1024K, 0% used [0x31de0000, 0x31de0000, 0x31ee0000) to space 1024K, 0% used [0x31ee0000, 0x31ee0000, 0x31fe0000) tenured generation total 10240K, used 4096K [0x31fe0000, 0x329e0000, 0x329e0000) the space 10240K, 40% used [0x31fe0000, 0x323e0010, 0x323e0200, 0x329e0000) compacting perm gen total 12288K, used 378K [0x329e0000, 0x335e0000, 0x369e0000) the space 12288K, 3% used [0x329e0000, 0x32a3e9f8, 0x32a3ea00, 0x335e0000) ro space 10240K, 54% used [0x369e0000, 0x36f5e4a8, 0x36f5e600, 0x373e0000) rw space 12288K, 55% used [0x373e0000, 0x37a822a0, 0x37a82400, 0x37fe0000) |
3. 长期存活的对象将进入老年代
可以通过参数:-XX:MaxTenuringThreshold设置。
public static void testTenuringThreshold() { byte[] a1, a2, a3; a1 = new byte[_1MB / 4]; a2 = new byte[4 * _1MB]; a3 = new byte[4 * _1MB]; a3 = null; a3 = new byte[4 * _1MB]; } |
参数值为:1时。
[GC [Tenured: 8192K->4501K(10240K), 0.3121115 secs] 8791K->4501K(19456K), [Perm : 378K->378K(12288K)], 0.3121796 secs] [Times: user=0.02 sys=0.01, real=0.31 secs] Heap def new generation total 9216K, used 327K [0x315e0000, 0x31fe0000, 0x31fe0000) eden space 8192K, 4% used [0x315e0000, 0x31631f98, 0x31de0000) from space 1024K, 0% used [0x31de0000, 0x31de0000, 0x31ee0000) to space 1024K, 0% used [0x31ee0000, 0x31ee0000, 0x31fe0000) tenured generation total 10240K, used 8597K [0x31fe0000, 0x329e0000, 0x329e0000) the space 10240K, 83% used [0x31fe0000, 0x32845420, 0x32845600, 0x329e0000) compacting perm gen total 12288K, used 378K [0x329e0000, 0x335e0000, 0x369e0000) the space 12288K, 3% used [0x329e0000, 0x32a3eaf8, 0x32a3ec00, 0x335e0000) ro space 10240K, 54% used [0x369e0000, 0x36f5e4a8, 0x36f5e600, 0x373e0000) rw space 12288K, 55% used [0x373e0000, 0x37a822a0, 0x37a82400, 0x37fe0000) |
参数值为:15时。
4.动态对象年龄判断
如果Survivor中相同年龄的对象占到此空间的一半,那么改年龄和大于该年龄的对象放到老年区。
5. 空间分配担保
在发生Minor GC时,会计算每次进入老年区的平均大小是否大于老年区空间的剩余大小,如果大于则进行一次Full GC。
四.对象分配规则
1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
转载于:https://my.oschina.net/u/197668/blog/361222
深入理解JVM读书笔记--内存管理相关推荐
- 深入理解 Jvm 读书笔记(一)
Jvm 内存管理,GC,类文件架构相关 知识包括: jvm内存管理 jvm运行时数据区划分 jvm层对象的创建过程 对象的内存布局 对象的访问定位 垃圾收集器与内存分配策略 对象已死的判定及引用分类 ...
- 《现代操作系统》第3章读书笔记--内存管理(未完成)
写在前面:本文仅供个人学习使用,如有侵权,请联系删除.文章中所用图片绝大多数来源于<现代操作系统(第4版)>,请读者支持原版. 内存(RAM) 是计算机中一种需要认真管理的重要资源.一个事 ...
- 深入理解JVM读书笔记二: 垃圾收集器与内存分配策略
3.2对象已死吗? 3.2.1 引用计数法 给对象添加一个引用计数器,每当有一个地方引用它的地方,计数器值+1:当引用失效,计数器值就减1;任何时候计数器为0,对象就不可能再被引用了. 它很难解决对象 ...
- 深入理解JVM读书笔记--Class文件结构
一.Class文件结构 1. Class文件是一组以8位字节为基础的二进制流,采用一种类似C语言结构体的伪代码类存储.它只有两种数据类型:无符号数和表. 2. 无符号数属于基本的数据类型,以u1,u2 ...
- Linux内核笔记--内存管理之用户态进程内存分配
内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...
- 一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)
JVM虚拟机的内存管理 文章目录 JVM虚拟机的内存管理 JVM与操作系统 Java虚拟机规范和 Java 语言规范的关系 java虚拟机的内存管理 JVM整体架构 一.PC 程序计数器 二.虚拟机栈 ...
- [翻译]理解Unity的自动内存管理
当创建对象.字符串或数组时,存储它所需的内存将从称为堆的中央池中分配.当项目不再使用时,它曾经占用的内存可以被回收并用于别的东西.在过去,通常由程序员通过适当的函数调用明确地分配和释放这些堆内存块.如 ...
- 《深入理解Java虚拟机》内存管理机制 部分 读书笔记
内存管理 运行时的数据区包括: 程序计数器 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器. 每个下次都需要有一个独立的程序计数器,各线程间计数器互不影响,独立存储. 如果线程执行的 ...
- 计算机存储的四个区,JVM读书笔记篇一:如何管理4个G的“封地”
开篇闲话: 王侯将相皆有封地,大小根据爵位高低而不同,等级森严.在计算机世界里,大boss(操作系统)很公平,给大家(进程)都分配了同样的内存大小(虽然这也是个假象).我们的主角JVM出生的那天,大b ...
最新文章
- [译]Professional ASP.NET MVC3(01)-Chapter 1:Getting Started(上)
- Kubernetes — 设计理念
- python整理excel数据-利用python整理需要的excel报表(上)
- AMD Athlon ⅡX2 240 K10 平台 超频 全记录
- 16个经典面试问题回答思路[求职者必看]
- 日期年月日的比较以及判断
- java查看上下文加载器_线程上下文类加载器
- DBeaver中event实验
- 用指针实现对二维数组元素的访问
- Spinner的简单实用
- xp无法远程计算机共享,解决XP局域网共享不能访问的问题
- windows安装hbase
- sql server 不是可以识别的 内置函数名称
- OC:跟随小码哥一起学习KVC
- 深入浅出移动直播技术之帧率、码率和分辨率
- 读书笔记--家庭教育1
- grpc加TLS加密和令牌认证
- windows平台下的oracle ORA-01031的解决方法
- 电商数据分析--常见的数据采集工具及方法
- 如何为雷电模拟器安装Burpsuite证书并抓包
热门文章
- 计算机学具制作,一种具有防护功能的计算机编程学具的制作方法
- python 案例串接_来撸串,一个案例轻松认识Python 字符串——翻转拼接游戏
- 微软采购amd服务器芯片,微软计划自研PC和服务器芯片 英特尔AMD股价应声下跌
- Leetcode-笔记-22.括号生成--递归
- iOS中几种数据持久化方案总结
- redis php数据插入失败,redis插入数据,恢复数据测试(禁止淘汰策略下恢复大于redis内存限制数据情况)...
- 犟泥巴php集训营,想要开发自己的PHP框架需要那些知识储备?
- sqlserver2008r2表复制原表_SQL Server 2008 R2 主从数据库同步
- 解决JQuery.Treeview在CI中无法加载查询函数的方法
- mqttnet 详解_MQTTnet 3.0.5学习笔记