JVM基础

(Java的一些特点):

  • 平台无关性:一次编译,到处运行;
  • 垃圾回收机制(GC);
  • 语言特性:泛型、反射;
  • 面向对象:封装、继承、多态;
  • 自带的一些类库;
  • 有异常处理机制;

一、JVM理解

1. Java平台无关性

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令 。

2. 如何加载class文件

ClassLoader(类加载器):依据特定格式,加载class文件到内存;

Execution Engine(执行引擎):对命令进行解析;

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

Runtimer Data Area(运行时数据区):JVM内存空间结构模型;

3.反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

Class 类中的 getFields、getMethods 和 getConstructors 方 法 将 分 别 返 回 类 提 供 的public 域 、方法和构造器数组,其中包括超类的公有成员。Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域 、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员 。在 Method 类中有一个 invoke 方法,它允许调用包装在当前 Method 对象中的方法。

反射机制使得人们可以通过在运行时查看域和方法,让人们编写出更具有通用性的程序 。这种功能对于编写系统程序来说极其实用
,但是通常不适于编写应用程序 。 反射是很脆弱的,即编译器很难帮助人们发现程序中的错误,因此只有在运行时才发现错误并导致异常。

打印一个类的全部信息的方法:

import java.util.*;
import java.lang.reflect.*;

/**
 * This program uses reflection to print all features of a class.
 * @version 1.1 2004-02-21
 * @author Cay Horstmann
 */
