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读书笔记--内存管理相关推荐

  1. 深入理解 Jvm 读书笔记(一)

    Jvm 内存管理,GC,类文件架构相关 知识包括: jvm内存管理 jvm运行时数据区划分 jvm层对象的创建过程 对象的内存布局 对象的访问定位 垃圾收集器与内存分配策略 对象已死的判定及引用分类 ...

  2. 《现代操作系统》第3章读书笔记--内存管理(未完成)

    写在前面:本文仅供个人学习使用,如有侵权,请联系删除.文章中所用图片绝大多数来源于<现代操作系统(第4版)>,请读者支持原版. 内存(RAM) 是计算机中一种需要认真管理的重要资源.一个事 ...

  3. 深入理解JVM读书笔记二: 垃圾收集器与内存分配策略

    3.2对象已死吗? 3.2.1 引用计数法 给对象添加一个引用计数器,每当有一个地方引用它的地方,计数器值+1:当引用失效,计数器值就减1;任何时候计数器为0,对象就不可能再被引用了. 它很难解决对象 ...

  4. 深入理解JVM读书笔记--Class文件结构

    一.Class文件结构 1. Class文件是一组以8位字节为基础的二进制流,采用一种类似C语言结构体的伪代码类存储.它只有两种数据类型:无符号数和表. 2. 无符号数属于基本的数据类型,以u1,u2 ...

  5. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

  6. 一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)

    JVM虚拟机的内存管理 文章目录 JVM虚拟机的内存管理 JVM与操作系统 Java虚拟机规范和 Java 语言规范的关系 java虚拟机的内存管理 JVM整体架构 一.PC 程序计数器 二.虚拟机栈 ...

  7. [翻译]理解Unity的自动内存管理

    当创建对象.字符串或数组时,存储它所需的内存将从称为堆的中央池中分配.当项目不再使用时,它曾经占用的内存可以被回收并用于别的东西.在过去,通常由程序员通过适当的函数调用明确地分配和释放这些堆内存块.如 ...

  8. 《深入理解Java虚拟机》内存管理机制 部分 读书笔记

    内存管理 运行时的数据区包括: 程序计数器 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器. 每个下次都需要有一个独立的程序计数器,各线程间计数器互不影响,独立存储. 如果线程执行的 ...

  9. 计算机存储的四个区,JVM读书笔记篇一:如何管理4个G的“封地”

    开篇闲话: 王侯将相皆有封地,大小根据爵位高低而不同,等级森严.在计算机世界里,大boss(操作系统)很公平,给大家(进程)都分配了同样的内存大小(虽然这也是个假象).我们的主角JVM出生的那天,大b ...

最新文章

  1. [译]Professional ASP.NET MVC3(01)-Chapter 1:Getting Started(上)
  2. Kubernetes — 设计理念
  3. python整理excel数据-利用python整理需要的excel报表(上)
  4. AMD Athlon ⅡX2 240 K10 平台 超频 全记录
  5. 16个经典面试问题回答思路[求职者必看]
  6. 日期年月日的比较以及判断
  7. java查看上下文加载器_线程上下文类加载器
  8. DBeaver中event实验
  9. 用指针实现对二维数组元素的访问
  10. Spinner的简单实用
  11. xp无法远程计算机共享,解决XP局域网共享不能访问的问题
  12. windows安装hbase
  13. sql server 不是可以识别的 内置函数名称
  14. OC:跟随小码哥一起学习KVC
  15. 深入浅出移动直播技术之帧率、码率和分辨率
  16. 读书笔记--家庭教育1
  17. grpc加TLS加密和令牌认证
  18. windows平台下的oracle ORA-01031的解决方法
  19. 电商数据分析--常见的数据采集工具及方法
  20. 如何为雷电模拟器安装Burpsuite证书并抓包

热门文章

  1. 计算机学具制作,一种具有防护功能的计算机编程学具的制作方法
  2. python 案例串接_来撸串,一个案例轻松认识Python 字符串——翻转拼接游戏
  3. 微软采购amd服务器芯片,微软计划自研PC和服务器芯片 英特尔AMD股价应声下跌
  4. Leetcode-笔记-22.括号生成--递归
  5. iOS中几种数据持久化方案总结
  6. redis php数据插入失败,redis插入数据,恢复数据测试(禁止淘汰策略下恢复大于redis内存限制数据情况)...
  7. 犟泥巴php集训营,想要开发自己的PHP框架需要那些知识储备?
  8. sqlserver2008r2表复制原表_SQL Server 2008 R2 主从数据库同步
  9. 解决JQuery.Treeview在CI中无法加载查询函数的方法
  10. mqttnet 详解_MQTTnet 3.0.5学习笔记