JVM学习记录浅析二

上一篇文章 主要记录了jvm类的加载机制,其实应该趁热打铁把类的加载机制应用- -TInker源码也一起解析的,由于工作原因,只能过两天再分析记录一下。今天主要来记录一下JVM中另外一个重要的模块,内存模型。

linux中一切都是文件,但是我觉得在学习分析JVM中,一切皆是内存比较合适:

栈空间:线程私有区域,分为方法栈、本地方法栈、程序计数器三部分

堆:线程共有区域内存,所有创建的对象都会在堆中分配内存

非堆(元空间):线程共有区域,主要保存解析完的类信息、静态变量、常量池,因此生命周期最长,虚拟机的生命周期一样长

第一部分:栈空间

栈空间其实也是一部分内存,由所有的线程瓜分,所以说如果程序中只用单线程,那么其实栈这部分内存基本上就都浪费掉了。那么我们的App有多少线程呢?默认是六七百个吧,具体多少可以打印日志查看。除去默认的线程,我们还可以创建多少线程呢?一千个左右,再多了就会报OOM,所以这里也再次印证了一点,那就是栈也是内存。这里也说一句题外话,就是如果我们用线程池,最大线程如果设置为Integer.MAX_VALUE,那么一定需要额外的容器来控制线程的数量的,否则肯定会报OOM的。例如Okhttp中:

public synchronized ExecutorService getExecutorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

他的这个线程池就是这种,那么他就需要额外的两个队列来控制正在执行(64个)的和等待执行的,否则的话那就是OOM:

synchronized void enqueue(AsyncCall call) {
    if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningCalls.add(call);
      getExecutorService().execute(call);
    } else {
      readyCalls.add(call);
    }
  }

这其实也从另一个角度说明为什么我们使用线程的时候不可以随便new Thread()来创建,因为栈中线程这块内存频繁的使用、销毁会造成内存抖动的,内存抖动就会出现GC,就会增加STW,影响用户体验。

栈中还有一个关键的点,那就是栈中所有的引用都是GC可达性分析的root,当然也包括线程本身。所以说,栈是操作数据的地方,而堆和非堆是保存数据的地方。

1.栈针:每一个方法都是一个栈针,执行此方法时会将这个栈针压入栈中

  局部变量表:保存栈针中需要的所有堆中数据的引用,非静态方法局部变量表中的第一个变量为this,这就保证了可以操作类中其他方法和属性。

  操作数栈:就是保存数据运算的运算操作符的栈(理解的可能不对)

  动态链接:就是局部变量表中的符号动态链接到堆、非堆中的实际变量

  方法返回:就是计算结果保存

2.程序计数器

  由于线程的执行过程是创建--准备--执行---挂起--准备--执行........结束,执行是断断续续的,所以再次执行的时候需要继续上次断开执行的地方,程序计数器就是用来保存这个变量的。

第二部分:堆

堆其实就是用来保存生命周期不是和进程生命周期一致的变量的地方。分为:

1.Young区:又分为伊甸园区+From区+To区,比例为8:1:1,为什么是这个比例呢?统计学算的。

    伊甸园区:朝生夕死,垃圾清理算法为标记--清理,清理的是那些没有标记的对象。对应的GC操作为YoungGc,频度高,但是速度快。

   From+To区:伊甸园区存活下来的都会先放到From或者To区中,这两个区只有一个区中有数据,另一个区为空的,垃圾清理算法为标记--复制算法+生命计数(由From区拷贝到To区算一岁),这个算法效率最高,但是最大的缺点就是浪费了一半的内存,典型的用空间换取时间,所以不可以大面积的使用。

2.Old区:老年区,当from区中对象生命计算到达15岁时,对象会进入老年区,当然如果对象过大young区无法存储下,也会直接升级放入到old区。对应的GC为OldGc,算法为标记--清除--整理(头尾双指针碰撞算法)算法,效率最低,因此最耗时,对用户体验影响最大。

第三部分:非堆(元空间)

元空间中保存的数据都是生命周期超长的,基本上就是和App的生命周期一样长,因此如果某些堆区对象的引用被非堆对象持有,那就发生内存泄漏了,例如单例模式。非堆中的对象也是非堆中也会有GC操作,但是频率很低,对应的GC为FullGC,主要回收的就是卸载的类和字符串常量池中对象。

总结一下:

1.类的加载机制:双亲委派机制(安全、高效)

2.内存管理机制:分区管理,算法各不同,提高垃圾回收效率,减小对用户影响

3.Gc root:栈中对象、线程、非堆中对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值