目录

  • 一,JVM的位置
  • 二,JVM的体系结构
  • 三,类加载器
  • 四,双亲委派机制:安全
  • 五,Java历史-沙箱安全机制
  • 六,Native
  • 七,PC寄存器
  • 八,方法区 Method Area
  • 九,栈与程序计数器
    • (1)栈
    • (2)栈运行原理:栈帧
    • (3)程序计数器
    • (4)栈,堆,方法区交互
  • 十,三种JVM
  • 十一,堆
  • 十二,新生区、老年区、永久区
    • (1)新生区、老年区
    • (2)永久区
    • (3)分代回收策略
  • 十三,堆内存调优
    • (1)扩大堆内存:
    • (2)OOM故障
    • (3)在一个项目中,突然出现了OOM故障,那么该如何排除 研究为什么出错
    • (4)使用JPofiler工具分析OOM原因
  • 十四,GC
    • (1)GC
      • GC题
        • ●JVM的内存模型和分区~详细到每个区放什么?
        • ●堆里面的分区有哪些? Eden, form, to, 老年区,说说他们的特点!
        • ●轻GC和重GC分别在什么时候发生?
    • (2)GC常用算法
      • JVM三种垃圾回收算法及引用计数器
        • 1,标记-复制算法
        • 2,标记-清除算法
        • 3,标记-整理算法
        • 4,引用计数器
      • 垃圾回收算法总结
    • (3)JMM
  • 十五,JVM题
    • ●请你谈谈你对JVM的理解?
    • ●java8虚拟机和之前的变化更新?
    • ●什么是OOM, 怎么分析?OutOfMemoryError:内存不足错误
    • ●什么是栈溢出StackOverFlowError,怎么分析?StackOverflowError:栈溢出错误
    • ●JVM的常用调优参数有哪些?
    • ●内存快照如何抓取,怎么分析Dump文件?
    • ●谈谈JVM中,类加载器你的认识

一,JVM的位置

二,JVM的体系结构


三,类加载器

作用:加载Class文件~

类加载机制 : 虚拟机把描述 类 的数据从class文件 加载 到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java 类 型。

类加载流程(三个阶段):
1.加载阶段
将编译好的class文件加载到内存中(方法区),然后会生成一个代表这个类的Class对象。
2.链接阶段
会为静态变量分配内存并设置默认值。
3.初始化阶段
执行类构造器()进行初始化赋值。


java自带的类加载器:
1.虚拟机自带的加载器
2.启动类(根)加载器
启动类加载器(Bootstrap ClassLoader):又名根类加载器或引导类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径,例:rt.jar
3.扩展类加载器
拓展类加载器(Extension ClassLoader):负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径

4.应用程序加载器
系统类加载器(Application ClassLoader):又名应用类加载器,负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器

四,双亲委派机制:安全

在Java的类加载机制中,默认的就是双亲委派机制,这种委派机制就是将类加载器进行优先级分层,在触发类加载的时候,当前类加载器会从低级层层往上委托父类加载器去加载,每层类加载器在加载时会判断是否已经加载过,如果加载过就不在重复加载,这样设计能够避免类重复加载、核心类被篡改等情况发生。


五,Java历史-沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox) ,
 什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
 所有的Java程序运行都可以指定沙箱,可以定制安全策略。
 在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。如下图所示JDK1.0安全模型

但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型

在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示

前最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示最新的安全模型(jdk 1.6)

组成沙箱的基本组件
●字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
●类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
  它防止恶意代码去干涉善意的代码;//双亲委派机制
  它守护了被信任的类库边界;
  它将代码归入保护域,确定了代码可以进行哪些操作。//沙箱安全机制
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成, 每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
 类装载器采用的机制是双亲委派模式。
 1.从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
 2.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
●存取控制器(access controller) :存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。 new Robot();//机器人类,可以操作电脑
●安全管理器(security manager) : 是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
●安全软件包(security package) : java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
  安全提供者
  消息摘要
  数字签名 keytools
  加密
  鉴别

六,Native

程序中使用:private native void start0();
native :凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
会进入本地方法栈
调用本地方法本地接口 JNI (Java Native Interface)

JNI作用:开拓Java的使用,融合不同的编程语言为Java所用!最初: C、C++
Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序
它在内存区域中专门开辟了一块标记区域: Native Method Stack,登记native方法
在最终执行的时候,加载本地方法库中的方法通过JNI
例如:Java程序驱动打印机,管理系统,掌握即可,在企业级应用比较少

Native Method Stack
 它的具体做法是Native Method Stack中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。[本地库]
Native Interface本地接口
 本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了块区域处理标记为native的代码,它的具体做法是在Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。
 目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用Web Service等等

七,PC寄存器

