2.JVM--new 关键字底层执行代码的执行过程

一. 在JVM中创建的对象示例放在堆中,那么当JVM执行new语句时,具体是如何操作的呢?

(1)首先在方法区的常量池中查看是否有new 后面参数(也就是类名)的符号引用,并检查是否有类的加载信息也就是是否被加载解析和初始化过。如果已经加载过了就不在加载,否则执行类的加载全过程。
(2)加载完类后,JVM开始为这个新生对象分配内存,这个时候选用的分配内存的方法取决于堆内存是否是规整的。

注意:这种情况需要考虑对象在虚拟机的创建中是否是频繁的,因为在多线程的情况下是不安全的,比如对象A正在使用一个指针位置划分内存区域,指针还没来得及修改,对象B也开始划分内存,那就是使用原来的指针位置,就会发生问题

解决办法:

一、对分配内存的方法加同步锁;

二、把分配内存的动作按照不同的线程分配在不同的空间中运行,即为每个线程分配一小块内存,叫做本地线程分配缓存(TLAB).那么每个线程的分配内存空间就在各自的TLAB中进行。(就是把堆中未分配的内存再分配给不同的线程)

  • 如果JVM的垃圾回收器带Compact功能,也就是压缩整理内存,那么内存就是规整的,也就是已经分配的内存放在一起,未分配的内存放在一起,中间用指针间隔,这样新分配内存只需要移动指针,这种方法叫做“指针碰撞”
  • 那么如果JVM的垃圾回收器采用的算法没有Compact功能,对应的堆内存就不是规整的,也就是未分配的内存不连续,那么JVM就需要维护一个列表记录哪些内存区域是可以分配的,这种方法叫做“空闲列表”

(3)对象的内存分配好之后,JVM会把对象的内存空间都初始化为零值(不包括对象头,一个对象在内存中的存储包括三个部分:对象头,实例数据,对其填充,后面再做详细解释)。这一步操作保证对象在java中没有赋值就可以使用,比如 Integer i=new Integer();创建完对象i,i的初始值是0;而不是null;

(4)接下来JVM就会对该新生对象的进行设置,填写对象头的内容。对象头中有对象自身的运行时数据,包括对象的hash码,GC分代年龄信息和类型指针(通过这个指针JVM才能知道这个对象是哪个例的实例)。根据虚拟机现在是否使用偏向锁会有不同的设置

上面的过程全部执行完,在JVM一个对象已经产生。在java中方法还没有执行,也就是还没有调用类的构造器,所以字段还是零值,也就是在java中还没有初始化,执行完new以后,JVM会根据字节码中是否有(invokespecial指令)决定是否执行完new就开始执行,执行完一个对象就真正产生了

区别init()方法和cinit()方法

clinit方法

在加载类的时候调用,加载类的过程:类加载—–验证—-解析—–初始化,JVM会在初始化阶段调用clinit方法来收集类中的类变量的赋值动作和静态语句块(static{}块)中的语句等

init()方法

是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行init方法

二. JVM运行时内存区域

jvm在运行java程序时,会把它管理的内存划分为五个模块

程序计数器

程序计数器占JVM内存很小的一部分,主要记录jvm执行当前线程字节码文件的行号指示器,字节码解释器会根据计数器的值执行对应行号的字节码。在多线程的时候,JVM是采用线程轮流切换并分配处理器时间的方式来实现的,那么为了切换后线程能找到正确的执行位置,所以每个线程都需要一个程序计数器。所以程序计数器是每个线程私有的。

Java虚拟机栈

java虚拟机栈线程私有,描述的是JAVA方法执行的内存模型,所以他与线程的生命周期相同。那么在每个线程执行调用某一个方法的时候都会创建一个栈帧,同来存储局部变量表等等,线程每调用一个方法,从执行到结束,对应着栈帧从Java虚拟机栈入栈到出栈的过程。

局部变量表存放编译器可知的基本数据类型和对象的引用类型(地址而不是对象本身),所以局部变量表的内存在编译时分配内存

long和double类型的数据占用两个局部变量表,其他占一个。

本地方法栈

与Java虚拟机栈不同的地方在于,它是为JVM中的native方法服务的,有的虚拟机会把这两个栈合二为一。

Java堆
堆是jvm管理的最大的内存的区域,是所有线程共享的区域,在虚拟机启动时创建。用来存放对象实例。堆是垃圾回收器管理的主要区域,所以又称为gc堆。

为了防止多线程情况下,给对象分配内存时,出现问题,线程共享的java堆可能划分多个线程私有的分配缓存区。

