java从零开始之初识JVM

什么是JVM?

JVM是(Java Virtual Machine)Java虚拟机的缩写,是一个运行在操作系统之上的虚拟机。

JVM 屏蔽了与操作系统平台相关的信息,使 Java 程序只需生成字节码就可以在多种平台上不加修改地运行。JVM 负责把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

三种JVM

  • SUN: HotSpot
  • BEA: JRockit
  • IBM: J9VM

运行时数据区

.java->.class->类加载器(ClassLoader)->运行时数据区
image

Car car1=new Car();
//car1存放在栈中,实际上就是一个地址,地址指向的内存空间就是堆. 真实的对象存在于堆中.
  • 堆(Heap): 存放new的对象和数组,Class对象.一个进程只有一个堆
  • 方法区(Method Area 即永久代1.8取消): 存放类元数据(.class)和static变量
    • 运行时常量池(1.7将常量池移到堆中): 包装类的-128~127之间的数值(超范围则在堆中new),String类型,final常量
  • 虚拟机栈(stack 又名引用地址): 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧中会存放基本数据类型的具体数值,对象引用(堆中对象的地址).一个线程有一个栈.
    • 局部变量表(Local Variable Table)
    • 操作数栈(Operand Stack)
    • 返回地址(Return Adderss)
    • 动态链接(Dynamic Linking)
    • 指向运行时常量池的引用
  • 本地方法栈(Native Method Stack): 主要为虚拟机使用到的Native方法服务。 也会抛出StackOverflowError和OutOfMemoryError。
  • 程序计数器(Program Counter Register 又名PC寄存器): 每个线程都有一个程序计数器,是线程私有的,存放下一条指令所在地址
区域是否线程共享是否会内存溢出是否存在垃圾
程序计数器不会
虚拟机栈
本地方法栈
是(大部分)
方法区

数据类型:

  • 堆:可以看做完全二叉树的数组对象
  • 栈:类似单口的队列(桶),先进后出,只能在栈顶对数据进行插入和删除

堆内存

  • 新生代: 1/3堆空间
    • 伊甸园区Eden: 当创建一个对象时,对象会被优先分配到新生代的Eden区,此时JVM会给对象定义一个对象年龄计数器(-XX:MaxTenuringThreshold).如果分配的对象大小超过了-XX:PetenureSizeThreshold,对象会直接被分配到老年代.
    • 幸存区:谁空谁是to
      • From Servivor
      • To Servivor
  • 老年代: 2/3堆空间,当对象的年龄到达-XX:MaxTenuringThreshold时进入老年代.
  • 永久代(jdk1.8取消): 即方法区,存放JDK自带的Class对象和Meta(元数据)的信息。不存在垃圾回收.
  • 元空间(1.8取代永久代): 元空间并不在虚拟机中,而是使用本地内存。

JVM堆内存分配

  • 在默认不配置JVM堆内存大小的情况下,JVM根据默认值来配置当前内存大小
  • 在JDK 1.7中,默认情况下新生代和老年代的比例是1:2,可以通过–XX:NewRatio来配置
    • 新生代中的Eden:From Survivor:To Survivor的比例是8:1:1,可以通过-XX:SurvivorRatio来配置
  • 若在JDK 1.7中开启了-XX:+UseAdaptiveSizePolicy,JVM会动态调整JVM堆中各个区域的大小以及进入老年代的年龄
    • 此时–XX:NewRatio和-XX:SurvivorRatio将会失效,而JDK 1.8是默认开启-XX:+UseAdaptiveSizePolicy
    • 在JDK 1.8中,不要随意关闭-XX:+UseAdaptiveSizePolicy,除非对堆内存的划分有明确的规划
    • 每次GC后都会重新计算Eden、From Survivor、To Survivor的大小
      • 计算依据是GC过程中统计的GC时间、吞吐量、内存占用量

GC垃圾回收

GC分类

  • MinorGC: 当伊甸园区满时,触发MinorGC对新生代进行垃圾回收,如果对象没有被MinorGC回收则进入幸存者区且年龄+1.每经历一次MinorGC,幸存者区中的对象年龄也会+1.MinorGC后伊甸园区和to区为空.
  • FullGC: 当老年代满时,触发FullGC对老年代和新生代进行垃圾回收.FullGC后如果堆内存仍然不足则报OOM错误.

引用计数法(弃用)

通过判断对象的引用数量来决定对象是否可以被回收。

很难处理循环引用,相互引用的两个对象则无法释放。因此目前主流的Java虚拟机都摒弃掉了这种算法。

标记清除法

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收清除所有未被标记的对象。