public class ReflectionTest
{
   public static void main(String[] args)
   {
      // read class name from command line args or user input
      String name;
      if (args.length > 0) name = args[0];
      else
      {
         Scanner in = new Scanner(System.in);
         System.out.println("Enter class name (e.g. java.util.Date): ");
         name = in.next();
      }
      try
      {
         // print class name and superclass name (if != Object)
         Class cl = Class.forName(name);
         Class supercl = cl.getSuperclass();
         String modifiers = Modifier.toString(cl.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");
         System.out.print("class " + name);
         if (supercl != null && supercl != Object.class) System.out.print(" extends "
               + supercl.getName());

         System.out.print("\n{\n");
         printConstructors(cl);
         System.out.println();
         printMethods(cl);
         System.out.println();
         printFields(cl);
         System.out.println("}");
      }
      catch (ClassNotFoundException e)
      {
         e.printStackTrace();
      }
      System.exit(0);
   }

   /**
    * Prints all constructors of a class
    * @param cl a class
    */
   public static void printConstructors(Class cl)
   {
      Constructor[] constructors = cl.getDeclaredConstructors();
      for (Constructor c : constructors)
      {
         String name = c.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(c.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(name + "(");

         // print parameter types
         Class[] paramTypes = c.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all methods of a class
    * @param cl a class
    */
   public static void printMethods(Class cl)
   {
      Method[] methods = cl.getDeclaredMethods();
      for (Method m : methods)
      {
         Class retType = m.getReturnType();
         String name = m.getName();
         System.out.print("   ");
         // print modifiers, return type and method name
         String modifiers = Modifier.toString(m.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(retType.getName() + " " + name + "(");
         // print parameter types
         Class[] paramTypes = m.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all fields of a class
    * @param cl a class
    */
   public static void printFields(Class cl)
   {
      Field[] fields = cl.getDeclaredFields();
      for (Field f : fields)
      {
         Class type = f.getType();
         String name = f.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(f.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.println(type.getName() + " " + name + ";");
      }
   }
}

二、ClassLoader

1. 类从编译到执行的过程

1. 编译器先将源文件及编译为字节码文件;

2. 类加载器将字节码转换成JVM中的类的对象;

3. JVM利用类的对象实例化实例对象;

2. 什么是ClassLoader

类加载器主要工作在Class装载的加载阶段,主要作用是从系统外部获取Class二进制数据流。它是Java的核心组件,所有的Class都是由类加载器加载的,类加载器负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等工作。

3. 类加载器的种类

BootStrapClassLoader(引导类加载器):C++编写,加载核心库java.*,没有对应的ClassLoader对象。

ExtClassLoader(扩展类加载器):Java编写,加载扩展库javax.*。

AppClassLoader(系统类加载器):Java编写,加载程序所在目录下的类库。

自定义ClassLoader:Java编写,定制化加载。

如果要编写自己的类加载器,只需继承ClassLoader类,然后覆盖方法findClass(String className)。

ClassLoader超类的loadClass方法用于将类的加载操作委托给其父类加载器进行,只有当该类尚未加载并且父类加载器也无法加载该类的时候,才调用findClass方法,如果要实现该方法,必须做到以下几点:

1. 为来自本地文件系统或者其他来源的类加载其字节码;

2. 调用ClassLoader超类的defineClass方法,向虚拟机提供字节码;

4. ClassLoader的双亲委派机制

类加载器有一种父子关系。除了引导类加载器外,每个类加载器都有一个父类加载器。类加载器会为它的父类提供一个机会,以便加载任何给定的类,并且只有在其父类加载器加载失败时,它才会加载该给定类。例如,要求系统类加载器加载一个系统类(java.util.ArrayList)时,首先要求扩展类加载器进行加载,该扩展类加载器则首先要求引导类加载器进行加载,引导类加载器会找到并加载rt.jar中的这个类,无需其他类加载器做更多的搜索。

使用双亲委派机制的原因:避免加载多份同样字节码,加载一次,多次使用。classloader的执行顺序是:loadClass→findClass→defineClass。

5. 类的加载方式

1. 隐式加载:new

2. 显式加载:loadClass,forName等

显示加载获取到类对象之后,需要调用newInstance()方法生成对象的实例;

newInstance: 弱类型。低效率。只能调用无参构造。

new: 强类型。相对高效。能调用任何public构造。

newInstance()是实现IOC、反射、依赖倒置 等技术方法的必然选择,new 只能实现具体类的实例化,不适合于接口编程。类里面就是通过这个类的默认构造函数构建了一个对象,如果没有默认构造函数就抛出InstantiationException, 如果没有访问默认构造函数的权限就抛出IllegalAccessException。

6. LoadClass和forName的区别

类的装载过程:

  1. Class.forName得到的class是已经初始化完成的;

  2. Classloader.loadClass得到的class是还没有链接的;

三、Java内存模型

1. 程序计数器(Program Counter Register)

  • 当前线程所执行的字节码的行号指示器(逻辑);
  • 通过改变计数器的值来选取下一条需要执行的字节码指令,如分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成;
  • 和线程是一对一关系即“线程私有”;
  • 对Java方法计数,如果是Native方法则计数器值为空(Undefined);
  • 此内存区域不会发生内存泄露问题;

2. Java虚拟机栈(Java Virtual Machine Stacks)

  • jav方法执行的内存模型;
  • 包含多个栈帧,方法运行期间的基本数据结构,用来存储局部变量表,操作数栈,动态连接,返回地址等;方法调用结束时,帧才会被销毁;

 

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译成Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的最大局部变量表的容量。(boolean、byte、char、short、int、float、reference和returnAddress)

操作数栈其实就是实现对局部变量的的操作,如变量的运算、赋值等。在Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

3. 本地方法栈

与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

4. 方法区

方法区用来存储class的基本信息,如类的方法、常量、静态变量、即时编译器编译后的代码等数据。方法区有一种叫法是元空间(Meta Space),也有人叫“永久代”(Permanent Generation)。两者并不等价,但是两者都是方法区的实现。在Java7之后,原先位于方法区的字符串常量池被移动到Java堆中,在jdk1.8之后,用元空间替代了永久代。两者最大的区别是,元空间使用的是本地内存,永久代使用的是jvm的内存。

元空间相较于永久代有下面几个优势:

1. 字符串常量池在永久代中,容易出现性能问题和内存溢出;

2. 类和方法的信息大小比较难确定,给永久代大小的指定带来困难;

3. 永久代会给GC带来不必要的复杂性,效率较低。

5. 堆Heap

是对象实例的分配区域,是GC管理的主要区域。

6.JVM性能调优参数

-Xss:规定了每个线程虚拟机栈的大小,一般256k足够,此配置将会影响此进程中并发线程数的大小。

-Xmx:堆的初始大小,即该进程刚刚创建时他的专属Java堆的大小,一旦对象的容量超过Java堆的最大容量,将会扩容至-Xms设置的大小

-Xms:堆能达到的最大值,但是在设置的时候往往将-Xmx和-Xms的大小设置为一样,因为当堆不够用要扩容时,会发生内存抖动,影响程序运行时的稳定性。

7. Java内存模型中堆和栈的区别

1. Java内存模型中内存分配策略

静态存储:编译时确定每个数据目标在运行时的存储空间需求,这种方式不允许有可变的数据类型存在,不允许嵌套和递归结构。

栈式存储:程序模块的数据区大小在编译的时候时未知的,只有在运行到程序模块入口前才会确定大小,从而分配内存空间

堆式存储:编译时或运行到程序模块入口前都无法确定所需内存大小,动态分配。

2. Java内存模型中堆和栈的联系

引用对象、数组时,栈里定义变量来保存堆中的目标地址的首地址。

new Person()时会在堆内存中创建相关实例,同时将首地址赋值给p,保存在当前线程的虚拟机栈内存中,通过获取栈中的p可以获取队中的实例。

3. 区别

1. 管理方式:栈自动释放,堆需要GC;

2. 空间大小:栈比堆小;

3. 碎片相关:栈产生的碎片小于堆;

4. 分配方式:栈支持静态分配和动态分配,而堆只支持动态分配;

5. 效率:栈的效率高于堆;

 

public class HelloWorld {
    private String name;
    public void sayHello(){
        System.out.println("Hello" + name);
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        int a = 1;
        HelloWorld hw = new HelloWorld();
        hw.setName("test");
        hw.sayHello();
    }
}

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值