JVM分析

一、背景:

  

.class文件 -> 类加载器  -> 运行时数据区 -> 执行引擎 -> 本地方法 -> 执行指令

    执行引擎:对命令进行解析,将字节码翻译程系统指令

    本地库接口:调用其他语言的原生库为java所用

1、方法区:

    线程共享

    存储:虚拟机加载的类信息,编译器编译后的代码数据,运行时常量池(编译期生成的各种字面量和符号+运行期产生常量),【字符串常量池、静态变量 jdk1.7后移到堆中】。

    问题:内存不足oom

    别名Non-Heap 非堆,jdk1.7前永久代实现方法区,jdk1.8元空间实现方法区存在本地内存

    回收内容:常量池废弃的常量+不再使用的类型

2、java堆:

    线程共享

    存储:对象实例和数组

    问题:空间不足OOM

3、虚拟机栈:

    线程私有

    存储局部变量表、操作数栈、动态链接、方法出口等

    问题:栈深度不足StackOverError,内存不足OOM

4、本地方法栈

    线程私有

    存储本地方法服务栈信息

5、程序计数器

    线程私有

    存储线程执行位置

    没有溢出错误

    掌握Java虚拟机(JVM)需考虑两方面,其一是理解垃圾回收、其二是JVM调优

1、垃圾回收:什么时候回收、回收哪些、怎么回收 三方面

2、JVM调优:提高应用响应速度、吞吐、资源利用率

类加载器 -> 运行时数据区 -> 执行引擎 -> 本地库接口

二、垃圾回收

    1、什么时候回收

        Minor GC:Eden区空间不足,进行Minor GC,回收Eden区,未被回收存入Survivor,15次GC(参数:-XX:+MaxTenuringThreshold=15)后还存活,进入老年区 

        Full GC:

                    老年区空间不足:年轻对象多 或 大对象(15次 Minor GC后进入老年代空间不足)

                    方法区满了:(jdk及之后版本元空间替代永久代)

                    System.gc()调用

    

                    永久区满了:(jdk7及之前版本)

  补充:1、方法区回收主要分为废弃的常量池和不在使用的类型。

                判定废弃常量明确:常量池中的常量没有被任何地方引用,就可以被回收

                判定不在使用的类型严格:

                            1)该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例。

                            2)加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP 的重加载等,否则通常是很难达成的。

                            3)该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

            2、jdk不同版本方法区调整

            3、元空间替代永久代原因:

                    1)、永久代GC回收率低(full gc触发),调优困难,字符串存在永久代,容易出现内存溢出

                    2)、类及方法的信息大小难确定,永久代大小指定难,太小容易永久代溢出,太大容易老年代溢出

                    3)、元空间和类加载器生命周期一致,GC发现类加载器不在存活,会将相关空间都回收了

    2、回收哪些

            GC回收不在存活的对象,判断对象是否存活,有引用计数算法和可达性分析算法,其中引用计数算法难解决循环引用问题,当前主流语言采用可达性分析算法

    GC Roots对象:

                        1、栈中引用的对象

                        2、方法区中的静态引用对象

                        3、方法区中的常量引用对象

                        4、本地方法栈(native方法)引用对象

                        5、同步锁持有的对象

    3、怎么回收

                    1、标记-清除(Mark-Sweep):标记无用对象,进行清除回收。缺点:效率不高,无法清除垃圾碎片

                2、复制(Coping):解决Mark-Sweep缺陷 ,将可用内存一分为二两个相等内存块,当一块内存用完将存活对象复制到另外一块内存块,然后将已使用内存块清理掉。缺点:内存使用率不高,只有原来一半

                    3、标记-整理(Mark-Compact):解决Coping算法缺陷,标记无用对象,存活的对象都向一端移动,然后清除掉端边界以外的内存。

                    4、分代(Generational Collection):根据对象存活周期将内存划分若干个不同区域。一般采用新生代和老年代,因为新生代大部分对象存活周期短,回收对象多(需要复制的操作少),采用复制算法;老年代特点存活周期长,回收对象少,采用标记整理算法

    4、常见垃圾回收器 CMS - 6~8G - G1

                1、CMS回收器:以最短回收停顿为目标的一款并发收集、低停顿。采用标记-清除算法,若干次后执行一次碎片清理

                2、G1回收器:优点:空间整合+可预测的停顿时间模型;缺点:额外执行负载比CMS高。将java堆分成2048个相同独立Region块,根据堆空间大小动态控制在1~32MB且为2的N次幂带大小,基于“标记-整理”算法实现收集器

                    

                3、ZGC:指针染色技术 读屏障

                4、其他收集器:

    5、常见面试问题:<JVM笔记:内存与垃圾回收>7-方法区_java1.7方法区回收条件-优快云博客

        1、百度:JVM内存模型,每个区作用

        2、阿里:JVM为什么两个 survivor 区?eden和survivor设计解决问题

                1)、JVM 为什么两个 survivor 区:解决内存碎片化,基于新生代Coping算法实现需要;

                2)、eden和survivor设计解决问题:

                            A、提高垃圾回收率:新生代大部分对象存活周期短,回收对象多,Minor GC更容易对生命周期短对象回收;也避免Full GC的全区扫描,提高垃圾回收率

                            B、避免过早晋升到老年代:过早晋升老年代容易导致老年代空间不足触发Full GC

        3、小米:JVM为什么设计新生代和老年代?

                1)、对象存活周期不同

                2)、回收算法不同

        4、字节跳动:JVM什么时候对象会进入老年代?

                1)、15次 Minor GC 后进入老年代:-XX:MaxTenuringThreshold

                2)、survivor幸存区对象占用内存大于50%平均年龄以上对象进入老年代

                3)、大对象直接进入老年代:-XX:PretenureSizeThreshold(避免大对象多次GC不清理,且多次在survivor内存复制)

                4)、Minor GC后对象超过survivor

                5)、老年代空间担保规则:XX:-HandlePromotionFailure=true jdk7参数移除

        5、jvm 的方法区会发生垃圾回收吗?

                A、废弃的常量池:常量池中的常量没有被任何地方引用,就可以被回收

                B、不在使用的类型:

                            1)该类所有的实例都已经被回收,也就是 Java 堆中不存在该类及其任何派生子类的实例。

                            2)加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP 的重加载等,否则通常是很难达成的。

                            3)该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