标记阶段:标记的过程其实就是可达性分析算法的过程,遍历所有的GC Roots对象,对从GC Roots对象可达的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象;

清除阶段:清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header信息),则将其回收。

  • 优点: 不需要额外空间
  • 缺点: 1.两次扫描浪费时间.2.会产生内存碎片,当内存碎片过多可能导致无法给大对象分配内存。

标记压缩法

标记清除算法的基础上将存活对象往内存的一端移动

  • 好处: 不会产生内存碎片
  • 坏处: 还需要第三次对内存空间的扫描整理

复制算法

新生代使用复制算法.GC时将伊甸园区和from区中幸存的对象复制到to区中,清理伊甸园区和from区,然后将from和to交换.保证to区中永远为空

  • 好处: 不会产生内存碎片
  • 坏处: 1.to区永远为空,浪费了内存.2.对象存活率较高时就要进行较多的复制操作,效率将会变低。
  • 适用于对象存活度较低的场景.

分代收集算法(GC使用)

分代回收算法实际上是把复制算法和标记整理法的结合,不同代就采用不同的回收算法,以此来达到高效的回收算法。

新生代:由于新生代产生很多临时对象,大量对象需要进行回收,所以采用复制算法是最高效的。

老年代:回收的对象很少,都是经过几次标记后都是不可回收的状态转移到老年代的,所以仅有少量对象需要回收,故采用标记清除+标记压缩算法混合实现。

类加载流程

  • 加载:将.class字节码文件加载到方法区中,在堆中生成对应类的Class对象.
    • 启动类加载器:加载java核心类库(jre/lib/rt.jar)中的类,该加载器无法直接获取
    • 扩展类加载器:加载jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录中的jar包
    • 应用类加载器:加载java -classpath或-D java.class.path目录下的类和jar包
  • 链接
    1. 验证:验证.class中的代码符合JVM规范
    2. 准备:为static变量分配内存和初始化默认值
    3. 解析:将常量池中的符号引用(常量名)解析为直接引用(地址)
  • 初始化:执行类构造器()方法,将类中所有静态代码块和静态变量语句合并自上而下执行.
    • 子类引用父类的静态变量,不会导致子类的初始化,但父类会初始化
    • 数组定义类引用,不会导致类初始化
    • 引用常量不会导致类的初始化

实例化对象流程

  1. 在堆中分配内存空间
  2. 执行构造方法,初始化对象
  3. 将栈中的对象引用指向堆的内存地址.

实例化对象不是一个原子操作,可能会发生指令重排

双亲委派机制

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

沙箱安全机制

沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

native方法

使用native关键字修饰的方法,说明是java无法直接操作的资源,会进入本地方法栈,调用本地方法接口JNI(java native interface),最后交由本地c/c++方法执行.

常用JVM参数

GC日志类

-XX:+PrintGCDetails #打印GC详细日志
-XX:+PrintGCDateStamps #输出绝对时间
-Xloggc:/Users/ouyang/gc.log #GC日志文件路径,目录/Users/ouyang必须存在
-XX:+HeapDumpOnOutOfMemoryError #发生OOM时备份heapdump文件
-XX:HeapDumpPath=/Users/ouyang #配置OOM时heapdump文件生成路径

堆空间大小

-Xms50m #JVM最小可用内存为50m(默认总内存1/64)
-Xmx50m #JVM最大可用内存为50m(默认总内存1/4)
-Xmn10m #设置young区大小为10m
-XX:+UseAdaptiveSizePolicy #开启自适应特性,默认开启,开启后 NewRatio和SurvivorRatio失效
-XX:NewRatio=2  # 新生代和老年代的比例
-XX:SurvivorRatio=8 #指定Eden与Survivor的大小比例为8:1,即Eden为8m,每个Survivor为1m
-XX:PermSize=32m #设置永久代初始内存为32m
-XX:MaxPermSize=32m #指定永久代最大内存为32m
-XX:MetaspaceSize=32m #设置元空间初始内存为32m
-XX:MaxMetaspaceSize=32m #指定元空间最大内存为32m

调优

-XX:MaxTenuringThreshold=15 #设置对象年龄阀值为15
-XX:+PrintTenuringDistribution #GC日志中输出对象年龄分布信息
-XX:ParallelGCThreads=8 #配置STW GC线程数

Jprofiler分析内存

eclipse使用MAT进行内存分析

安装jprofiler客户端(安装路径无空格中文).

安装jprofiler的idea插件,并配置客户端启动器在安装目录/bin/jprofile.exe

作用:

  • 分析OOM时产生的heapdump内存文件,快速定位内存泄漏位置
  • 获取堆中数据
  • 获取大的对象等等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值