java知识扩展

jvm进程

JVM进程可以由bin\jps查看。在命令行下输入jps

  1. 由一个jdk文件系统,可以产生很多jvm进程(没bin\java.exe执行后产生一个)
  2. JVM进程有个pid,默认情况下河他执行的main所在的类相同
  3. bin\jconsole 可以监视和管理java程序
  4. jvm的三大财产
  • 内存
    内存是JVM拥有的主要财产之一,内存你看到了分堆和非堆,这两个值是 在执行命令java.exe是可以修改的,如
    java.exe 类名 -Xmx3550m -Xms3550m -XX:MaxPermSize=16m
    -Xmx堆内存最大
    -Xms堆内存最小
    -XX:MaxPermSize 非堆 (放class,static var)

一般在实际的过程中将Xms与 Xmx设置为一样。应为避免程序执行后期内存不够或分配不及时。这两个值的大小将直接影响程序的性能

在eclipse中,可以在点Run configurations...后界面设置.

java知识扩展_第1张图片
  • 线程
    程启动运行时会有一个线程去启动main方法
    除了main线程,其它都是JVM内置的,我们自己没有开启.在实际运行中,这里的线程太多和太少都不行,要维持在一个合理的范围,而且也要时刻关注他们的状态。如果程序中一个线程都没有,那么进程也死了。


  • 其实就是程序,包括JRE中的类,使用的第三方的jar
    包和我们应用中自己写的程序,这些类加载进入内存,都放在非堆中。
    所以我们JVM进程是一个有身份证(pid),有姓名(名称),拥有内存,程类(程序)的一个静态实体(CPU无法调度执行)。

java类加载器基本知识

java web server 基本实现原理

  1. 远端服务器使用ServerSocket技术打开一个端口,等待请求的到来。
  2. 浏览器得到用户输入的地址(url)或者得到点击联接地址(url)。
  3. 浏览器看输入的是IP还是域名,如果是域名,通过查找DNS服务找到此域名IP,并缓存到浏览器中,以加快下次查找速度。
  4. 浏览器组装满足HTTP协议的数据包(请求报文)。
  5. 浏览器请求在本机随机开启一个端口与服务端IP和服务端端口建立联接 (TCP)。(本机IP + 本机端口 + 服务端IP + 服务端端口,用来唯一标示一条TCP 联接)
  6. 通过此联接发送HTTP数据包。
  7. 服务端通过端口接收到数据之后,解析HTTP数据包,组装成良好的格式,并调用程序处理。
  8. 服务程序处理完成之后,服务端组装满足HTTP协议的数据包(响应报文)通过TCP联接返回到请求端口上。
  9. 浏览器从请求端口得到数据解析响应报文得到相应数据后给浏览器软件进行解析渲染。
  10. 请求关闭联接

先用socket技术实现一个main方法

public class WebServer {

    //服务端Socket只要一个,所以定义成static, 同一时间只能一个线程访问(主线程)
    private static ServerSocket ss;

    public static void main(String[] args) throws IOException {
        //绑定端口,只会执行一次
        ss = new ServerSocket(8999);
        //主线程永远不死,所以用了个死循环
        while (true) {
            //这是一个IO阻塞式语句,也就是如果没有请求(IO操作)进来,当前线程会在此等待,不再向下执行
            Socket socket = ss.accept();


            //得到请求报文(内容)
            StringBuffer sb = new StringBuffer();
            PrintWriter pw = null;
            try {
                InputStream socketIn = socket.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
                while(true) {
                    String msg = br.readLine();
                    sb.append(msg);
                    System.out.println(msg);
                    if (msg == null || msg.trim().equals("")) {
                        break;
                    }
                }
                
                //输入响应报文
                pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));

                pw.println("HTTP/1.1 200 OK");
                pw.println("Content-Type: text/html;charset=UTF-8");
                pw.println();   //如果注释掉这句,下面的html不会打印出来,未出头部

                pw.write("html");
                pw.flush();

            } catch (IOException exception) {
                //TODO 处理异常
            } finally {
                socket.close();
                pw.close();
                //socket = null;
            }
        }
    }
}

启动程序后,在浏览器输入http://127.0.0.1:8999/abc查看报文。

java知识扩展_第2张图片
浏览器中的报文

但是上程序有一个问题,一个线程同一时间只能运行一段代码,在上面的例子中,逻辑处理是在主线程中运行的(当产生一个JVM进程时,同时会产生一个主线程,main方法中的代码就是在主线程中执行),当一个请求还在处理逻辑和输出时,此时又来了一个请求,那么此请求将会被阻塞。所以我们可以把程序调整成如下样子。

public class WebServer {

    //服务端Socket只要一个,所以定义成static, 同一时间只能一个线程访问
    private static ServerSocket ss;

    public static void main(String[] args) throws IOException {
        ss = new ServerSocket(8999);
        //有线程永远不死,所以用了个死循环
        while (true) {
            Socket socket = ss.accept();
            Thread thread = new Thread(new Handler(socket));
            thread.start();
        }
    }
}

public class Handler implements Runnable  {

    private Socket socket;
    public Handler(Socket socket){
        this.socket=socket;

    }
    @Override
    public void run() {
        StringBuffer sb = new StringBuffer();
        PrintWriter pw = null;
        try {
            InputStream socketIn = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
            while(true) {
                String msg = br.readLine();
                sb.append(msg);
                System.out.println(msg);
                if (msg == null || msg.trim().equals("")) {
                    break;
                }
            }
            pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));

            pw.println("HTTP/1.1 200 OK");
            pw.println("Content-Type: text/html;charset=UTF-8");
            pw.println();