程序计数器: Program Counter Register
 每个线程都有一个程序计数器,是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令, 是一个非常小的内存空间,几乎可以忽略不计

八,方法区 Method Area

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

九,栈与程序计数器

(1)栈

栈:先进后出
队列:先进先出( FIFO : First Input First Output )

栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over!
栈内存中::8大基本类型+对象引用+实例的方法

而栈通常就是指这里的虚拟机栈,或者更多情况下只是指虚拟机栈中局部变量表部分

(2)栈运行原理:栈帧

(3)程序计数器

(4)栈,堆,方法区交互

十,三种JVM

●Sun公司 Java HotSpot™ 64-Bit Server VM (build 25.121-b13, mixed mode)
●BEA JRockit
●IBM J9VM

十一,堆

java堆是虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
java堆是垃圾收集器管理的内存区域
Heap, 一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类, 方法,常量,变量~,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
●新生区(伊甸园区) Young/New
●养老区old
●永久区Perm

GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space
永久存储区里存放的都是Java自带的 例如lang包中的类 如果不存在这些,Java就跑不起来了
在JDK8以后,永久存储区改了个名字(元空间)

十二,新生区、老年区、永久区

收集器应该将java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储

(1)新生区、老年区

●类:诞生和成长的地方,甚至死亡;
●伊甸园,所有的对象都是在伊甸园区new出来的!
●幸存者区(0,1)

伊甸园满了就触发轻GC,经过轻GC存活下来的就到了幸存者区,幸存者区满之后意味着新生区也满了,则触发重GC,经过重GC之后存活下来的就到了养老区。
经过研究,99%的对象都是临时对象

(2)永久区

这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境~ 这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存
●jdk1.6之前:永久代,常量池是在方法区;
●jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
●jdk1.8之后:无永久代,常量池在元空间




元空间:逻辑上存在,物理上不存在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中

(3)分代回收策略


1.绝大多数刚刚被创建的对象会存放在Eden区
2.当Eden区第一次满的时候,会触发MinorGC(轻GC)。首先将Eden区的垃圾对象回收清除,并将存活的对象复制到S0,此时S1是空的。
3.下一次Eden区满时,再执行一次垃圾回收,此次会将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1,此时S0变为空。
4.如此反复在S0和S1之间切换几次(默认15次)之后,还存活的对象将他们转移到养老区中。
5.当养老区满了时会触发FullGC(全GC)

MinorGC 轻量级GC

使用的算法是复制算法
年轻代堆空间紧张时会被触发
相对于全收集而言,收集间隔较短
FullGC 重量级GC

使用的算法一般是标记压缩算法
当老年代堆空间满了,会触发全收集操作
可以使用 System.gc()方法来显式的启动全收集
全收集非常耗时

十三,堆内存调优

(1)扩大堆内存:


(2)OOM故障

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

[Full GC (Allocation Failure) 
[PSYoungGen: 0K->0K(14848K)] 
[ParOldGen: 881116K->881061K(989184K)] 881116K->881061K(1004032K), 
[Metaspace: 4025K->4025K(1056768K)], 0.1890297 secs] 
[Times: user=0.95 sys=0.00, real=0.19 secs] 

(3)在一个项目中,突然出现了OOM故障,那么该如何排除 研究为什么出错

●能够看到代码第几行出错:内存快照分析工具,MAT, Jprofiler
●Dubug, 一行行分析代码!
MAT, Jprofiler作用
●分析Dump内存文件,快速定位内存泄露;
●获得堆中的数据
●获得大的对象~

(4)使用JPofiler工具分析OOM原因

JPofiler:JProfiler是一个重量级的JVM监控工具,提供对JVM精确监控,其中堆遍历、CPU剖析、线程剖析看成定位当前系统瓶颈的得力工具。
添加参数运行程序:
-Xms1m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError:当出现OOM错误,会生成一个dump文件(进程的内存镜像)


问题查看:

看哪一行代码出现问题:

命令参数详解
// -Xms设置初始化内存分配大小/164
// -Xmx设置最大分配内存,默以1/4
// -XX: +PrintGCDetails // 打印GC垃圾回收信息
// -XX: +HeapDumpOnOutOfMemoryError //oom DUMP

十四,GC

(1)GC


JVM在进行GC时,并不是对这三个区域统一回收。 大部分时候,回收都是新生代~
●新生代
●幸存区(form,to)
●老年区
GC两种类:轻GC (普通的GC), 重GC (全局GC)

GC题

