JVM虚拟机

1 篇文章 0 订阅

虚拟机就是java的运行环境,使得java能一次编译,到处运行\

常见术语:

                JVM:java运行的最基本环境

                JRE:java Runtime Environment = JVM + 基础类库

                JDK:Java Development Kit = JVM+ 基础类库 + 编译工具

                javaSE:ava Standard Edition = JDK+基础开发

                javaEE: Java Enterprise Edition = JDK+基础开发+应用服务器

基本结构

        内存结构

        垃圾回收器

        java程序加载机制

java程序的生命周期

        编写源码->代码编译->虚拟机加载->虚拟机储存->虚拟机运行->垃圾回收

JAVA代码运行机制

        代码编译机制

        将.java文件编译成.class文件

javac xxx.java

对.class文件的编译方式有两种

        解释执行:逐条将字节码编译成字节码并执行

        既时编译:即将一个方法中包含所有的字节码编译成机械码在执行

Hotspot默认使用混合编译(mixed mode),那些编译,那些优化,则是有监视器(Profile Monitor)决定

        可以使用javap命令文件.class文件

javap -c xxx.class > xxx.txt

代码加载机制

        代码加载过程

                Loading:使用类加载器加载类

                LinKing:是指将创建成的类合并到java虚拟机中,使之能够执行的过程 

                Initializing:则是为标记为常量值的字段赋值,利用这个机制可以使用的单例模式的代码可以延时加载

类加载器

        多个类加载器        

        Bootstrap ClassLoader:加载$JAVA_HOME中jre/lib/rt.jar里面所有的class或Xbootclassoatch选项指定的jar包

        Extension ClassLoader:加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

        App ClassLoader:加载classpath中指定的jar包及Djava.class.path所指定目录下的类和jar

        Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身 需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

双亲委派原则:

        由于出现了多个类加载,那么就可能会出现类重复加载的问题.

        当一个类加载器收到类加载的指令的时候,自己不会去加载,而是让父类去加载,如果父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需要的加载),然后自己加载

        如此就可以保证重复被类加载器加载的类是同一个

代码运行机制

        内存可以分为两部分

                代码的储存区域        

                        方法区

                         堆

        代码的运行机制

                栈

                计数器

                本地方法区(主要运行native的方法)

代码被加载之后都存在方法区,代码执行过程中创建的对象存储在堆中

代码主要实在栈里面运行

        每个线程都会在栈里面被分配一块独立的内存空间

        每个线程中执行的方法,会当前栈中,再次被分配一个空间,即栈帧

JVM的内存结构

        程序计数器(Program Counter Register )

                用于多线程在切换了记录当前运行位置,以保证切换回来的时候可以继续

                是唯一一个没有内存溢出的空间

虚拟机栈

        Java Virtual Machine Stacks(Java 虚拟机栈) 是每个线程运行时所需要的内存。

        每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

        每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

        栈内存可能出现的异常的情况

                -(1)当固定大小情况下,线程请求分配的栈容量大于Java虚拟机栈最大容量时,抛出异常:StackOverFlowError。

                - (2)当可拓展时,如果在拓展过程中,无法申请到足够的内存时,抛出异常:OutOfMemoryError(比如:JVM运行内存被占满,此时已经无处可以申请内存了)。

栈内存的大小

        可以自定义设置栈的大小,通过java运行的执行设置栈的大小

java -Xss1024k  Test001
java -XX:ThreadStackSize=1024 Test002

查看默认的栈大小

java -XX:+PrintFlagsFinal -version | findstr /i "ThreadStackSize"

 不同的操作系统栈大小不同

Linux / ARM(32位):320 KB
Linux / i386(32位):320 KB
Linux / x64(64位):1024 KB
OS X(64位):1024 KB
Oracle Solaris / i386(32位):320 KB
Oracle Solaris / x64(64位):1024 KB

  一台服务器可以创建线程的计算公式,根据栈的大小计算可以创建线程的数量

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

- MaxProcessMemory 指的是一个进程的最大内存

- JVMMemory JVM内存

- ReservedOsMemory 保留的操作系统内存

- ThreadStackSize 线程栈的大小

本地方法栈

1,本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。本地方法栈服务的对象是JVM执行的native方法,其就是一个java调用非java代码的接口,作用是与操作系统和外部环境交互

2,某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数以某个确定的顺序压入栈,它的返回值也以确定的方式传回调用者。同样,这就是虚拟机实现中本地方法栈的行为。

3,能本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。

4,描绘了这样一个情景,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。

        heap定义

                堆内存被所有线程共享

                通过new出来的对象都是在堆里面

                堆会出现内存溢出问题

        堆的大小

                有初始 大小

                有最大值大小

        //字节大小 K -> M -> G -> T
        double initialMemory=  Runtime.getRuntime().totalMemory()/1024/1024;
        double maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
        System.out.println("堆内存的初始总量Xms:"+initialMemory);
        System.out.println("堆内存的最大总量Xmx:"+maxMemory);

默认情况下,初始内存大小=物理电脑 内存大小/64,最大内存大小=物理电脑内存/4

修改初始值和最大值的内存大小

-Xms10m -Xmx10m

-Xms10m 初始值的大小 -XX:InitialHeapSize=10m

-Xmx10m 最大值的大小 -XX:MaxHeapSize=10m

        查看正在运行的java程序状态

                jps:查看系统上运行的java程序进程

                jstat:查看java程序的运行状态

                jvisualvm:可以调用jdk提供的可视化工具进行查看