            pw.write("html");
            pw.flush();

        } catch (IOException exception) {
            //TODO 处理异常
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pw.close();
            //socket = null;
        }
    }
}

这中间的main方法相当于一个公司的前台,将客户(请求) 接入到公司,安排其他人来处理。
这样每个请求一个线程,运行完成后,线程就死了,这样主线程负担就轻了,不会发生阻塞了,但是新问题又来了,每个请求一个线程,那线程太多了,所以我们真正应该使用线程池。

servlet注意点

  • servlet可以在web配置中设置容器一启动就被创建、初始化而不是第一个访问后再被创建、初始化
    number(1or2or3)数字越小越早被创建、初始化

  • init(servletconfig) 可以从web.xml中获取初始化数值。

  • servlet 中变量要定义在方法内,不允许放在servlet 下,防止线程串行,线程就不安全(如果这样,会产生用户a的线程进行到一半,用户b的线程进来将用户a的信息替换,用户a有可能登陆到用户b的账号)
    无状态的对象(只有方法或变量为只读)可以放在servlet第一层下。
    有状态的对象(变量可以改变)注意线程安全问题。

线程注意点

  1. 线程、进程、程序之间的关系
    CPU是所有进程共同拥有的,所有进程(包括所有JVM进程和非JVM进程)。大家都知道一个CPU在一个时间点,只能运行一行指令,也就是我们的程序,在Java中,所有程序都必须运行在线程里,所以CPU是调度线程再运行线程中的指令(程序),这些指令在运行时会向它的进程申请使用公共财产(堆内存),同时线程也有自己的私有财产(栈内存),这样就构成了”内存(堆)”,”线程(栈内存)”,”类(程序)”三者之间的关系,打个比方来说:
    一个家庭有夫妻两个,他们都有自己的小金库,同时也有家庭共同的财产,丈夫在做一件事情时,他要审请家庭财产,同时要使用自己的小金库才可以完成。那么在这个例子中,家庭就是一个进程,夫妻是两个线程,共同的财产是堆内存,小金库是栈内存,事情就是类(程序)。夫妻在家庭这个空间中生存,如果夫妻两人不幸都死了,那这个家庭就不存在了(相当所有线程死了,进程也就死了),但只要有一个还在,家庭就还在(进程中只要有一个线程还存活,进程就还存活)。
  2. 主线程的产生
    启动一个JVM进程时,JVM会自动为我们创建一个线程,把它命名成”main”, 并把这个类中的main方法(程序)放到这个线程中的run方法中去执行。
  3. java中产生线程的方式
    在java编程时,除了main线程是由JVM为我们产生的以外,其它所有线程都由我们自己的程序生成。
    Java中定义线程的方式有两种,继承Thread和实现Runnable接口。我们来看如下程序:
package com.zhengmenbb.thread;
public class ChildThread implements Runnable {
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
       for(long i = 0; i < Long.MAX_VALUE; i++) {
       }
   }
}

上面程序使用Runnable定义了一个线程类,在主程序(或者其它程序)中我们这样调用:

package com.zhengmenbb.thread;
public class TestMain {

   public static void main(String[] args) {
       //System.out.println(Thread.currentThread().getName());
       Thread thread = new Thread(new ChildThread());
       thread.start();
   }
}

运行main方法,你会看到生成的线程名字为:Thread-0, 当然你可以通过thread这个线程对象引用来重设他的名字,优先级等。使用jconsole我们打开这个进程的线程tab页:

java知识扩展_第3张图片
Paste_Image.png

会发现main线程已死,但是Thread-0还活着,因为我使用了一个时间很长的循环.这也证明了只要一个线程还活着,进程是不会死的, 但如果你等把Thread-0中run方法执行完成了,进程就死了。记住,只要run方法中的代码执行完成了,线程就死了.


我们再来看线程的另一种定义方法:继承 Thread

package com.zhengmenbb.thread;
public class ChildThread1 extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        for(long i = 0; i < Long.MAX_VALUE; i++) {

        }
    }
}

在主程序(或者其它程序)中我们这样调用:

package com.zhengmenbb.thread;
public class TestMain {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        Thread thread = new ChildThread1();
        thread.start();
    }
}

两种方式只是定义线程类方式不一样,运行时产生的线程是一样的,强烈建议使用Runnable接口方式。

  1. ”用户线程”和“守护线程”

请看如下代码:

package com.zhengmenbb.thread;

public class TestMain {

    public static void main(String[] args) {

        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
          public void run() {
          System.out.println("JVM 退出了");
          }
        });

        Thread thread = new Thread(new ChildThread());
        thread.setDaemon(true);
        thread.start();
    }
}  

在上面这段代码中,我把线程的daemon(“守护”),设置了true, 你再运行这段代码,发现JVM进程很快退出了。我们知道main线程run方法运行很快,所以很快就死了,相当妻子死了,Thread-0这个线程我们设置了daemon(“守护”),也就是说妻子死了,丈夫要守护她,也自杀随她去了,这样家庭(JVM)就死了。

那下面我们定义一个“用户线程”和“守护线程”:

“用户线程”: 只要有一个这样的线程还活着,JVM就不会退出,这样的线程我们定义为用户线程. 其实是主线程和我们把daemon(“守护”),设置为false的线程。

“守护线程”:只要没有用户线程存活了,我就会自杀,这样JVM主会退出。只要有用户线程活着,我也活着。这一类线程我们称为“守护线程”, 其实就是把daemon(“守护”),设置为true的线程。

值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。

你可能感兴趣的:(java知识扩展)