三、JVM调优

        1、jdk自带工具:

                1)、jps:查看正在运行java进程 -l -v -m

                2)、jstat:查看jvm统计信息:jstat -gc 27580 1s 3

                3)、jmap:内存使用情况: jmap -dump:format=b,file=heapdump.hprof 27580

                4)、jinfo:实时查看jvm配置参数

                5)、jstack:打印jvm中线程快照:jstack -l 2990 > /sk/jstack.txt

四、arthas工具使用

    Arthas使用教程(8大分类)-优快云博客^v100^pc_search_result_base2&utm_term=arthas%E4%BD%BF%E7%94%A8&spm=1018.2226.3001.4187

五、1.7和1.8区别:转载jvm内存模型jdk1.7和jdk1.8的区别_weixin_43889362的博客-优快云博客

        

Java垃圾回收、引用计数法、根可达算法 - 简书

  • 主要成员

编译器:将java转换成.class字节码文件

类加载器(ClassLoader):将字节码二进制流加载到内存中,转换为JVM中的Class<>对象

运行时数据区(Runtime Data Area):

执行引擎(Execution Engine):对命令进行解析,将字节码翻译成底层系统指令

本地库接口(Native Interface):融合不同开发语言的原生库为java所用

组件的作用: 

1、类加载器把 Java 代码(java文件)转换成字节码(.class)。再由不同平台的JVM解析,命令javac -xx.java转换成.class文件;javap -c xx.xlass对代码进行反汇编

2、运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,

自定义ClassLoader:

      2.1、继承ClassLoader

      2.2、重写findclass方法:自定义获取class,并调用defineClass

3、因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行

4、而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

  • JVM运行时数据区

程序计数器(Program Counter Register):当前线程所执行的行号指示器,字节码解析器的工作室通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成

Java虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息

本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务java方法的,而本地方法栈是为虚拟机调用本地方法服务的

Java堆(Java heap):Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象示例都在这里分配内存

方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据

  • 反射