●JVM的内存模型和分区~详细到每个区放什么?

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念。java内存模型中分为主内存和工作内存。主内存里面存储着所有变量,主内存是共享内存区域,所有线程都可以访问。每一个线程都私有一个工作内存,工作内存里面保存着主内存里面变量值的副本,线程对变量的操作都是在工作内存中完成,操作结束后再放回主内存。
操作系统中,一般CPU都会从内存取数据到寄存器,然后进行处理,但由于内存的处理速度远远低于CPU,导致CPU在处理指令时往往花费很多时间在等待内存做准备工作,于是在寄存器和主内存间添加了CPU缓存,CPU缓存比较小,但访问速度比主内存快得多。Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间。

jvm管理的内存区域包括以下几个区域:
栈区:
栈分为java虚拟机栈和本地方法栈

  1. 重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同。

  2. 每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程。

  3. 通常说的栈就是指局部变量表部分,存放编译期间可知的8种基本数据类型,及对象引用和指令地址。局部变量表是在编译期间完成分配,当进入一个方法时,这个栈中的局部变量分配内存大小是确定的。

  4. 会有两种异常StackOverFlowError和 OutOfMemoneyError。当线程请求栈深度大于虚拟机所允许的深度就会抛出StackOverFlowError错误;虚拟机栈动态扩展,当扩展无法申请到足够的内存空间时候,抛出OutOfMemoneyError。

  5. 本地方法栈 为虚拟机使用到本地方法服务(native)

堆区:

  1. 堆被所有线程共享区域,在虚拟机启动时创建,唯一目的存放对象实例。

  2. 堆区是gc的主要区域,通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区,放新创建对象,From survivor 和 To survivor 保存gc后幸存下的对象,默认情况下各自占比 8:1:1。

  3. 会有异常OutOfMemoneyError

方法区:

  1. 被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanmentgeneration)

  2. 垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载。

  3. 常量池具有一定的动态性,里面可以存放编译期生成的常量;运行期间的常量也可以添加进入常量池中,比如string的intern()方法。

程序计数器:

  1. 当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,线程恢复等都是依赖计数器来完成。

  2. Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。

  3. 唯一一块Java虚拟机没有规定任何OutofMemoryError的区块

●堆里面的分区有哪些? Eden, form, to, 老年区,说说他们的特点!

堆中的分区主要分为

1,新生区;它又分为幸存1区和幸存2区,伊甸园区.
2,老年区
首先新生区:大部分的对象都是在伊甸园区创建并回收的,回收后幸存的对象会被放到幸存区(to)区,幸存区1,2他俩是互相交换的,这其中牵涉到了复制算法,
复制算法主要被应用在新生区中的幸存区,具体流程就是在幸存区中将活的对象进行复制到另一个幸存区中,留下来的对象就会被清理掉,不断地交换.这样做的缺点就是占用空间大,用到的空间只有开辟的1/2.
复制算法主要是根据GCroot判断一个对象是否是垃圾.
/*
要进行垃圾回收,如何判断一个对象是否可以被回收?
引用计数法
很难解决对象之间的循环引用问题

枚举根节点做可达性分析
通过一系列名为“GC Roots”的对象作为起始点,从“GC Roots”对象开始向下搜索,如果一个对象到“GC Roots”没有任何引用链相连,说明此对象可以被回收。
*/
当幸存区中经过十五次GC还未被清除掉的对象会进入老年区.

老年代一般存放的是比较大的对象和存活时间很久的对象因此老年代用标记整理算法,老年代容量满后会启用重GC.

●轻GC和重GC分别在什么时候发生?

Minor GC
此时如果新生的对象无法在 Eden 区创建(Eden 区无法容纳) 就会触发一次Young GC 此时会将 S0 区与Eden 区的对象一起进行可达性分析,找出活跃的对象,将它复制到 S1 区并且将S0区域和 Eden 区的对象给清空,这样那些不可达的对象进行清除,并且将S0 区 和 S1区交换。
但是这里会产生一个问题,Q:为啥会有两个 Survivor 区?
A: 因为假设设想一下只有一个 Survibor 区 那么就无法实现对于 S0 区的垃圾收集,以及分代年龄的提升。
Major GC
发生在老年代的GC ,基本上发生了一次Major GC 就会发生一次 Minor GC。并且Major GC 的速度往往会比 Minor GC 慢 10 倍。
什么时候发生Major GC
既然我们已经知道了 Minor GC 是在 Eden 区快满的情况下才会触发
那么 Major GC 呢?:
~对于一个大对象,我们会首先在Eden 尝试创建,如果创建不了,就会触发Minor GC
~随后继续尝试在Eden区存放,发现仍然放不下
~尝试直接进入老年代,老年代也放不下
~触发 Major GC 清理老年代的空间
~放的下 成功
~放不下 OOM

(2)GC常用算法

●GC的算法有哪些?
标记清除法,标记整理,复制算法,引用计数器

JVM三种垃圾回收算法及引用计数器

