JVM简单入门
2021-09-20 10:50:44 0 举报
JVM简单入门
作者其他创作
大纲/内容
再次扫描对象,向一端移动所有存活的对象
引用变量dog
没有最优的算法,只有最合适的算法
垃圾存在方法区和堆,jvm调优主要是在堆
存活对象
养老区
引用计数法
伊甸园(Eden Space)
幸存0区
常量池
from
老年区
标记压缩
本地方法接口(JNI)java naitve method interface
幸存1区
伊甸园
1.扩大堆内存2.分析内存找问题 内存快照分析工具:MAT ,Jprofiler MAT ,Jprofiler 作用: 1.分析Dump内存文件,快速定位内存泄漏 2.获得堆中的数据 3.获得大的对象
元空间
Car Class(类模板)
伊甸园与养老区的过渡区
Class Loader
类加载器classloader
输入输出的参数
to
Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。栈内存的大小可以有两种设置,固定值和根据线程需要动态增长。在JVM栈这个数据区可能会发生抛出两种错误。1. StackOverflowError 出现在栈内存设置成固定值的时候,当程序执行需要的栈内存超过设定的固定值会抛出这个错误。2. OutOfMemoryError 出现在栈内存设置成动态增长的时候,当JVM尝试申请的内存大小超过了其可用内存时会抛出这个错误。总结1. font color=\"#ff0000\
加载、初始化
执行引擎execution engine
虚拟机自带加载器跟加载器 Bootstrap classLoader rt.jar 拓展类加载器 ExtClassLoader jre/lib/ext应用程序加载器 AppClassLoader
老年代
Applicationmain( )
-Xms1m -Xmx1m -XX:+PrintGCDetails
GC垃圾回收主要在伊甸园区和养老区OOM(java.lang.OutOfMemoryError) 堆内存满了
类诞生和成长的地方,甚至死亡伊甸园区:所有的对象都是在伊甸园区new 出来的幸存者区(0,1)
操作系统
加载:将class字节码加载到内存(方法区),在方法区中生成二进制字节流(可运行的数据结构),生成Pet类的java.lang.Class对象。链接 验证 准备 name=null age=0 解析初始化
java程序
元空间:逻辑上存在,物理上不存在
new 实例化
1
1.每次GC都会将Eden中活的对象移到幸存区,Eden区变空。2.from 、 to 中谁空谁是to3.当一个对象经历了15次(默认)GC后,都还没有死,就进入老年代。通过-XX:MaxTenuringThreshold=值 可以设置
Native
java
JDK1.8后
缺点:进行两次扫描,有内存碎片
堆数据区是用来存放对象和数组(特殊的对象)。堆内存由多个线程共享。堆内存随着JVM启动而创建。众所周知,Java中有一个很好的特性就是自动垃圾回收。垃圾回收就操作这个数据区来回收对象进而释放内存。如果堆内存剩余的内存不足以满足于对象创建,JVM会抛出OutOfMemoryError错误。总结1. 存储的全部是对象,每个对象包含一个与之对应的class信息–class的目的是得到操作指令。2. jvm只有一个堆区(heap)被所有线程共享,堆区中不存放基本类型和对象引用,只存放对象本身。3. 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。4. 缺点是,由于要在运行时动态分配内存,存取速度较慢。
方法区method area
GC后
方法区是所有线程共享的内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数的限制,在java8中移除了永久代的内容,方法区由元空间(Meta Space)实现,并直接放到了本地内存中,不受JVM参数的限制(当然,如果物理内存被占满了,方法区也会报OOM),并且将原来放在方法区的字符串常量池和静态变量都转移到了Java堆中
常量池大体可以分为:静态常量池,运行时常量池。静态常量池 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面运行时常量池,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 值的是运行时常量池。
栈
new Pet( ) 0x001name=\"旺财\"age=3shut( )
Car.class
标记清除
作用区域:堆、方法区
缺点:多了一次移动成本
总结
新生区
静态方法区
优点:不需要额外的空间(复制算法的幸存 to)
本地变量
复制算法
复制算法的核心就是,每次只用其中的一块幸存区,在垃圾回收时,将正在使用的对象复制到另外一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾回收。如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。优点:在垃圾对象多(即对象存活的低)的情况下,效率较高。清理后,内存无碎片。缺点:在垃圾对象少(即对象存活的高)的情况下,不适用,如:老年代内存。分配的2块内存空间,在同一个时刻,只能使用一般,内存使用率较低极端情况(假设对象100%存活)要复制全部对象,所以使用前提是对象存活度较低(即在新生代)
凡是有native 关键字的,说明java作用范围达不到了,会去调用底层的C语言库,会进入本地方法栈native method stack ,调用本地方法接口 JNIJNI作用:拓展java使用,融合不同的编程语言为java所用,最初是c、c++;java诞生的时候,c、c++横行,想要立足,必须调用c、c++的程序它在内存区域专门开辟了一块标记区域:native method stack 登记native方法在最终执行的时候,通过JNI加载本地方法库中的方法native 使用越来越少了,除非与硬件有关的应用,如通过java驱动打印机或java系统管理生产设备,在企业级应用中较少见,因为异构领域间通信发达,比如Scoket通信,Web Service .
子帧
本地方法栈native method stack
父帧
Class File (引用)
方法索引(index)
方法区
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
标记:扫描对象,对存活的对象进行标记
清除:再次扫描对象,对没有存活的对象进行清除
car实例对象
创建对象内存分析
程序计数器
程序正在执行的方法一定在栈的顶部每执行一个方法,就开一个新的栈帧
幸存区
过程
3
栈运行原理:栈帧
GC掉
永久存储区
这个区域常驻内存,用来存放JDK自身携带的Class对象。interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟机就会释放这个区域的内存。jdk1.6 之前:永久代,常量池在方法区jdk1.7 : 永久代,但是慢慢的退化了,去永久代,常量池在堆中jdk1.8之后 : 无永久代,常量池在元空间一个启动类,加载了大量的第三方jar包、Tomcat部署了太多应用,大量动态生成的反射类。不断的被加载。直到内存满了,就会出现OOM.
伊甸园区
getClassLoader 获得类加载器
Petname=nullage=0shut( )
重GC
堆
2
每个线程都有一个程序计数器,是线程私有,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是非常小的内存空间。
getclass 获得反射对象
堆heap area
1.类加载器收到类加载的请求。2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到根加载器。3.根加载器检查是否能够加载这个类,能加载就加载,否则,抛出异常,并返回给子类4.重复3步骤
“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
作用:加载class文件
.class文件
栈stack area
非堆
3.类加载器
2. JVM体系结构
多次标记清除后,在进行一次压缩
分代收集算法
双亲委派机制
car2
4
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。缺点: (1)他需要单独的字段存储计数器,这样的做法增加了存储空间的开销。 (2)每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。 (3)引用计数器还有一个严重的问题,即无法处理循环引用的问题,这是一条致命 的缺陷,导致在Java回收的垃圾回收器中没有使用这类算法。
栈:先进后出栈:栈内存,主管程序的运行,生命周期和线程同步; 线程结束,栈内存就释放,不存在垃圾回收问题;一旦线程结束,栈就over。栈有哪些内容:8大基本类型、对象引用、实例方法
本地方法库native method library
标记清除压缩
GC
硬件体系
native方法:jvm底层由c、c++实现,native method是java调用非java代码的接口,native method 存在的原因:java需要与外部环境交互,如操作系统或硬件,native method 是与这些外部环境进行交互的接口(而无需了解其中的细节)java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要native methodJVM支持着java语言本身和运行时库,它是java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。它依赖于一些底层(underneath在下面的)系统的支持。通过使用本地方法,java实现了jre的与底层系统的交互
car1
作用:防止同名包、类与 jdk 中的相冲突
永久区
JRE
优点:减少了内存碎片
JVM内存模型分为:堆和非堆。堆分为:Old区、young区,比例为2:1。young区分为:Eden 区、 ServivorFrom、 ServivorTo 三个区。比例为8:1:1Eden:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。ServivorFrom:上一次 GC 的幸存者,作为这一次 GC 的被扫描者。ServivorTo:保留了一次 MinorGC 过程中的幸存者。注意:from和to区始终有一块是不使用的(空的)。MinorGC 采用复制算法。MinorGC执行过程:首先把Eden和from区中存活的对象复制到to区(如果对象的年龄达到老年代的标准,则直接复制到老年代中,这个年龄默认是15),同时把这些对象的年龄+1(如果to区内存不够就放到老年区)。然后清空Eden和from区。最后to区和from区互换,原来的to区成为下一次GC时的from区。Old区:老年代的对象比较稳定,所有MajorGC不会频繁执行。在进行MajorGC之前一般都先进行一次MinorGC。当无法找到足够大的连续空间分配给创建较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。MajorGC采用标记清除算法。MajorGC执行过程:首先扫描一次所有老年代,标记处存活对象,然后回收没有标记的对象。MajorGC需要扫描回收所以耗时比较长。MajorGC会产生内存碎片,为了减少内存损耗,一般需要进行合并或者标记出来方便下次直接分配。
年轻代:(microGC)存活率低,采用复制算法老年代: (majorGC)区域大,存活率高,采用标记清除+标记压缩(标记清除n次,进行标记压缩)
重量级(重GC )(Full GC)
main( )
方法区被所有线程共享,所有字段和方法字节码,以及一些特殊的方法,如构造器,接口代码也在此定义,简单说,所有定义的方法信息都保存在该区域,此区域属于共享区间;静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但实例变量存在堆内存中,和方法区无关。
程序计数器是线程私有,所以当一个新的线程创建时,程序计数器也会创建。由于Java是支持多线程,Java中的程序计数器用来记录当前线程中正在执行的指令。如果当前正在执行的方法是本地方法,那么此刻程序计数器的值为undefined。注意这个区域是唯一一个不抛出OutOfMemoryError的运行时数据区。
car3
轻量级(轻GC)
轻GC
项目中出现OOM错误,如何排除
内存效率(时间复杂度):复制算法(1次)>标记清除(两次)>标记清除压缩(三次)内存整齐度:复制算法=标记清除压缩>标记清除内存利用率:标记压缩=标记清除>复制算法
javac
1. JVM位置
0 条评论
下一页