动态获取信息和动态调用方法:把java类中映射各个对象

1、获取类:Class a = Class.forName("全路径");

2、初始化:a.newInstance();强转对象

3、获取方法对象:Method c = a.getDeclaredMethod("方法名",参数类.class);不能获取继承方法和所实现接口方法

Method c = a.getMethod("方法名",参数类.class);不能获取私有方法

4、调用方法:c.invoke(a,"参数");c.setAccessible(私有方法设置)

5、获取属性对象:Filed f = a.getDeclaredFiled(“属性名称”);

6、对象赋值:f.set("值")

  • 类的加载方式

隐式加载 new

显示加载 loadClass forname(通过newinstance())

  • 堆栈区别

功能:堆是用来存放数组或对象的,栈是用来执行程序的

共享性:堆是线程共享,栈是线程私有

空间:堆远大于栈

数据共享的有栈、寄存器、PC,线程共享的有:堆、全局变量、静态变量、方法区。栈和常量池中的数据可以共享,即可以有多个引用对象(int a=3,int b=3);堆来说,数据不可以共享(String a=new ("3")),String b = new String ("3");

  • 双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类

  • 类装载过程

加载:通过classloader加载class文件字节码,生成.class对象

检查:检查加载class文件的正确性和安全性

准备:为类变量分配存储空间和设置类变量初始值

解析:JVM将常量池内的符号引用转换为直接引用 

初始化:执行类变量赋值和代码块

  • classloader和forname的区别

classloader:loadclass(name,false),解析为false,没有初始化。例如:spring ioc延迟加载

class.forname:

  • java内存模型

线程私有:程序计数器、java虚拟机栈、本地方法栈(native)

线程共享:MetaSpace、Java堆

  • java虚拟机栈

递归过深,栈帧数超出虚拟栈深度(引发java.lang.StackOverflowError)。每次递归,都会往栈里压一个栈帧,如果超过允许最大允许虚拟栈深度

解决思路:限制递归次数,或者使用循环的方法去替换递归

  • MetaSpace(类加载信息)

jdk1.8之前:元数据(MetaSpace)与永久代(PermGen)

元数据使用本地内存,永久代使用的是jvm的内存

  • MetaSpace比PermGen优势

字符串常量池存在永久代中,容易出现性能问题和内存溢出。jdk1.7把常量池移到堆内存中,jdk1.8移除永久代

类和方法的信息大小难易确定,给永久代的大小制定带来问题

永久代会为GC带来不必要的复杂性

  • java堆(Heap 常量池、数组和类对象)

在虚拟机启动时创建,是对象实例的分配区域。占主要内存

GC的管理主要区域

  • 判断对象是否被回收

引用计数器:为每个对象创建一个计数,有对象引用计数器+1,引用释放计数-1,当计数器为0时回收。缺点是不能解决循环引用问题

可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链时,此对象可以被回收

JDK6:当调用intern()方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串的引用。

我的理解:JDK6当调用intern()方法时,如果常量池存在不处理;如果常量池不存在,拷贝副本到常量池中。

JDK6+:当调用intern()方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于Java堆中,则将堆中此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。

我的理解:JDK6+当调用intern()方法时,如果常量池存在不处理;如果常量池不存在:堆已存在,将堆的引用存放在常量池中;堆不存在,在常量池创建

注:在JDK1.6的时候,字符串常量池是存放在Perm Space中的(PermSpace和堆是相隔而开的),在1.6+的时候,移到了堆内存中

  • java引用类型
  • JVM垃圾回收算法

标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

  • Java Bean生命周期

  • JVM配置列表

-Xss:每个线程虚拟机栈大小

-Xms:java堆的初始值(一般设置和Xmx一致,防止堆扩展影响)

-Xmx:java堆达到最大值

  • 内存分配策略

静态存储:编译时确定每个数据目标在运行时的存储空间需求

栈式存储:数据区需求在编译时未知,运行时模块入口

Linux查看进程和线程:

        1、查看所有进程线程数:pstree -p | wc -l
        2、查看指定进程线程数:pstree -p pid | wc -l
        3、查看系统用户最大进程数:ulimit -u
        4、查看系统支持最大线程数:cat /proc/sys/kernel/pid_max

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值