前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM(一)

JVM(一)

作者头像
小土豆Yuki
发布2021-08-06 14:59:15
3260
发布2021-08-06 14:59:15
举报
文章被收录于专栏:洁癖是一只狗

java内存区域

代码语言:javascript
复制
public class kafka {
public static void main(String[] args) {
        ReplicaManager replicaManager = new ReplicaManager();
        replicaManager.loadReplicasFromDisk();
    }
}
public class ReplicaManager {
public void loadReplicasFromDisk() {
        Boolean hasFinishedLoad = false;
if (isLoacalDataCorrupt()) {
//....
        }
    }
public Boolean isLoacalDataCorrupt() {
        Boolean isCorrupt = false;
return isCorrupt;
    }
}

首先,你的JVM进程启动,首先会加载你的kafka类到内存中,然后有一个main线程,开始执行你的kafka中main方法

main线程是关联了一个程序计数器,那么他执行到哪一行行指令,就会记录到哪里

其次,就是main 线程在执行main方法的时候,会在main线程关联的java虚拟机栈里,压入一个main方法的帧栈,接着发现需要创建ReplicaManager类的实例对象,此时就会加载ReplicaManager类加载到内存中

然后一个Replication的对象实例分配到java堆内存,然后main方法的帧栈里的局部变量引入一个replication变量,让他引用ReplicaManager对象在java堆内存中的地址,

接着,main线程开始执行RelicaManager对象的方法,会一次执行到方法对应的帧栈中,执行完方法之后,再把方法对应的帧栈从java虚拟机栈里出栈。

双亲委派机制

扩展类加载器

Bootstrap ClassLoader,主要负责加载在安装的java目录下的核心类,在JDK安装目录下有一个lib目录,这个就是java的一些核心思路,支撑java系统的运行,

扩展类加载器

Extension ClassLoader,这个类加载器其实也是类似的,就是加载JDK安装目录下的lib\ext目录的一些类。

应用程序类加载器

这个就是负责加载classPath环境变量所指定路径的类,就是你写的代码,

自定义类加载器

根据你的需求加载你的类

双亲委派机制

JVM的类加载器是有亲子层级结构的,就是启动类加载器是最上层,扩展类加载器第二层,应用程序类加载器,最后一层就是自定义加载器

例如,JVM加载ReplicaManager类,此时用用程序类加载器就会问自己的爸爸,就是扩展类加载器,你能加载整个类吗,然后扩展加载器直接问自己的爸爸,启动类加载器,你能加载整个类吗,启动类加载器就会在自己的加载的目录寻找,发现没有找到,就会告诉自己的儿子,你去加载,然后扩展类加载器,也没有找到,就会告诉自己的儿子,你去加载,应用程序类加载器,就会看时候是自己的负责范围,果然是自己的,然后就会加载整个类到内存中去,这就是所谓的双亲委派模型,先找父亲加载,不行就去由儿子加载,这样的话,可以避免多层级的加载器结构重复加载某些类。

Tomcat这种web容器中类加载器如何设计实现

首先Tomcat类加载体系如下图,他是自定义了许多类加载器

Tomcat自定义了Common,Catalina,Shared等类加载器,其实就是用来加载tomcat自己的一些核心基础类库,然后tomcat每个部署在的web应用都有一个对应的WebApp类加载器,负责加载我们部署的这个Web应用的类,至于Jsp类加载器,则是给每个jsp都准备了一个Jsp类加载器,切记的是Tomcat是打破了双亲委派机制,

每个WebAPP负责加载对应的那个web引用的class文件,也即是我们写好的某个系统打包好的war包中的所有class文件,不会上传给上层类加载器去加载.

tomcat如何打破双亲委派机制

common类加载器,tomcat最基本的类加载器,加载的class可以被tomcat容器本身访问以及各个WebApp访问,实现共有类库,war和tomcat可以通用这个类class

cacalina类加载器,加载的webapp不可见,加载的是tomcat容器私有的类加载器,就是

shared类加载器,各个webapp共享类加载器,对于所有的webapp可见,但是对于Tomcat容器不可见,所有的webapp可以共用加载的类库,如上图的war1和war2使用同一个的mysql5.6的类,这个mysql就是share类加载器加载

webapp类加载器,各个webapp的私有加载器,仅对webapp可见,这个就是为了不同war包可能引用不同的版本,起到隔离的作用,

jsp类加载器,加载的仅仅是这个jsp所编译出来的class文件,当web容器检测到jsp修改了,然后新建一个jsp实例,就会替换旧的jsp实例

分代模型

我们知道我们代码写的对象大部分存活的周期极短,少数对象是长期存活的,JVM使用分代模型,存储不同的对象,将java堆内存划分为年轻代和老年代,

顾名思义,年轻代存放存放存活周期短的对象,老年代存放长期存在的对象

