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设置为一样。应为避免程序执行后期内存不够或分配不及时。这两个值的大小将直接影响程序的性能<br />

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

  • 线程
    程启动运行时会有一个线程去启动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查看报文。

浏览器中的报文

但是上程序有一个问题,一个线程同一时间只能运行一段代码,在上面的例子中,逻辑处理是在主线程中运行的(当产生一个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配置中设置容器一启动就被创建、初始化而不是第一个访问后再被创建、初始化
    <load-on-startup>number(1or2or3)</load-on-startup>数字越小越早被创建、初始化

  • 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页:

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属性,都将是守护线程,用户线程同样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,178评论 11 349
  • 一、认识多任务、多进程、单线程、多线程 要认识多线程就要从操作系统的原理说起。 以前古老的DOS操作系统(V 6....
    GT921阅读 1,009评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,438评论 1 15
  • 为丧失提供的一个哀伤过程 --------心理咨询手记 人的成长过程中伴...
    恰如初阅读 342评论 0 0