堆内存结构        

        堆内存要分一下结构

                新生代+老年代+永生代(1.7)

                新生代+老年代+元空间(1.8)

        新生代:新创建的对象就是放在新生代

                eden区:所有对象最先出生的地方

                Servivor区:又分为s0和s1也就是from和to

                新生代和老年代的比例是1:2

                如果eden满了会发生MinorGC

                        在eden区满了之后会触发MinorGC,将eden中存活下来的对象放入到Servivor区的s1,然后清空eden区的内存

                        等eden的区的内存在在满了之后,又会触发MinorGC,会将S1中所有存活下来的对象和eden中存活下来的对象都放入到s0的区域,然后将s1和eden区的对象全部清空

                        如果在minorGC中,Servivor区放不下了,就会放入到老年代中

Servivor区如果满了,会将一部分对象放到老年代

老年代:
        生命周期长的储存对象

        一般而言新生代的对象经过15次GC没有被释放就会被放入老年代

        如果老年代满了就会进行MajorGC

        如果MajorGC并释放老年代的对象,那么就会OOM

永生代

        在1.7的时候大小是固定的

        在1.8之后就改成了matespace元空间,大小不受限制,和物理内存一样大

内存的活动的流程:

        

程序运行会先加载程序的元数据放入到meta区

在程序的过程中逐步的创建对象放入到Eden区

当Eden区中的内存满了以后,会进行Young GC/minGC,其中大多数的对象被回收,没有被回收的拷贝到(Copying)s0区

再次YGC,将eden区和S0区的没有被回收的对象拷贝到S1区

再次YGC,eden+S1 -> S0,然后循环4,5

年龄足够拷贝到老年代(一般是15次,CMS6次)

如果新创建的对象占用内存很大,则直接分配到老年代,当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

关于GC

        在内存GC的时候所有线程都会暂停SWT

        minGC触发条件,就是eden区内存满了

        oldGC触发条件,就是老年代内存满了

        fullGC=oldGC+minGC+mateGC       

                调用System.gc()时,系统建议执行Full GC,但是不必然执行。

                        - 老年代的空间不足。

                        - 方法区的空间不足。

                        - 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。

                        - 年轻代放不下,放到老年代中,若该对象大小大于老年代,也会触发Full GC。

                尽量减少内存的碎片

方法区    

        和堆内存差不多,逻辑上其实就是堆内存

        主要是存放的内容

                方法区需要储存每个加载的类(类,接口,枚举,注解)

                域(属性)信息

                JVM需要保存所有方法的信息及其声明的顺序

                non-final的类变量(static)

方法区大小设置

        1.7的设置

-XX:Permsize 设置永久代初始分配空间
-XX:MaxPermsize 设定永久代最大可分配空间
OutOfMemoryError:PermGen space OOM错误

       1.8的设置

-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 元数据区大小,其默认值依赖于平台。windows下,-XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1 即没有限制。

方法区大小测试

//-XX:MaxMetaspaceSize=100m
public class TestMethod extends ClassLoader  {
    public static void main(String[] args) {
        int j=0;
        TestMethod test = new TestMethod();
        try{
            for(int i=0;i<300000;i++){
                //ClassWriter作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                //版本号,public,类名,包名,父类,接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
                //返回byte[]
                byte[] code = cw.toByteArray();
                //执行了类的加载
                test.defineClass("Class"+i,code,0,code.length);
                j++;
            }
        }finally {
            System.out.println(j);
        }
    }
}

垃圾回收

如何定义垃圾

引用计数法(reference count):如果指向当前对象引用是0那么这个对象就会被回收,虽然简单,但是不能解决循环引用的问题

根可达性分析(RootSearch):为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

垃圾如何清理

        标记清理(Mark-Sweep)最基础的垃圾回收算法,分为两个阶段,

                标记和清除.

                        标记阶段标记出所有要清除的对象,

                        清除阶段清除被标记的对象所占用的空间

        该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到利用空间的问题

        复制算法(copying)为了解决Mark-Sweep算法内存碎片化的缺陷而提出的算法,按内存的容量划分为两块等大小的.

        但是最大的问题是可用内存被压缩到了原本的一半.并且存货对象增多的话,Copying算法的效率大大降低

  • 标记压缩算法(Mark-Compact**)**:结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象

  • 分带收集算法:分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

垃圾回收算法

        在1.8默认情况下使用的是UseParallelGC

        查看默认的垃圾回收器的算法

java -XX:+PrintCommandLineFlags -version

如何切换垃圾回收器

-XX:+UseG1GC

可以通过jinfo查看,垃圾回收器的配置 

小结:

        JVM的基本构成

                代码运行机制

                内存结构

                垃圾回收算法

        代码运行机制

                解释翻译+JIT既使翻译

                双亲委派机制

        内存结构

                程序计数器

                        唯一没有内存泄漏问题

                虚拟机栈

                        每个线程是独立拥有的

                        设置栈的大小

                本地方法栈

                        执行C方法的内存空间

                堆空间

                        新生代

                                eden区

                                suvivior区

                老年代

                元空间

                垃圾回收方式

                        minGC

                        oldGC

                        fullGC=minGC+oldGC

                设置堆大小的指令和查看内存变化的工具

        方法区

                用于储存代码,静态变量的区域

        垃圾回收

                如何定义垃圾

                回收的基本算法

                各种垃圾回收器

                        在1.8设置使用G1垃圾回收器

                

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值