代码语言:javascript
复制
public class kafka {
    private static  ReplicaFetcher fetcher = new ReplicaFetcher();  
public static void main(String[] args) {
       loadReplicasFromDisk();        
        while (true){
           fetchReplicasFromRemote();
           Thread.sleep(2000);
       }        
    }    
private static void loadReplicasFromDisk(){
        ReplicaManager replicaManager = new ReplicaManager();
        replicaManager.load();
    }
public static void fetchReplicasFromRemote(){
        fetcher.fetch();
    }
}

例如代码,他们在java堆内存中是如何分布的,如下图

然后一旦loadReplicasFromDisk执行完毕之后,方法的帧栈就会出栈,对应的年轻代里的ReplicaManager对象也会被回收掉,如下图

永久代

JVM里的永久代其实就是我们之前说的方法区,可以理解就是所谓的永久代,你可以认为永久代就是放一些类信息的

方法区会进行垃圾回收吗,满足下面条件就可以回收引用

  1. 首先该类的所有实例对象都已经从java堆内存被回收
  2. 其次加载这个类的classLoader已经被回收
  3. 最后,对该类class对象没有任何应用

JVM内存中如何分配,如何流转

大部分的对象都是优先在新生代分配内存的,正如上面类静态变量fetcher用用的RelicaFetcher对象,会长期存活内存中,其实他刚开始的时候也是存活在新生代,最后经过多次垃圾回收之后,最后转移到老年代

那么什么时候进行垃圾回收

其实并不是java堆内存的对象没有引用,并不一定会发生垃圾回收,实际上垃圾回收也是需要条件的

常见的是当新生代预分配内存空间,几乎被对象占满了,但是还有对象创建,这个时候就要进行垃圾回收,新生代内存空间的垃圾回收称为Minor GC,有时候也叫 Young GC,这个时候就会回收没有任何引用的对象.

长期存活的对象会躲过多次垃圾回收,

比如上面说的ReplicaFetcher实例对象,他长期被静变量引用,所以他在新生代不会别回收,但是我们JVM有一条规定,当成功回收15次之后,还是没有回收,就会进入老年代,因此老年代放的就是年纪大的对象.

JVM核心参数

-Xms

java堆内存大小

-Xmx

java堆内存的最大大小

-Xmn

java堆内存新生代的大小

-X:PermSize

永久代大小

-X:MaxPermSize

永久代最大大小

-Xss

每个线程的栈内存大小

如何设置JVM堆内存

如上图一个支付系统,每天产生100万的订单,当然正常的生产环境不会在一台机器上,假设我们有三个机器,如下部署,每台机器每秒大概有30个请求

对于上面我们看看我们如何预估如何设置JVM堆内存大小

  1. 每条订单大约多少内存 我们的interger是4个字节,Long是8个字节等等,按此计算,假设我们的一个订单对象大概是500字节,不到1kb
  2. 实际估算要加10-20倍 由于我们创建订单不仅仅是订单对象,可能还有其他对象,比如我们的而此时的订单,30*500=1500,15kb,加上其他对象,每秒产生几百kb到1Mb
  3. 触发Minor GC 我就认为每秒产生1M,当过一段时间产生了几十万对象,此时占据了几百M内存,此时可能新生代快满了
  4. 此时如何设置JVM堆内存 假设我们机器是2核4G,机器本身就要占用内存空间,最后留给JVM的最多有2G,此时JVM还有方法区,栈内存,堆内存,留给堆内存可能只有1G,但是堆内存还分为新生代和老年代,留给新生代的空间假设只有500M,此时我们的系统每秒1M,不到500秒,新生代就满了,就会发生GC 这样频繁GC,是一种不好的现象,再比如我们用4核8G,再次计算半个小时到一个小时,才会发生一次Minor GC,其实我们也可以再多部署几台机器,比如5台机器,平均每天每秒也就20订单对象,这样对每台机器的请求越少,JVM压力越小

按照上面步骤合理的预估你们生产环境,将事半功倍。

如何堆内存设置不合适会出现什么情况

比如我们有一台2核4G机器,分给堆内存是1G,但是对于新生代就是500MB,正常情况下没有问题,但是在大促销的时候,我们就可以发现问题

假设每秒产生100个对象,每个对象500字节,就有50kb,此时我们要在此基础上乘以20倍,此时就是大约1M

而每秒产生1M,几百秒之后,新生代就会慢,此时就会发生Minor GC,频繁的发生GC,就会导致系统卡顿,体验不好

极端情况下,每秒1000个对象,最终系统内存每秒有10MB,甚至几十MB,同时系统的CPU,资源性能急剧下降,就会导致请求变慢,最后在Minor GC之后,但是还会有几十MB没有被回收,慢慢的就会导致进入老年代

如果进入老年代,那就会更加糟糕,因为老年代的GC,是非常慢的,老年代频繁的GC,最后有可能造成内存溢出,极大的影响性能

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档