方法区

所有线程共享,存储被虚拟机加载的类信息,常量,静态变量还有即时编译后的代码等数据

运行时常量池
它属于方法区的一部分,用于存放编译期间生成的各种字面量和符号引用。之所以叫运行时常量,就是它具备动态性,运行期间也可以将新的常量放入池中

直接内存

不属于jvm运行时数据区一部分,在jdk1.4之后引入一种基于通道与缓冲区的I/O方式。可以使用native的方法分配堆外内存,然后通过DirectByteBuffer对象作为这块内存的引用进行操作。

三. OutOfMemoryError异常(OOM)

上述所有的内存区域都会发生OOM异常,那么什么样的代码或者操作会导致不同区域的OOM异常呢,以及我么该怎么处理?

java堆溢出

堆是存放对象实例的,那么当对象数据达到最大堆的容量,且没有回收,就会内存溢出异常。

为了实验,设置eclipse的堆内存大小,-Xms堆内存的最小值,-Xmx堆内存的最大值,相等时说明不支持自动扩展

-XX:+HeapDumpOnOutOfMemoryError 让JVM出现内存溢出异常时Dump出详细信息便于分析

image

package xidian.lili.OOMtest;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * -verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 * 
 */
public class HeapOOM {
	static class OOMObject{
		
	}
	public static void main(String[] args) {
		List<OOMObject> lists=new ArrayList<OOMObject>();
		while(true){
			lists.add(new OOMObject());
		}
 
	}
 
}

结果:

image

那么堆内存异常该怎么解决呢?

(1)首先分析是内存溢出还是泄露

(2)内存泄露的话用工具找到泄露对象到GCroot的引用链,定位泄露代码的位置

(3)溢出的话,就检查-Xms,-Xmx参数设置,看可不可以增大Xmx,然后从代码上检查是否有些对象的生命周期过长,持有状态时间太长

虚拟机栈和本地方法栈溢出

栈是线程私有的,每个方法有自己的栈帧。

-Xoss设置本地方法栈的大小

-Xss设置虚拟机栈的大小

在jvm栈中有两种异常

StackOverflowError:当一个线程申请的请求的栈深度大于JVM所允许的最大栈深度

OutOfMemoryError:当运行时动态扩展栈失败,即没有足够的空间

但是以下两个实验均是StackOverflowError,单线程操作

(1)用-Xss设置一个很小的栈内存-Xss128k,抛出StackOverflowError异常

(2)定义大量本地变量,增大该方法的栈帧的本地变量表的长度,(这里以为会是动态扩展栈容量)结果抛出StackOverflowError异常

所以结论:在单线程的情况下,不论是栈帧太大还是虚拟机栈内存太小,内存无法分配时,都会抛出StackOverflowError异常

那么在多线程的情况下,产生OutOfMemoryError,也和虚拟机栈内存大小没有多大关系

原因:在机器中分配给一个进程的内存大小是固定的,程序计数器及其他内存消耗忽略不计

那么-Xss=进程的固定大小减去-Xmx-再减去-XX:MaxPermSize

可以抽象的理解为-Xss=栈帧的大小*线程数,-Xss一定,线程数越多,能分配的栈帧就会越小,就需要动态扩展栈,但是空间也不够了,就会抛出OutOfMemoryError异常

那么虚拟机栈异常该怎么解决呢?

(1)减少-Xmx

(2)减少进程数

方法区和运行时常量池溢出

-XX: PermSize -XX:MaxPermSize限制方法区的大小

主要以String.intern()作为测试方法,这个方法是一个Native方法,在jdk1.7对该方法进行改进

jdk1.7之前:

如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,则复制这个字符串常量到常量池,并返回在常量池的引用

jdk1.7:

如果字符串常量池中已经包含一个等于String对象的字符串,则返回常量池中这个字符串的引用,若果字符创常量池中没有,并返回该字符串对象在堆中的引用
在这里插入图片描述
方法区的溢出也是很常见的溢出,我们知道方法区存放的是Class相关信息,类名,修饰符,常量池,字段描述,方法描述等,但是一个类并不容易被垃圾回收器回收

本机直接内存溢出

可以用-XX MaxDirectMemorySize来指定直接内存的大小,如果不指定,与-Xmx一样大小。

当直接内存发生OOM异常,它不会直接向操作系统申请分配内存,而是通过计算知道无法分配后就手动抛出异常,所以在Heap Dump中不会看出明显的异常,所以如果OOM之后发现dump很小,程序又使用的NIO,就可以检查是不是直接内存溢出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值