1,标记-复制算法

让to区干净

我们首先一起来看一下复制算法的做法,复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。



●好处:没有内存的碎片~
●坏处:浪费了内存空间~ :多了一半空间永远是空to。假设对象100%存活(极端情况)
复制算法最佳使用场景:对象存活度较低的时候;新生区~

2,标记-清除算法

此算法执行分两阶段。第一阶段从引用根节点开始标记可回收对象,第二阶段遍历整个堆,统一回收掉所有被标记的对象
优点:不会浪费内存空间
缺点:此算法需要暂停整个应用,同时,会产生内存碎片

3,标记-整理算法


根据老年代的特点,有人对“标记 - 清除”进行改进,提出了“标记 - 整理”算法。“标记 - 整理”算法的标记过程与“标记 - 清除”算法相同,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

4,引用计数器

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时,计数器值就减一,任何时刻计数器为零的对象就是不可能再被使用的

垃圾回收算法总结

内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:复制算法=标记压缩算法>标记清除算法

内存利用率:标记压缩算法=标记清除算法>复制算法
没有最好的算法,只有最合适的算法---->GC:分带收集算法

年轻代::
存活率低
复制算法
老年代:
区域大:存活率
标记清除(内存碎片不是太多)+标记压缩混合实现

(3)JMM

https://blog.csdn/m0_50507927/article/details/116887710?spm=1001.2014.3001.5501:
十四,JMM
1.什么是JMM?

JMM :(java Memory Model 缩写)java内存模型

2.它是干什么的?

解决共享对象可见性的问题:volilate

十五,JVM题

推荐文章:https://thinkwon.blog.csdn/article/details/104390752

●请你谈谈你对JVM的理解?

JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。

java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。

JVM执行程序的过程 :

I.加载.class文件

II.管理并分配内存

III.执行垃圾收集

JRE(java运行时环境)由JVM构造的java程序的运行环境

●java8虚拟机和之前的变化更新?

Java8和以后版本中JVM的内存结构慢慢发生了变化

堆和方法区连在了一起,但这并不能说堆和方法区是一起的,它们在逻辑上依旧是分开的。但在物理上来说,它们又是连续的一块内存。也就是说,方法区和前面讲到的Eden和老年代是连续的。

之前:

java8虚拟机

永久代(PermGen)
“永久代(Permanet Generation,也称PermGen)”。对于习惯了在HotSpot虚拟机上开发、部署的程序员来说,很多都愿意将方法区称作永久代。
本质上来讲两者并不等价,仅因为Hotspot将GC分代扩展至方法区,或者说使用永久代来实现方法区。在其他虚拟机上是没有永久代的概念的。也就是说方法区是规范,永久代是Hotspot针对该规范进行的实现。
理解上面的概念之后,我们对Java7及以前版本的堆和方法区的构造再进行一下变动。
再重复一遍就是对Java7及以前版本的Hotspot中方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的。
永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。
但在Java7中永久代中存储的部分数据已经开始转移到Java Heap或Native Memory中了。比如,符号引用(Symbols)转移到了Native Memory;字符串常量池(interned strings)转移到了Java Heap;类的静态变量(class statics)转移到了Java Heap。
然后,在Java8中,时代变了,Hotspot取消了永久代。永久代真的成了永久的记忆。永久代的参数-XX:PermSize和-XX:MaxPermSize也随之失效。

元空间(Metaspace)
对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
本地内存(Native memory),也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。

Java8内存结构图进行调整
元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中“java.lang.OutOfMemoryError: PermGen space”这种错误。看上图中的方法区,是不是“膨胀”了。
默认情况下元空间是可以无限使用本地内存的,但为了不让它如此膨胀,JVM同样提供了参数来限制它使用的使用。

-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。

永久代为什么被替换了
表面上看是为了避免OOM异常。因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误。
当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制。
更深层的原因还是要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代,也不需要开发运维人员设置永久代的大小,但是运行良好。同时也不用担心运行性能问题了,在覆盖到的测试中, 程序启动和运行速度降低不超过1%,但是这点性能损失换来了更大的安全保障。

●什么是OOM, 怎么分析?OutOfMemoryError:内存不足错误

十三–>{(2) (3) (4) }

●什么是栈溢出StackOverFlowError,怎么分析?StackOverflowError:栈溢出错误

如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出StackOverflowError
九–>{ (1) (2) }

●JVM的常用调优参数有哪些?

推荐:https://cloud.tencent/developer/article/1198524
十三,堆内存调优

●内存快照如何抓取,怎么分析Dump文件?

十三,(4)使用JPofiler工具分析OOM原因

●谈谈JVM中,类加载器你的认识

三,类加载器

更多推荐

jvm学习