关于方法区和永久代:
在HotSpot JVM中,永久代就是方法区(JVM规范中称为方法区)。《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。在其他JVM上不存在永久代。JDK1.8及以后把永久带改成为元空间。
为啥做如此改变:
jvm在启动的时候会根据这些配置来分配一块连续的内存块,但是随着动态类加载的情况越来越多,这块内存我们变得不太可控,到底设置多大合适是每个开发者要考虑的问题,如果设置太小了,系统运行过程中就容易出现内存溢出,设置大了又总感觉浪费,尽管不会实质分配这么大的物理内存。基于这么一个可能的原因,于是metaspace出现了,希望内存的管理不再受到限制,也不要怎么关注元数据这块的OOM问题。
堆参数设置:
-
设置堆空间大小的参数
-Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
-X 是jvm的运行参数
ms 是memory start
-Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小 -
默认堆空间的大小
初始内存大小:物理电脑内存大小 1 / 64
最大内存大小:物理电脑内存大小 1 / 4 -
设置新生代/老年代比率
-XX:NewRatio=1(比率)
-
设置edan区和幸存者比率
-XX:SurvivorRatio=4(edan/survivor)
-
手动设置:-Xms600m -Xmx600m
开发中建议将初始堆内存和最大的堆内存设置成相同的值。 -
设置栈内存大小
-Xss:如-Xss128k
- 查看设置的参数:
方式一: jps 查看进程号, jstat -gc 进程id
方式二:添加堆参数打印详细信息 -XX:+PrintGCDetails
深入理解元空间(metaspace)
元空间的内存大小
元空间是方法区在HotSpot JVM 中的实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。
存储内容方法区存储 已被虚拟机加载的类信息、即时编译后的代码(方法字节码)、静态变量和常量等数据。
方法区无法满足内存分配需求时,也会报OOM异常,和堆的垃圾回收效率相比,方法区的回收效率实在是太低,但是此部分内存区域也是可以被回收的。
方法区的垃圾回收主要分为两种,分别是对废弃常量的回收(常量池的回收)和无用类的回收(类的卸载)。
当一个常量对象不在任何地方被利用的时候,则被标记为废弃常量,这个常量可以被回收。
方法区中的类需要满足以下三个条件才能被标记为无用类- Java堆中不存在该类的任何实例对象;
- 加载该类的类加载器已经被回收;
- 该类对象java.lang.Class对象不在任何地方被引用,且无法在任何地方通过反射该类的方法。
满足上述三个条件类也不一定会被回收,例如HotSpot虚拟机提供的参数进行控制是否回收。
永久带又叫Perm区,只存在于hotspot jvm中,并且只存在于jdk7和之前的版本中,jdk8中已经彻底移除了永久带,jdk8中引入了一个新的内存区域叫metaspace(元空间),类的元信息被存储在元空间中。
2.堆垃圾回收 2.1空间分配新生代(1/3堆空间)其中Eden占8/10,S0占1/10,S1占1/10
老年代(2/3堆空间)
Java中的堆也是GC收集垃圾的主要区域。GC分为两种:Minor GC(复制算法)、Full GC(标记清除算法)(或者称为Major GC)
垃圾回收机制:
2.2年轻代GC(Minor GC)触发机制:- 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC.(每次 Minor GC会清理年轻代的内存。)
- 因为Java对象大多都是具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
- Minor GC会引发 STW(暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行)。
-
指发生在老年代的GC,对象从老年代消失时,我们说“Major GC” 或“ Full GC”发生了。
-
老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。
-
Major GC 的速度一般会比Minor GC慢10倍以上,STW的时间更长。
-
如果Full GC后,内存还不足,就报OOM了。
-
标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
3.2 STW的时间CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
3. 3垃圾碎片CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
3.4垃圾回收的过程不一样 CMS收集器 G1收集器
-
初始标记 1.初始标记
-
并发标记 2. 并发标记
-
重新标记 3. 最终标记
-
并发清除 4. 筛选回收
注解:筛选回收
首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region。
G1回收器官方给G1设定的目标是在延迟可控的情况下获取尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任和期望。
G1是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存收集器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能。
4.垃圾收集流程Jvm在进行GC时,并非每次对三个内存(新生代、老年代、方法区)区域一起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM,它的回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)。
- 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
-
a. 新生代收集(Minor GC/ Young GC):只是新生代(Eden、S0、S1)的垃圾收集。
-
b.老年代收集(Major GC/ Old GC): 只是老年代的垃圾收集。
- 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。
1)当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC.(每次 Minor GC会清理年轻代的内存。)
2)因为Java对象大多都是具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。这一定义即清晰又易于理解。
3)Minor GC会引发 STW(暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行)。
老年代GC(Major GC/Full GC)触发机制:1)指发生在老年代的GC,对象从老年代消失时,我们说“Major GC” 或“ Full GC”发生了。
2)出现Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge 收集器的收集策略里就有直接进行Major GC的策略选择过程)。也就是在老年代空间不足时,会先触发Minor GC。如果之后空间不足,则触发Major GC。
3)Major GC 的速度一般会比Minor GC慢10倍以上,STW的时间更长。
4)如果Major GC后,内存还不足,就报OOM了。
Full GC触发机制:(1) 调用 System.gc()时,系统建议执行Full GC,但是不必执行。
(2)老年代不足。
(3) 方法区空间不足。
(4)通过Minor GC后进入老年代的平均大小大于老年代可用内存。
(5) 由Eden区、survivor space0区向survivor space1区赋值时,对象大于s0可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
注意:
Full GC 是开发或调优中尽量要避免的。这样暂停时间会短一些。
1、准备在新生代进行minorGC时,首先检查“老年代”最大连续空间区域的大小是否大于新生代所有对象的大小
2、如果老年代能装下所有新生代对象,minorGc没有风险,进行minorGC
3、老年代无法装下,垃圾收集器进行一次预测:根据以往minorGC过后存活对象的平均数来预测这次minorGC后存活对象的平均数。
(1)以往平均数小于当前老年代最大的连续空间,就进行minorGC,
(2)大于,则进行一次fullGC,通过清楚老年代中废弃数据来扩大老年代空闲空间,以便给新生代做担保。
注意事项:
-
分配担保是老年代为新生代作担保;
-
新生代中使用“复制”算法实现垃圾回收,老年代中使用“标记-清除”或“标记-整理”算法实现垃圾回收,只有使用“复制”算法的区域才需要分配担保,因此新生代需要分配担保,而老年代不需要分配担保。
- 虚拟机栈中引用的对象
- 比如:各个线程被调用的方法中使用的参数、局部变量等。
- 本地方法栈内JNI(常说的本地方法)引用的对象。
- 方法区中类静态属性引用的对象
- 比如:Java类的引用类型静态变量
- 方法区中常量引用的对象
- 比如:字符串常量池(String Table)里的引用。
- 所有被同步锁synchronized持有的对象,等。
| 类型 | 回收时间 | 应用场景 |
|---|---|---|
| 强引用 | 一直存活,除非GC Roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
| 软引用 | 内存不足的时候会被回收 | 一般用在对内存非常敏感的资源上,用作缓冲的比较多,例如:网页缓存,图片缓存等 |
| 弱引用 | 只能存活到下一个GC前 | 生命周期很短的对象,比如ThreadLocal中的key |
| 虚引用 | 随时会被收回,创建了可能很快就会被回收 | 被JVM团队内部用来跟踪JVM垃圾回收活动 |
线程私有,生命周期与线程相同
作用:描述Java方法执行的内存模型
- 局部变量表(Local Variables)局部变量表也被称之为局部变量数组或本地变量表。定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference) ,以及returnAddress类型。
- 操作数栈(Operand Stack)(或表达式栈)
- 动态链接(Dynamic linking)(或指向运行时常量池的方法引用)
- 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
- 一些附加信息
栈优势是存取速度比堆快,仅次于直接位于CPU中寄存器,但缺点存在栈中的数据大小和生命周期必须是确定,缺乏灵活性,另外栈内存数据可以共享。
每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
8.1 设置栈的大小我们可以使用—Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大深度。
在idea中的编辑:单位可为k/K,m/M,g/G.
9.栈和堆存储在计算机RAM中,堆内存和栈内存及C++内存分配现在操作系统内存管理主流的操作系统(Windows,Linux)都采用的虚拟机内存管理方式,具体说就是:页式管理、段式管理、段页式管理。操作系统分配资源的单位是进程,所以,内存管理的过程也是以进程为单位的。
进程的地址空间这里的地址空间指的都是虚拟地址空间。
要将进程的地址空间分为两部分:用户空间和内核空间,内核空间中存放内核的代码和数据,这个是用户空间不能随意访问的,只能通过系统调用陷入内核空间。用户空间存放用户的代码和数据:
在Linux32位系统下,将1G用作内核空间,3G用于用于用户空间。
10. 方法区中保存的内容 10.1 类型信息对每个加载的类型,jvm必须在方法区中存储以下类型信息:
1.类型的完整有效名;
2.直接父类的完整有效名(除非当前类型是Interface 或 java.lang.Object,两者都没有父类);
3.类型的修饰符(public,abstract, final等);
总结来说就是一个类上户口,需要知道这个类的名字叫什么、父亲是谁、有没有实现接口、 权限是什么。
10.2 类型的常量池已加载的类型(每一个class文件中),都维护着一个常量池(不同于方法区的运行时常量池),里面存放着编译时期生成的各种字面值(像string、基本数据类型、以及它们的包装类的值、及final修饰的变量,即在编译期间就确定下来的值)和符号引用(对类型、域、方法的引用);这个常量池的内容在类加载的时候,被复制到方法区的运行时常量池;池中的数据项类似数组项一样,是通过索引访问的。
10.3 域信息类型的所有域的相关信息(域名、域类型、域修饰符如
声明的顺序、修饰符、返回值类型、名字、参数列表(有序保存)、异常表(方法抛出的异常)、方法字节码(native、abstract方法除外)、操作数栈和局部变量表大小。
10.5 类变量jvm使用一个类之前,必须在方法区中为每个非final类变量分配空间,非final类变量存储在定义它的类中;
PS:final类变量不存储在这里,由于final的不可改变性,final类变量的值在编译期间就被确定了,因此保存在类的常量池里面,然后在加载类的时候,复制进方法区的运行时常量池里面,final类变量存储在运行时常量池里面,每一个使用它的类保存着一个对其的引用。
10.6 对类加载器的引用jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的,如果一个类型是由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。jvm在动态链接的时候需要这个信息,当解析一个类型到另一个类型的引用的时候,jvm需要保证这两个类型的类加载器是相同的,这对jvm区分名字空间的方式是至关重要的。
10.7 对Class类的引用jvm为每个加载的类都创建一个java.lang.Class的实例(存储在堆上),而jvm必须以某种方式把Class的这个实例和存储在方法区中的类型数据(类的元数据)联系起来。
10.8方法表java语言始终是把安全放在首位,难免牺牲效率为代价的,为了提高应用效率jvm还添加了法表,jvm可以通过方法表快速激活实例方法。
计算机网络
Java基础
垃圾回收
IO流
Mysql
异常
Spring



